Skip to content

Help me understand 'Types.RootState' #85

@kilgarenone

Description

@kilgarenone

Sorry if this was a dump question- In this connected component, I'm confused about the Types and Types.RootState. Is the Types a npm package cuz it doesn't seem to be in the package.json?

Activity

piotrwitek

piotrwitek commented on Jul 25, 2018

@piotrwitek
Owner

This is a global scope type annotation, if you'd open project and search all references everything should be clear

carpben

carpben commented on Feb 3, 2019

@carpben

Thanks for this guide.
I find import Types from "Types" very confusing. According to the syntax Types refers to a library named Types, but after checking in NPM it's not the case. Shouldn't it be import Types from "./Types".

There is still the challenge of how and where to create the "RootState" type. There should be a State type defined in each of the separate reducers. I guess file containing the combine reducers is the appropriate place for the RootState type. Makes sense?

piotrwitek

piotrwitek commented on Feb 3, 2019

@piotrwitek
Owner
chawax

chawax commented on Feb 3, 2019

@chawax
Contributor

I use typesafe-actions library to achieve this. It makes it easy to create a RootState type that you can export from the same file where you combine reducers.

For example :

import { combineReducers } from 'redux'
import { StateType } from 'typesafe-actions'
import { reducer as formReducer } from 'redux-form'
import { reducer as authReducer } from './AuthRedux'
import { reducer as navReducer } from './NavigationRedux'

export const reducers = combineReducers({
  auth: authReducer,
  form: formReducer,
  nav: navReducer,
})

export type RootState = StateType<typeof reducers>

Then it mapStateToProps for example :

const mapStateToProps = (state: RootState) => ({
  isAuthenticated: state.auth.isAuthenticated,
})
piotrwitek

piotrwitek commented on Feb 3, 2019

@piotrwitek
Owner

@chawax I used that in the past but it's not an optimal solution and introduces all kind of issues when scaling your codebase, primarily because then in your components you are adding hard-dependency on this module like this:

import {RootState} from '../../rootReducer'; <= hard dependency

It will add all of your reducers and their dependencies as a dependency to your components and you'll have to mock tons of unrelated stuff when trying to test in isolation. That means you have tight coupling in your codebase and this is considered a code smell.

With my approach (the one that is using purely ambient type definition to define global scope types using d.ts files #97), you're not be impacted by any of that issues.

piotrwitek

piotrwitek commented on Feb 3, 2019

@piotrwitek
Owner

@chawax I just realized there is more to my argument above that needs to be added here:

  1. In theory, I think you could prevent a hard-dependency issue by using a type level import syntax, but I haven't confirmed that so you have to check yourself.
type RootState = import("../../rootReducer'").RootState;

// but it's still more cumbersome to use and more error-prone
// when using with other global types, consider comparing it to this:
import { RootState, Services, RootAction, TodoModel, ... } from '@Types';
  1. A more important argument here is the scalability of that solution. You can very easily extend and remove any global level type because of the inversion of control principle.
    Also your @Types imports are consistent in every module across the entire application without a worry where does it come (you can always jump to it with F12). And all of the above reasons will make your application easier to extend and to maintain in the long run.
chawax

chawax commented on Feb 3, 2019

@chawax
Contributor

Something like that ?

types/index.d.ts file :

import { IState as AuthState } from '../redux/AuthRedux'

export interface RootState {
  readonly auth: AuthState;
}

with AuthState defined in my AuthRedux file

Then :

import { RootState } from '../types';

const mapStateToProps = (state: RootState) => ({
  isAuthenticated: state.auth.isAuthenticated,
})
piotrwitek

piotrwitek commented on Feb 3, 2019

@piotrwitek
Owner

Yes correct, but notice I prefer to add "Ambient Modules" technique (linked above) because that gives me absolute import capability, without paths mapping and aliasing configuration concerns.

I don't really like relative imports for global stuff because it's hard to maintain.

chawax

chawax commented on Feb 3, 2019

@chawax
Contributor

I don'k now ambient modules (a lot of things to learn about TS yet !). Do you have any example how you declare such modules to have absolute imports ?

piotrwitek

piotrwitek commented on Feb 3, 2019

@piotrwitek
Owner

Please check /playground project here in the repository, you can find it in the store folder

chawax

chawax commented on Feb 3, 2019

@chawax
Contributor

Great ! Thanks a lot !

carpben

carpben commented on Feb 3, 2019

@carpben

@piotrwitek ,

  1. What do u mean by hard dependency? Is it that soft dependency a simple form of a type, and hard dependency the same type but "mapped", or interpreted from another type? I guess the main problem with such types is that they are more difficult to read. Would you consider
const reducers = combineReducers({reducer1, reducer2})
type RootState = ReturnType<reducers>

When RootState is imported would you consider it to be a hard import?

  1. regarding 'Ambient Modules technique' I read the resources you suggested. I've noticed you defined type of Types here: https://github.com/piotrwitek/react-redux-typescript-guide/blob/master/playground/typings/modules.d.ts . But how does it know (or where is it defined) which object to import?
piotrwitek

piotrwitek commented on Feb 3, 2019

@piotrwitek
Owner

Ad1) By hard dependency I mean that you have a runtime dependency that you have to mock it during testing in isolation. I agree the example I have provided is not the best one, but you can imagine real-world situation (commonly happens with the ducks-pattern), when you're importing the type (State, ActionType) and some other runtime export (selectors, actionCreators) from the same file, then it'll be a problem.
But with the clear separation of type imports (*.d.ts files) and runtime imports from regular modules the intention is more clear and it's easier to find the problem in such case.

Ad2) I don't understand your question. An ambient definition doesn't import anything, you simply extend its definition across multiple files, that's how it works.

carpben

carpben commented on Feb 14, 2019

@carpben
  1. So In other words, An import
import {RootState} from '../../rootReducer'; <= hard dependency

Though it just imports a type, it will make rootReducer a dependency of the component file, which will create a problem when trying to test the component independently?

4 remaining items

Loading
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @piotrwitek@chawax@kilgarenone@carpben

        Issue actions

          Help me understand 'Types.RootState' · Issue #85 · piotrwitek/react-redux-typescript-guide