'React-Redux useSelector typescript type for state

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



Solution 1:[1]

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

Solution 2:[2]

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.

Solution 3:[3]

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.

Solution 4:[4]

  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 {}
    } 
    

Solution 5:[5]

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

Solution 6:[6]

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

Solution 7:[7]

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

Solution 8:[8]

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
}

Solution 9:[9]

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.

Solution 10:[10]

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

import { RootStateOrAny} from "react-redux";

const recentActivityResponse = useSelector(
    (state: RootStateOrAny) => state.dashboard.recentActivityResponse
);

Solution 11:[11]

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 {}

Solution 12:[12]

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;

Solution 13:[13]

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)

Solution 14:[14]

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