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

Viewed 56151

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});

7 Answers

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;
  });

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.)

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);
...


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>;
});

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);
};

this worked for me

import { RootStateOrAny, useSelector } from "react-redux"

const search = useSelector((state: RootStateOrAny) => state.searchObj.values)

in non-react components just functions or hooks you can do

import {store} from "../redux/store"

const search: SearchProps | any = store.getState().searchObj.values

it is just about types that you need to satisfy to this hook, useSelector accepts DefaultRootState type, you can override its default types through this

type User = {
  firstname: string;
  lastname: string;
};
type SelectorTypes = {
  users: User[];
  //...other reducers
};
const router = useSelector<SelectorTypes>((state) => state.users);
Related