CombineReducers in TypeScript - javascript

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

Related

React typescript redux reducer type 'never'

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

Reducer is not a function. React Native Redux

I am trying to add a reducer to my react native app.
Here is the store constant:
export const USER_PROFILE = 'USER_PROFILE';
Here is the action.js
import {USER_PROFILE} from '../constants/index';
export function userProfile(userReducer) {
return {
type: USER_PROFILE,
payload: {
email: '',
},
};
}
This the userReducer that is causing the error. I keep getting an error of customerReducer is not a function.
import {USER_PROFILE} from '../constants/index';
const initialState = {
userProfile: '',
};
const customerReducer = (state = initialState, action) => {
switch (action.type) {
case USER_PROFILE:
return {
...state,
userProfile: action.payload,
};
default:
return state;
}
};
export default customerReducer;
And I have declared the email as a state... const [email, setEmail] useState('')
calling the reducer here. const customerReducer = useSelector(state => state.userProfile);
Now dispatching it with the useDispatch method.
const dispatch = useDispatch();
...
dispatch(customerReducer(email));
Your action.js should look somthing like this:
import {USER_PROFILE} from '../constants/index';
export function userProfile(userReducer) {
return {
type: USER_PROFILE,
payload: {
email: userReducer.email,
},
};
and when you dispatch you should use the action "userProfile" not the reducer "customerReducer". Your dispatch should look somthing like this:
dispatch(userProfile({email}));
make sure to use curly brackets. beacause you should pass an object not a string.

mapStateToProps returning undefined state from reducer

I am getting this error:
I have this reducer for async:
const initUserState = {
fetching: false,
fetched: false,
users: [],
error: null
};
const userReducer = (state = initUserState, action) => {
switch(action.type){
case "FETCH_USER":
state = {
...state,
users : action.payload
};
break;
case "FETCH_USER_START":
state = {
...state,
fetching: true
};
break;
case "FETCH_USER_SUCCESS":
state = {
...state,
fetched: true,
users: action.payload
};
break;
case "FETCH_USER_ERROR":
state = {
...state,
fetched: false,
error: action.payload
};
break;
default:
break;
}
return state;
};
export default userReducer;
And my container is:
import React from 'react';
import { Blog } from '../components/Blog';
import { connect } from 'react-redux';
//import { act_fetchAllUser } from '../actions/userActions';
import axios from 'axios';
class App extends React.Component {
componentWillMount(){
this.props.fetchAllUser();
}
render(){
console.log("Prop: " , this.props.user);
return(
<div>
<h1>My Blog Posts</h1>
<Blog/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
user: state.userReducer
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchAllUser: () => {
dispatch((dispatch) => {
dispatch({ type: "FETCH_USER_START" })
axios.get('http://infosys.esy.es/test/get_allBlog.php')
.then((response) => {
dispatch({
type: "FETCH_USER_SUCCESS",
payload: response.data
});
})
.catch((err) => {
dispatch({
type: "FETCH_USER_ERROR",
payload: err
});
})
});
}
};
};
export default connect(mapStateToProps, mapDispatchToProps)(App);
Im new to redux and I am into async now...
my application should load the users on page load but why my user state is returning undefined state? in the render method, i am logging the user props that contains the returned state from userReducer(mapStateToProps) but i am getting undefined. But when I getState() in store and log it, I am getting the expected result but it is not the ideal place to get the state right?... What i am doing wrong?
My store is:
import { createStore, applyMiddleware } from 'redux';
import logger from 'redux-logger';
import userReducer from './reducers/userReducer';
import thunk from 'redux-thunk';
const store = createStore(userReducer,applyMiddleware(thunk, logger()));
//store.subscribe(() => console.log('State: ',store.getState()));
export default store;
And my index is:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './container/App';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider> , document.getElementById('root'));
Also, on my default case in userReducer, what should I code there.. should I just break; or do I need also to return state it? Thank you
You should have;
const mapStateToProps = (state) => {
return {
user: state.users
};
};
mapStateToProps passes state (global store) as a props to the component.
Global store
const initUserState = {
fetching: false,
fetched: false,
users: [],
error: null
};
inside component listening to user state
const mapStateToProps = (state) => {
return {
user: state.users
};
};

Reducer typings

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
}

Nested redux reducers

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

Categories

Resources