'Using connect() with React/Redux/TypeScript cannot find property
I want to access the properties and methods on distinct components using the connect() functionality, but I'm having trouble defining this the right way. What I currently have in my redux-folder:
/redux/app/hooks.ts
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from './store';
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
/redux/app/store.ts
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import rootReducer from "..";
export const store = configureStore({
reducer: rootReducer,
});
export type AppDispatch = typeof store.dispatch;
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType, RootState, unknown, Action<string>>;
/redux/index.ts
import { combineReducers } from "redux";
import userReducer from "./modules/user";
export const rootReducer = combineReducers({
user: userReducer,
});
export default rootReducer;
/redux/modules/user.ts
import { createSlice } from "@reduxjs/toolkit";
import { PayloadAction } from "@reduxjs/toolkit/dist/createAction";
import { User } from "../../models/user";
interface UserState {
user: User | null;
}
const initUserState = { user: null } as UserState;
export const userSlice = createSlice({
name: "user",
initialState: initUserState,
reducers: {
setUser: (state, action: PayloadAction<User>) => {
state.user = action.payload;
},
},
});
export const { setUser } = userSlice.actions;
export default userSlice.reducer;
This looks pretty okay to me.
So I have the following Wrapper component which acts as the layout in which the child components reside.
Wrapper.ts
import axios from "axios";
import React, { useEffect, useState } from "react";
import { Navigate } from "react-router-dom";
import { User } from "../models/user";
import Menu from "./Menu";
import Nav from "./Nav";
import { connect } from "react-redux";
import { setUser } from "../redux/modules/user";
import { RootState } from "../redux/app/store";
export interface WrapperProps {}
const Wrapper: React.FC<WrapperProps> = (props) => {
const [redirect, setRedirect] = useState<boolean>(false);
useEffect(() => {
(async () => {
try {
const res = await axios.get("user");
if (res.status === 200) {
props.setUser(res.data as User);
}
} catch (error) {
setRedirect(true);
}
})();
}, [props]);
if (redirect) {
return <Navigate to="/login" />;
}
return (
<>
<Nav />
<div className="container-fluid">
<div className="row">
<Menu />
<main className="col-md-9 ms-sm-auto col-lg-10 px-md-4">
{props.children}
</main>
</div>
</div>
</>
);
};
const mapState = (state: RootState) => ({ user: state.user });
const mapDispatch = { setUser };
const connector = connect(mapState, mapDispatch);
export default connector(Wrapper);
In the Wrapper, I'm getting an error when I try to set the user in useEffect with props.setUser, stating: Property 'setUser' does not exist on type 'PropsWithChildren<WrapperProps>'.
I also want to access this user in the Nav-component which is a child of Wrapper. This looks like the following:
Nav.ts
import axios from "axios";
import React from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { RootState } from "../redux/app/store";
export interface NavProps {}
const Nav: React.FC<NavProps> = (props) => {
const logoutHandler = async () => await axios.post("logout", {});
return (
<nav className="navbar navbar-dark sticky-top bg-dark flex-md-nowrap p-0 shadow">
<span className="company">Company Name</span>
<ul className="my02 my-md-0 mr-md-3">
<Link to="/profile" className="p-2 text-white text-decoration-none">
{props.user.name}
</Link>
<Link to="/login" onClick={logoutHandler}
className="p-2 text-white text-decoration-none">
Sign out
</Link>
</ul>
</nav>
);
};
const mapStateToProps = (state: RootState) => ({ user: state.user });
export default connect(mapStateToProps)(Nav);
In Nav I get an error when I try to access props.user.name, stating: Property 'user' does not exist on type 'PropsWithChildren<NavProps>'.
Any ideas on how to do this with Redux Toolkit and TypeScript? If you have suggestions on refactoring/optimizing the code above, let me know as well. Help is appreciated.
Solution 1:[1]
For the first problem, you're using an empty interface, which isn't sufficient. Try the following, straight from https://redux.js.org/usage/usage-with-typescript
import { connect, ConnectedProps } from 'react-redux'
const connector = connect(mapState, mapDispatch)
type PropsFromRedux = ConnectedProps<typeof connector>
// Use this type for the props of your component!!
type Props = PropsFromRedux & {
// ... non-redux-related props here
}
The second problem is probably solved with the same pattern. TS support is much better if you're using the useDispatch and useSelector hooks. The RTK docs explain how to set these up in a way you never have to type them again, anywhere in your app. You would simply use an useAppDispatch that knows which actions exist in your app, and a version of useSelector that knows the exact shape of your store.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | timotgl |
