React-Redux useSelector typescript type for state
Asked Answered
C

16

87

I'm using the useSelector(state => state.SLICE_NAME) hook from React-Redux however I'm having difficulty defining the state parameter. It is set by default as unknown so I get an error when I try to return state.SLICE_NAME (Error: Object is of type 'unknown').

How do I define the state type without having to manually create a separate state type and adding in every new state definition as they're created?

I've tried defining state as typeof store however that doesn't work.

Some code to help explain:

// navDrawer.ts

import { createSlice } from "redux-starter-kit";

// navDrawer initial state
export interface NavDrawerInitialState {
  open: boolean;
}

const navDrawerInitialState: NavDrawerInitialState = {
  open: false
};

// Create state slice
const { actions, reducer } = createSlice({
  slice: "navDrawer",
  initialState: navDrawerInitialState,
  reducers: {
    open: (state: NavDrawerInitialState) => {
      state.open = true;
    },
    close: (state: NavDrawerInitialState) => {
      state.open = false;
    }
  }
});

export const navDrawerActions = actions;
export default reducer;
// reducers.ts

import navDrawer from "./navDrawer";

const reducers = {
  navDrawer
};

export default reducers;
// store.ts

import { configureStore } from "redux-starter-kit";
import reducer from "./reducers";

const store = configureStore({
  reducer
});

export default store;
// Page.tsx

import React, { FC } from "react";
import { Provider } from "react-redux";
import store from "./store";
import ChildComponent from "./ChildComponent";

const StateProvider: FC = () => {
  return <Provider store={store}><ChildComponent /></Provider>;
};

export default StateProvider;
// ChildComponent.tsx

import React, { FC } from "react";
import { useSelector } from "react-redux";

const ChildComponent: FC = () => {
  const navDrawerState = useSelector(state => state.navDrawer); // ERROR OCCURS HERE. "state" is defined as 'unknown' so "state.navDrawer" throws an error.
  return <div>Text</div>
}

Edit: I noticed that the type definition for configureStore() contains the state as the first generic type. See screenshot below. If I can get the first generic value from EnhancedStore then I'll be able to use that to define state. Is there any way I can do this in Typescript?

enter image description here

Corby answered 13/8, 2019 at 6:18 Comment(1)
A lot has changed since this question was asked. https://mcmap.net/q/237357/-react-redux-useselector-typescript-type-for-state is the "most" correct answer currently. @Corby you should select an answer.Myrt
K
135

This might not be the answer but I use it like so:

const isLoggedIn = useSelector<IRootState, boolean>(state => state.user.loggedIn);

EDIT: Or use Peter's answer which is shorter/cleaner

const isLoggedIn = useSelector((state: IRootState) => state.user.loggedIn);

FYI: To get the IRootState, there are a couple of options (from the docs):

From combineReducers():

import { combineReducers } from '@reduxjs/toolkit'
const rootReducer = combineReducers({})
export type IRootState = ReturnType<typeof rootReducer>

From configureStore():

import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ ... })
export type IRootState = ReturnType<typeof store.getState>
Konstanze answered 14/2, 2020 at 9:50 Comment(9)
This is the proper answer. People, stop upvoting the answer above and upvote this one.Shanks
Yup. Worked for me. Thanks!Sidwohl
couldn't help laugh at statement above. great answer it helped as wellJacobba
what is IRootState?Isom
Use @peter-v-mørch answer https://mcmap.net/q/237357/-react-redux-useselector-typescript-type-for-stateKonstanze
@TuanDo, IRootState is the interface of your app's state, in my case it's the following: js import { combineReducers } from 'redux'; const rootReducer = combineReducers({ user: userReducer, foo: fooReducer, }) export type IRootState = ReturnType<typeof rootReducer>; Konstanze
I'm sorry but I don't think it's the correct answer, the best one would be to create createSelectorHook<YourStateType>.Nore
I will also mention that it requires @types/react-redux to be installed (as of today)Tetrachloride
LIFE SAVER! I love you @KonstanzeUzzia
H
60

You can create your custom typed useSelector like so:

import {
  useSelector as useReduxSelector,
  TypedUseSelectorHook,
} from 'react-redux'
import { RootState } from 'app/redux/store'

export const useSelector: TypedUseSelectorHook<RootState> = useReduxSelector

where RootState is the type of the store, usually defined as:

export type RootState = ReturnType<typeof rootReducer>

This is the method described in the definitely typed declaration.

Don't forget to install @types/react-redux.

Harney answered 13/8, 2019 at 6:41 Comment(12)
Type '{ navDrawer: Reducer<NavDrawerInitialState, AnyAction>; }' does not satisfy the constraint '(...args: any) => any'. Type '{ navDrawer: Reducer<NavDrawerInitialState, AnyAction>; }' provides no match for the signature '(...args: any): any'.Corby
where happen that error, what's the code involved? doesn't seems related to the TypedUseSelectorHookHarney
const state = useSelector( (getState: ReturnType<typeof reducer>) => getState.navDrawer );Corby
Also, export const useSelector: TypedUseSelectorHook<ReturnType<typeof reducer>> = useReduxSelector has the same errorCorby
where reducer come from in your example?Harney
I edited my post. It's listed above under reducers.tsCorby
Is this not even simpler ? const { cars } = useSelector((s: RootState) => s.carReducer)Tub
TypedUseSelectorHook is deprecated, you should use something like export const useSelector = createSelectorHook<VRampState>();Nore
@santamanno, thanks for let me know, I appended a note to redirect anyone to NearHuscarl's answerHarney
actually, TypedUseSelectorHook's deprecation was a rogue commit that has never been talked through with the react-redux team. It has since been undone. Please continue using TypedUseSelectorHook instead of createSelectorHook.Sememe
@Sememe thanks for correcting me. I've deleted my answer.Spaniel
thanks @NearHuscarl, but I don't think you should delete the answer with createSelectorHook (as it's still a valid solution)Harney
E
43

Here is the suggestion (more or less) from the redux docs:

import { RootState } from 'app/redux/store';
const isLoggedIn = useSelector(state: RootState => state.user.loggedIn);

The advantage over @Federkun's answer is that it is much simpler. The advantage over @alextrastero's answer is that I don't have to specify isLoggedIn's type manually.


FYI: To get the RootState, there are a couple of options (from the docs):

From combineReducers():

import { combineReducers } from '@reduxjs/toolkit'
const rootReducer = combineReducers({})
export type RootState = ReturnType<typeof rootReducer>

From configureStore():

import { configureStore } from '@reduxjs/toolkit'
const store = configureStore({ ... })
export type RootState = ReturnType<typeof store.getState>
Eastnortheast answered 27/3, 2020 at 11:55 Comment(3)
Simple and practical! Thanks!Testament
Work extremely well. ThanksAurar
I will also mention that it requires @types/react-redux to be installed (as of today)Tetrachloride
C
36

As per Redux docs, export the state as RootState in store.tsx file

// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>

Then in component use it as

const navDrawerOpen = useSelector((state:RootState) => state.navDrawer.open); 
Chicle answered 21/6, 2021 at 5:49 Comment(3)
This is the (complete) answer. Amazing how many of the answers here are not actually answers!Insectarium
This should definitely be the accepted answer. Cleanest and most correct answer I could find on the internet. Wish I could upvote more than once. Cheers!Depolarize
This is the only answer that has both steps and files that need to be augmented in order to make this solution work. thank you @Pavan Jadda for this great answer.Prosser
A
15
  1. Create config.d.ts

  2. Define your custom state

    import 'react-redux';
    import { ApplicationState } from '@store/index';
    declare module 'react-redux' {
      interface DefaultRootState extends ApplicationState {}
    } 
    
Agio answered 4/9, 2020 at 13:45 Comment(2)
That should be the actual selected answer.Roomful
This solution works like a charm but there is one small issue that the code completion of ApplicationState does not bring into DefaultRootState, how do you solve that?Ferromagnetic
P
7

In this video, Mark Erikson shows how to create some custom useAppSelector and useAppDispatch, to give them the correct types. Example in hooks.ts:

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import { RootState, AppDispatch } from './store';

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

And then in store.ts we have:

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import { composeWithDevTools } from 'redux-devtools-extension';

import productsReducer from '../slices/products.slice';

const store = createStore(
  productsReducer,
  composeWithDevTools(applyMiddleware(thunk))
);

export default store;

export type AppDispatch = typeof store.dispatch; // Here we export the store's dispatch type
export type RootState = ReturnType<typeof store.getState>; // Here we export the store's state

I think it's the best and simpler approach nowadays. Hope it's useful to somebody.

Pirnot answered 10/3, 2022 at 19:16 Comment(1)
Thanks it's in 38:06 here is a link: youtube.com/watch?v=9zySeP5vH9c#t=38m6sTopcoat
A
4

Thanks to @Иван Яковлев, I've finally achieved my perfect solution :D

import { useSelector } from 'react-redux'
import configureStore from '../configureStore'

export const { store } = configureStore()
// We can use RootState type in every file in project
declare global {
  type RootState = ReturnType<typeof store.getState>
}

// Thanks to that you will have ability to use useSelector hook with state value
declare module 'react-redux' {
  interface DefaultRootState extends RootState { }
}

const profile = useSelector(state => state.profile) // Profile is correctly typed
Ambagious answered 7/10, 2021 at 14:21 Comment(0)
S
3

Use typesafe-actions. It really makes life easy for you.

  1. npm install --save typesafe-actions
  2. Go to your reducer or rootReducer file and
  3. import { StateType } from 'typesafe-actions';
  4. Write your reducer function and
  5. export type Store = StateType<typeof yourReducer>;

Then

  1. Go to the ts file you want to use and
  2. import { useSelector } from 'react-redux';
  3. import {Store} from 'your-reducer-or-wherever-file'
  4. Inside your component:
  5. const store = useSelector((store: Store) => { return {students: store.manager.students} });

Notice how I used the Store type I exported from my reducer in the useSelector hook (the type of your store is what useSelector needs and you can get it easily with typesafe-actions as we just did). Also, notice how I returned an object that contains all the states I want to use. You can get creative at this point, it doesn't matter. So, the store variable in line 10 has all the states and you could as well just destructure if you want. You can read more on typesafe-actions from https://www.npmjs.com/package/typesafe-actions

Studious answered 7/12, 2020 at 15:59 Comment(1)
I've tried the other listed solutions, maybe some are outdated but this is the only that worked for me, thanksIodize
B
3

there is a type that 'react-redux' bundle provide

import { RootStateOrAny} from "react-redux";

const recentActivityResponse = useSelector(
    (state: RootStateOrAny) => state.dashboard.recentActivityResponse
);
Bubaline answered 11/1, 2022 at 14:38 Comment(0)
N
2
import React from 'react'
import { useSelector } from 'react-redux'

type RootState = {
    auth: {
        login: string
        isAuth: boolean
    }
}

const State: RootState = {
  auth: {
     login: ''
     isAuth: false
  }
}

export function useSelectorTyped<T>(fn: (state: RootState) => T): T {
  return useSelector(fn)
}

const LoginForm = () => {
    const { login, loginError } = useSelectorTyped(state => state.auth)
    return null
}
Names answered 30/4, 2021 at 13:44 Comment(0)
D
2

I think that the best way to understand this thoroughly is the Redux docs themselves.

https://react-redux.js.org/using-react-redux/usage-with-typescript

They state

Define Typed Hooks​ While it's possible to import the RootState and AppDispatch types into each component, it's better to create pre-typed versions of the useDispatch and useSelector hooks for usage in your application. This is important for a couple reasons: For useSelector, it saves you the need to type (state: RootState) every time For useDispatch, the default Dispatch type does not know about thunks or other middleware. In order to correctly dispatch thunks, you need to use the specific customized AppDispatch type from the store that includes the thunk middleware types, and use that with useDispatch. Adding a pre-typed useDispatch hook keeps you from forgetting to import AppDispatch where it's needed.

Therefore the below should do the trick and always be used to avoid having to constantly type selectors or dispatches.

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
Detrital answered 3/2, 2022 at 10:31 Comment(0)
H
2

Try this:

export interface IUser {
  userName: string;
}    

export interface IRootState {
  user: IUser;
} 

const user = useSelector<IRootState, IUser>(state => state.user);
Henleigh answered 28/7, 2022 at 7:20 Comment(0)
C
1

DefaultRootState was removed in react-redux v8, so augmenting it no longer works. It was removed specifically because the authors of react-redux consider it an anti-pattern. Useful discussion in the original issue.

The official solution now is to create typed versions of useSelector and useDispatch and use them throughout your app: https://redux.js.org/tutorials/typescript-quick-start#define-typed-hooks

The unofficial solution, if you prefer a global declaration, is to re-map the type paths. Much thanks to this comment for the solution.

tsconfig.json:

"paths": {
  "react-redux-default": [
    "./node_modules/react-redux"
  ],
  "react-redux": [
    "./src/my-react-redux.d.ts"
  ]
},

my-react-redux.d.ts:

import { 
  TypedUseSelectorHook, 
  useDispatch as useDefaultDispatch, 
  useSelector as useDefaultSelector 
} from 'react-redux-default';

import type { AppDispatch } from './MY_PATH';
import type { ApplicationState } from './MY_PATH';

export * from 'react-redux-default';

export const useDispatch: () => AppDispatch = useDefaultDispatch;
export const useSelector: TypedUseSelectorHook<ApplicationState> = useDefaultSelector;
Claypool answered 29/5 at 14:7 Comment(0)
O
0

I just found in code this snippet

/**
 * 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 {}
Oxendine answered 4/6, 2021 at 11:49 Comment(0)
I
0

You should make an interface at store.ts that contans all the types of all state values of all files, and then import it and use it as the type of state whenever you use useState()

at foo.ts

import { createSlice } from "@reduxjs/toolkit"
const foo = createSlice({
    initialState: {value: "some value"}
})
export default foo.reducer

at store.ts

    import {foo} from "./foo.ts"
    import {configureStore} from "@reduxjs/toolkit"
    interface RootState {
      foo: {
        value: string
      }
    }
    
    export default configureStore({
        reducer: {foo: foo}
    })

at distination file

import { RootState } from "../store"
import { useState } from "react-redux"
const bar = useState((state: RootState) => state.foo.value)
Incontinent answered 6/5, 2022 at 16:31 Comment(0)
H
-3

You could just use any for the type. Just another solution.

Hardhearted answered 13/8, 2021 at 13:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.