TS2339: Property 'tsReducer' does not exist on type 'DefaultRootState'

44,901

Solution 1

It is complaining about the type. Quick solution would be adding any as state type.

Proper solution will require following two steps:

  1. Create RootState type in Root Reducer.
export const rootReducer = combineReducers({
  dashboard: dashboardReducer,
  user: userReducer
});

export type RootState = ReturnType<typeof rootReducer>
  1. Provide RootState type to state object.
  let userData = useSelector((state: RootState) => {
    return state.user.data;
  });

Solution 2

You need to declare the type of the state argument in your selector, like:

const isDialogOpen = useSelector( (state: RootState) => state.tsReducer.isDialogOpen);

Please see the Redux docs on TypeScript usage, as well as the React-Redux docs page on static typing for examples.

(Also, as a stylistic note: please don't call that tsReducer in your root state. Give it a name that matches the data it's handling, like state.ui.)

Solution 3

For me, a better solution than specifying state in useSelector would be as below.
As in the node_modules/@types/react-redux/index.d.ts, you can use module augmentation.

/**
 * This interface can be augmented by users to add default types for the root state when
 * using `react-redux`.
 * Use module augmentation to append your own type definition in a your_custom_type.d.ts file.
 * https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
 */
// tslint:disable-next-line:no-empty-interface
export interface DefaultRootState {}

Do as below

  1. Export AppState in reducer src/reducer/index.ts
const reducers = combineReducers({
  userReducer,
});

export type AppState = ReturnType<typeof reducers>;
  1. Create new your_custom_type.d.ts. (I prefer react-redux.d.ts).
    In src/@types/your_custom_type.d.ts
import 'react-redux';

import { AppState } from '../reducers';

declare module 'react-redux' {
  interface DefaultRootState extends AppState { };
}
  1. Add typeRoots in tsconfig.json
{
  "compilerOptions": {
    ...
    "typeRoots": ["src/@types"]
  }
}

You can use as below without specifying AppState

import React, { memo } from 'react';
import { useSelector } from 'react-redux';

export default memo(() => {
  const isLoggedIn = useSelector(
    ({ userReducer }) => userReducer.isLoggedIn
  );
  return <div>{isLoggedIn}</div>;
});

Solution 4

If you are using react-redux, another out of the box solution would be to use RootStateOrAny.

import { RootStateOrAny, useSelector } from 'react-redux';

// and then use it like so in your component
...
const authState = useSelector((state: RootStateOrAny) => state.auth);
...


Solution 5

These are useful articles.

  1. https://redux.js.org/recipes/usage-with-typescript#define-root-state-and-dispatch-types
  2. https://redux.js.org/recipes/usage-with-typescript#define-typed-hooks

So first define RootState and AppDispatch like following:

//at store/index.ts
const rootReducer = combineReducers({
  tsReducer: tsReducer
});
const store = createStore(rootReducer)

export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch

And then define hooks (useAppDispatch, useAppSelector) can be used in components.

//at store/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
import type { RootState, AppDispatch } from './'

export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector

And use it on component like following:

import {useAppSelector} from '../store/hooks'
//...
const TsApp = (): JSX.Element => {
    const dispatch = useDispatch();

// ERROR should be fixed
    const isDialogOpen = useAppSelector(state => state.tsReducer.isDialogOpen);
};
Share:
44,901
Ry2254
Author by

Ry2254

Updated on July 09, 2022

Comments

  • Ry2254
    Ry2254 almost 2 years

    Struggling with the above question. Seen similar questions but cannot figure it out.

    The below code is me attempting to open and close a dialog using TypeScript for first time in an existing React project which uses .js and .jsx.

    import Button from '@material-ui/core/Button';
    import Dialog from '@material-ui/core/Dialog';
    import DialogActions from '@material-ui/core/DialogActions';
    import DialogContent from '@material-ui/core/DialogContent';
    import {useDispatch, useSelector} from 'react-redux';
    import {closeTsDialog} from '../actions/tsDialog'
    import {ActionTypes} from '../actions/types';
    
    const TsApp = (): JSX.Element => {
        const dispatch = useDispatch();
    
    // ERROR SHOWS UP ON LINE BELOW "state?.tsReducer?.isDialogOpen"
        const isDialogOpen = useSelector(state => state?.tsReducer?.isDialogOpen);
        const state = useSelector(s => s);
        console.log('->>>>>> state', state);
    
    
        // main tsx excluded to allow for posting on stackoverflow
    };
    
    
    export default TsApp;
    
    import {TsDialogAction} from "../actions/tsDialog";
    
    
    const initialState = {
        id: 0,
        isDialogOpen: false
    };
    
    const tsReducer = (state: TsDialogAction = initialState, action: Action) => {
        switch (action.type) {
            case ActionTypes.closeDialog: {
                return {...state, isDialogOpen: false};
            }
            case ActionTypes.openDialog: {
                return {...state, isDialogOpen: true};
            }
            default:
                return state;
        }
    };
    
    export default tsReducer;
    

    import {ActionTypes} from './types';

    export interface TsDialogAction { isDialogOpen: boolean number: number }

    export interface CloseTsDialog { type: ActionTypes.closeDialog payload: TsDialogAction }

    export interface OpenTsDialog { type: ActionTypes.openDialog payload: TsDialogAction }

    export interface Increment { type: ActionTypes.increment payload: TsDialogAction }

    export interface Decrement { type: ActionTypes.decrement payload: TsDialogAction }

    export const closeTsDialog = (id: number) => ({type: ActionTypes.closeDialog, payload: id}); export const openTsDialog = (id: number) => ({type: ActionTypes.openDialog, payload: id}); export const incrementAction = (id: number) => ({type: ActionTypes.increment, payload: id}); export const decrementAction = (id: number) => ({type: ActionTypes.decrement, payload: id});

  • Ry2254
    Ry2254 about 4 years
    Thanks for taking the time to answer the question. However, I am still seeing the same error.
  • markerikson
    markerikson about 4 years
    Can you update the question with the actual type declaration code you're trying to use?
  • Ry2254
    Ry2254 about 4 years
    updated question as requested but wasn't allow to put it in code
  • Priyanshu Sekhar
    Priyanshu Sekhar over 3 years
    Elegant solution !
  • Muganwas
    Muganwas about 3 years
    This seems to be the best answer, thank you @Yuvraj
  • Erkka Mutanen
    Erkka Mutanen about 3 years
    Great answer and this solution is tidy and it works.
  • Quinton Chester
    Quinton Chester almost 3 years
    Fantastic solution. Thanks for sharing... way cleaner than importing AppState on every usage. 👍
  • Mohammad Ahmad
    Mohammad Ahmad about 2 years
    Great solution. Actually, this could work with any other libraries too. Brilliant