I want to use 'useSelector' to select proper state of rootStore, but can't get state properly. The reason is auth reducer of RootState gives me type never.
How can I access any values from the user object properly?
My store looks like this :
export const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);
export const persistor = persistStore(store);
export default { store, persistor};
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
export type RootStore = ReturnType<typeof rootReducer>
export type AppDispatch = typeof store.dispatch;
My auth reducer looks like this :
import {
LOGIN_SUCCESS,
LOGIN_FAIL,
LOGIN_REQUEST,
LoginDispatchTypes,
} from "../actions/types";
import { User } from "../types/index";
interface initialStateI {
token: string;
isAuthenticated: boolean;
isLoading: boolean;
user?: User;
error: string;
}
const initialState = {
token: "",
isAuthenticated: false,
isLoading: false,
error: "",
};
export default function (
state: initialStateI = initialState,
action: LoginDispatchTypes
) {
switch (action.type) {
case LOGIN_REQUEST:
return {
...state,
isLoading: true,
};
case LOGIN_SUCCESS:
return {
...state,
isAuthenticated: true,
isLoading: false,
user: action.payload.user,
token: action.payload.access_token,
error: null,
};
case LOGIN_FAIL:
return {
...state,
isAuthenticated: false,
token: null,
user: null,
error: action.payload.message,
};
default:
return state;
}
}
My action looks like this :
export const LOGIN_SUCCESS = "LOGIN_SUCCESS";
export const LOGIN_FAIL = "LOGIN_FAIL";
export const LOGIN_REQUEST = "LOGIN_REQUEST";
import { User } from "../types/index";
export interface LoginSuccess {
type: typeof LOGIN_SUCCESS;
payload: {
expires_in: number;
user: User;
access_token: string;
token_type: string;
};
}
export interface LoginFail {
type: typeof LOGIN_FAIL;
payload: {
message: string;
};
}
export interface LoginRequest {
type: typeof LOGIN_REQUEST;
}
export type LoginDispatchTypes = LoginRequest | LoginFail | LoginSuccess;
This is how i try to display the user details on my view :
const { user : currentUser} = useSelector((state:RootState) => state.auth);
Also the type user is in this format:
export interface User {
email: string;
author_id: number;
}
Any advice or recommendations/useful links on how to access data from state will be highly appreciated.
You should use configureStore from redux-toolkit instead of createStore if you do not want to add any additional typings
It seems this is what the redux team recommends https://redux.js.org/usage/usage-with-typescript#define-root-state-and-dispatch-types
Related
export const rootReducer = combineReducers({
login: loginReducer,
});
This works fine but as soon as I try to combine another reducer,
export const rootReducer = combineReducers({
login: loginReducer,
logout: logoutReducer
});
I start getting an error on rootReducer that
'rootReducer' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
How could I modify this?
This is how my logoutReducer looks like:
import {store} from '../index'
export const logoutReducer = (state = store.getState(), { type, payload } :any) => {
switch (type) {
case "logout":
return {...state, token: payload};
default:
return state;
}
};
Have you tried this with assigning type to your underlying reducers?
For example:
import {Action, Reducer} from 'redux';
interface LoginState {
isLoggedIn: boolean;
token: string;
}
interface LogoutState {
token:string;
}
export const logOutReducer: Reducer<LogoutState> = (state: LogoutState | undefined, incomingAction: Action): LogoutState=> {
switch (incomingAction.type) {
case "logout":
return {...state, token: payload};
default:
return state;
}
}
//... export const logInReducer: Reducer<LoginState>...
I'm receiving the following error:
(property) hero: Reducer
No overload matches this call.
Overload 1 of 3, '(reducers: ReducersMapObject): Reducer, AnyAction>', gave the following error.
Type '(state: string | undefined, action: Action) => string | Hero' is not assignable to type 'Reducer'.
Types of parameters 'state' and 'state' are incompatible.
Type 'Hero | undefined' is not assignable to type 'string | undefined'.
Type 'Hero' is not assignable to type 'string'.ts(2769)
index.ts(7, 3): The expected type comes from property 'hero' which is declared here on type 'ReducersMapObject'
It appears on the 'hero' written in index.ts below with the 3 asterix either side of it.
reducers/index.ts:
import { combineReducers } from "redux";
import { heroesReducer, heroReducer } from "./reducer";
import { Hero } from "../actions";
export interface StoreState {
heroes: Hero[];
hero: Hero;
}
export const reducers = combineReducers<StoreState>({
heroes: heroesReducer,
***hero***: heroReducer
});
reducers/reducers.ts
import { Hero, Action, ActionTypes } from "../actions";
export const heroesReducer = (state: Hero[] = [], action: Action) => {
switch (action.type) {
case ActionTypes.fetchAllHeroes:
return action.payload;
default:
return state;
}
};
export const heroReducer = (state: Hero, action: Action) => {
switch (action.type) {
case ActionTypes.fetchSingleHero:
return action.payload;
default:
return state;
}
};
actions/types.ts
import { FetchAllHeroesAction, FetchSingleHeroAction } from "./heroes";
export enum ActionTypes {
fetchAllHeroes,
fetchSingleHero,
searchHero
}
export type Action = FetchAllHeroesAction | FetchSingleHeroAction;
components/HeroItem:
import React from "react";
import { StoreState } from "../reducers";
import { connect } from "react-redux";
import { Hero, fetchSingleHero } from "../actions";
interface HeroProps {
id: string;
hero: Hero;
}
export const _HeroItem: React.FC<HeroProps> = props => {
console.log(props);
return <div>{props.hero}hihihihi</div>;
};
const mapStateToProps = (state: StoreState): { hero: Hero } => {
return { hero: state.hero };
};
export const HeroItem = connect(mapStateToProps, { fetchSingleHero })(
_HeroItem
);
actions/heroes.ts:
import axios from "axios";
import { Dispatch } from "redux";
import { ActionTypes } from "./types";
export interface Hero {
id: string;
name: string;
powerstats: {
strength: string;
speed: string;
intelligence: string;
durability: string;
power: string;
combat: string;
};
biography: {
fullName: string;
alterEgos: string;
aliases: string[];
placeOfBirth: string;
publisher: string;
alignment: string;
};
appearance: {
gender: string;
race: string;
height: string[];
weight: string[];
};
work: {
occupation: string;
base: string;
};
connections: {
groupAffiliation: string;
relatives: string;
};
image: {
url: string;
};
}
export interface FetchSingleHeroAction {
type: ActionTypes.fetchSingleHero;
payload: Hero;
}
export interface HeroId {
id: string;
}
export const fetchSingleHero = (heroId: HeroId) => {
const url = `https://superheroapi.com/api/2987607971258652/${heroId}`;
return async (dispatch: Dispatch) => {
const res = await axios.get(corsProxy + url);
console.log(res.data);
dispatch<FetchSingleHeroAction>({
type: ActionTypes.fetchSingleHero,
payload: res.data
});
};
};
I got it working for a 'FetchAllHeroes' action last night but did it by deleting part of a prop checking typescript component which I think isn't the best way. If anyone has any ideas on how to fix it or wants any other information to help it would be greatly appreciated. I'm very new to Typescript.
Thanks!
I am using TypeScript for my React-Redux app. I have followed the TypeScript React Starter, however, the example mentioned there does not have any action methods that has some arguments/payload.
I have a simple requirement -
Show a list of names and when a button is clicked a new name is added. When another button is clicked 2 names are added.
The entry point for the app index.tsx looks like this -
import * as React from 'react';
import * as ReactDOM from 'react-dom';
import registerServiceWorker from './registerServiceWorker';
import './index.css';
import { createStore } from 'redux';
import { StoreState } from './types';
import { listUsers } from './reducers';
import { Provider } from 'react-redux';
import ListUsers from './containers/ListUsers';
const store = createStore<StoreState>(listUsers, {
'users': [
{
'id': 0,
'firstName': 'Aniyah',
'lastName': 'Luettgen',
'phone': '861-332-5113',
'email': 'Danika.Ryan84#yahoo.com',
'role': 'admin'
},
{
'id': 1,
'firstName': 'Alisa',
'lastName': 'Pacocha',
'phone': '085-056-3901',
'email': 'Eusebio68#yahoo.com',
'role': 'admin'
}
]
});
ReactDOM.render(
<Provider store={store}>
<ListUsers/>
</Provider>,
document.getElementById('root') as HTMLElement
);
registerServiceWorker();
The constants file (constants/index.tsx) looks like this -
export const LIST_USERS = 'LIST_USERS';
export type LIST_USERS = typeof LIST_USERS;
export const ADD_USER = 'ADD_USER';
export type ADD_USER = typeof ADD_USER;
export const ADD_USERS = 'ADD_USERS';
export type ADD_USERS = typeof ADD_USERS;
The types (types/index.tsx) are -
export interface User {
id: number;
firstName: string;
lastName: string;
phone: string;
email: string;
role: string;
}
export interface StoreState {
users: Array<User>;
}
export interface Action<T> {
type: string;
payload?: T;
}
My Component (components/ListUsers.tsx) looks like this -
import * as React from 'react';
import { DispatchProps, ListUsersProps } from '../containers/ListUsers';
import { User } from '../types';
interface ListState {
counter: number;
}
class ListUsers extends React.Component<ListUsersProps & DispatchProps, ListState> {
constructor(props: ListUsersProps & DispatchProps) {
super(props);
this.addUser = this.addUser.bind(this);
this.addMultipleUser = this.addMultipleUser.bind(this);
this.state = {
counter: 0
};
}
render() {
return (
<div>
<button onClick={() => this.addUser()}>Add User</button>
<button onClick={() => this.addMultipleUser()}>Add User</button>
{this.props.users.map((user: User) => (
<div key={user.id}>{user.firstName}</div>
))}
</div>
);
}
private addUser() {
this.props.addUser({
id: this.state.counter,
firstName: 'abcd',
lastName: 'efgh',
email: 'abcd#gmail.com',
phone: '1234567890',
role: 'admin'
});
this.setState({
...this.state,
counter: this.state.counter + 1
});
}
private addMultipleUser() {
this.props.addUsers([{
id: this.state.counter,
firstName: 'abcd',
lastName: 'efgh',
email: 'abcd#gmail.com',
phone: '1234567890',
role: 'admin'
}, {
id: this.state.counter,
firstName: 'ijkl',
lastName: 'mnop',
email: 'ijkl#gmail.com',
phone: '1234567890',
role: 'admin'
}]);
this.setState({
...this.state,
counter: this.state.counter + 2
});
}
}
export default ListUsers;
My container component (containers/ListUsers.tsx) looks like this -
import { connect, Dispatch } from 'react-redux';
import { Action, StoreState, User } from '../types';
import ListUsers from '../components/ListUsers';
import { addUser, addUsers } from '../actions';
export interface ListUsersProps {
users: Array<User>;
}
export interface DispatchProps {
addUser(user: User): Action<User>;
addUsers(users: Array<User>): Action<Array<User>>;
}
export const mapStateToProps = (state: StoreState): ListUsersProps => {
return {
users: state.users
};
};
export const mapDispatchToProps = (dispatch: Dispatch<DispatchProps>) => {
return {
addUser: (user: User) => dispatch(addUser(user)),
addUsers: (users: Array<User>) => dispatch(addUsers(users))
};
};
export default connect<ListUsersProps, DispatchProps, {}>(mapStateToProps, mapDispatchToProps)(ListUsers);
Now here is the reducer where I am facing issues (reducers/index.tsx) -
import { Action, StoreState, User } from '../types';
import { ADD_USER, ADD_USERS, LIST_USERS } from '../constants';
export const listUsers = (state: StoreState, action: Action<User>): StoreState => {
switch (action.type) {
case LIST_USERS:
return state;
case ADD_USER:
return {
...state,
users: action.payload === undefined ? [...state.users] : [...state.users, action.payload]
};
case ADD_USERS:
return {
...state,
users: action.payload === undefined ? [...state.users] : [...state.users, ...action.payload]
};
default:
return state;
}
};
Here the compiler says that in the reducers/index.tsx in
case ADD_USERS:
return {
...state,
users: action.payload === undefined ? [...state.users] : [...state.users, ...action.payload]
};
that action.payload is User and not Array<User> which is right (I have declared the reducer with type (state: StoreState, action: Action<User>)).
How do I work with this? If there isn't an alternative, I will have to write separate reducers for every ACTION that has a different payload which I think is bad.
The way I am using the DispatchProps type also looks very odd to me. I am trying to follow this for the types of mapStateToProps and mapDispatchToProps.
Is there any work around for this?
PS: I am using react, redux and react-redux libraries.
I found a solution for this. I am not sure if it is right way to do this.
Instead of making the Action Type generic like so -
interface Action<T> {
type: string;
payload?: T;
}
I created a Separate Action Type for each Action and then the Action Type accepted by the Reducer would be the | of them.
So, the types look like this (types/index.tsx) -
import { ADD_USER, ADD_USERS, LIST_USERS } from '../constants';
export interface User {
id: number;
firstName: string;
lastName: string;
phone: string;
email: string;
role: string;
}
export interface StoreState {
users: Array<User>;
}
export type ListUsersAction = {type: typeof LIST_USERS};
export type AddUserAction = {type: typeof ADD_USER, payload: User};
export type AddUsersAction = {type: typeof ADD_USERS, payload: Array<User>};
export type Actions = ListUsersAction | AddUserAction | AddUsersAction;
and the Container Component (containers/ListUsers.tsx) looks like this -
import { connect, Dispatch } from 'react-redux';
import { AddUserAction, AddUsersAction, StoreState, User } from '../types';
import ListUsers from '../components/ListUsers';
import { addUser, addUsers } from '../actions';
export interface ListUsersProps {
users: Array<User>;
}
export interface DispatchProps {
addUser(user: User): AddUserAction;
addUsers(users: Array<User>): AddUsersAction;
}
export const mapStateToProps = (state: StoreState): ListUsersProps => {
return {
users: state.users
};
};
export const mapDispatchToProps = (dispatch: Dispatch<DispatchProps>) => {
return {
addUser: (user: User) => dispatch(addUser(user)),
addUsers: (users: Array<User>) => dispatch(addUsers(users))
};
};
export default connect<ListUsersProps, DispatchProps>(mapStateToProps, mapDispatchToProps)(ListUsers);
There are no compilation errors any more, and payload for both ADD_USER and ADD_USERS is received properly.
There is a recommendation to reduce the Action Type boilerplate here.
I can not understand how to deal with new typings of Redux.
Right now i am trying to complete this tutorial https://maxfarseer.gitbooks.io/redux-course-ru/content/index.html
At this moment I am on "Creating Store" phase with this simple code below:
store/configureStore.ts
import {createStore, Reducer} from 'redux';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState);
if (module.hot) {
module.hot.accept('../reducers', () => {
const nextRootReducer=require('../reducers');
store.replaceReducer(nextRootReducer);
})
}
return store;
}
When I try to compile it messages me about this error:
ERROR in ./src/store/configureStore.ts
(10,34): error TS2345: Argument of type '{}' is not assignable to parameter of type '<A extends Action>(state: any, action: A) => any'.
Type '{}' provides no match for the signature '<A extends Action>(state: any, action: A): any'
This is my index file of reducers
import {combineReducers} from "redux/index";
import page from './page';
import user from './user';
const rootReducer = combineReducers({
page,
user
});
export default rootReducer;
//page.ts
const initialState = {
year: 2016,
photos: []
};
function page(state = initialState,
action = {type: '', payload: null}) {
return state
}
export default page;
//user.ts
const initialState = {
name: 'Аноним'
};
export default function user(state = initialState,
action = {type: '', payload: null}) {
return state
}
Is it possible to combine reducers that are nested with the following structure:
import 'user' from ...
import 'organisation' from ...
import 'auth' from ...
// ...
export default combineReducers({
auth: {
combineReducers({
user,
organisation,
}),
auth,
},
posts,
pages,
widgets,
// .. more state here
});
Where the state has the structure:
{
auth: {
user: {
firstName: 'Foo',
lastName: 'bar',
}
organisation: {
name: 'Foo Bar Co.'
phone: '1800-123-123',
},
token: 123123123,
cypher: '256',
someKey: 123,
}
}
Where the auth reducer has the structure:
{
token: 123123123,
cypher: '256',
someKey: 123,
}
so maybe the spread operator is handy? ...auth not sure :-(
It is perfectly fine to combine your nested reducers using combineReducers. But there is another pattern which is really handy: nested reducers.
const initialState = {
user: null,
organisation: null,
token: null,
cypher: null,
someKey: null,
}
function authReducer(state = initialState, action) {
switch (action.type) {
case SET_ORGANISATION:
return {...state, organisation: organisationReducer(state.organisation, action)}
case SET_USER:
return {...state, user: userReducer(state.user, action)}
case SET_TOKEN:
return {...state, token: action.token}
default:
return state
}
}
In the above example, the authReducer can forward the action to organisationReducer and userReducer to update some part of its state.
Just wanted to elaborate a bit on the very good answer #Florent gave and point out that you can also structure your app a bit differently to achieve nested reducers, by having your root reducer be combined from reducers that are also combined reducers
For example
// src/reducers/index.js
import { combineReducers } from "redux";
import auth from "./auth";
import posts from "./posts";
import pages from "./pages";
import widgets from "./widgets";
export default combineReducers({
auth,
posts,
pages,
widgets
});
// src/reducers/auth/index.js
// note src/reducers/auth is instead a directory
import { combineReducers } from "redux";
import organization from "./organization";
import user from "./user";
import security from "./security";
export default combineReducers({
user,
organization,
security
});
this assumes a bit different of a state structure. Instead, like so:
{
auth: {
user: {
firstName: 'Foo',
lastName: 'bar',
}
organisation: {
name: 'Foo Bar Co.'
phone: '1800-123-123',
},
security: {
token: 123123123,
cypher: '256',
someKey: 123
}
},
...
}
#Florent's approach would likely be better if you're unable to change the state structure, however
Inspired by #florent's answer, I found that you could also try this. Not necessarily better than his answer, but i think it's a bit more elegant.
function userReducer(state={}, action) {
switch (action.type) {
case SET_USERNAME:
state.name = action.name;
return state;
default:
return state;
}
}
function authReducer(state = {
token: null,
cypher: null,
someKey: null,
}, action) {
switch (action.type) {
case SET_TOKEN:
return {...state, token: action.token}
default:
// note: since state doesn't have "user",
// so it will return undefined when you access it.
// this will allow you to use default value from actually reducer.
return {...state, user: userReducer(state.user, action)}
}
}
Example (see attachNestedReducers bellow)
import { attachNestedReducers } from './utils'
import { profileReducer } from './profile.reducer'
const initialState = { some: 'state' }
const userReducerFn = (state = initialState, action) => {
switch (action.type) {
default:
return state
}
}
export const userReducer = attachNestedReducers(userReducerFn, {
profile: profileReducer,
})
State object
{
some: 'state',
profile: { /* ... */ }
}
Here is the function
export function attachNestedReducers(original, reducers) {
const nestedReducerKeys = Object.keys(reducers)
return function combination(state, action) {
const nextState = original(state, action)
let hasChanged = false
const nestedState = {}
for (let i = 0; i < nestedReducerKeys.length; i++) {
const key = nestedReducerKeys[i]
const reducer = reducers[key]
const previousStateForKey = nextState[key]
const nextStateForKey = reducer(previousStateForKey, action)
nestedState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? Object.assign({}, nextState, nestedState) : nextState
}
}
Nested Reducers Example:
import {combineReducers} from 'redux';
export default combineReducers({
[PATH_USER_STATE]: UserReducer,
[PATH_CART_STATE]: combineReducers({
[TOGGLE_CART_DROPDOWN_STATE]: CartDropdownVisibilityReducer,
[CART_ITEMS_STATE]: CartItemsUpdateReducer
})
});
Output:
{
cart: {toggleCartDropdown: {…}, cartItems: {…}}
user: {currentUser: null}
}