I've looked at a bunch of examples of working with nested reducers, but I'm having a very strange problem.
I have the following initial state:
window.store = configureStore({
slider: {
mainImageIndex: 0,
pageNum: 1,
perPage: 4, // Per supplied requirements
},
});
The following in my index.js reducer (excluding all the imports):
export default combineReducers({
searchPhotos,
slider: combineReducers({
mainImageIndex: setMainImage,
pageNum: nextPage,
perPage: setPerPage,
}),
form: reduxFormReducer, // mounted under "form"
});
And my setMainInage.js reducer:
export default (state = {}, action) => {
switch (action.type) {
case 'SET_MAIN_IMAGE':
return {
...state,
mainImageIndex: action.mainImageIndex,
};
default:
return state;
}
};
Attached are before and after screen grabs of the redux devtools. Notice that after calling the SET_MAIN_IMAGE, the hierarcy inside the slider node changes. For some reason instead of just updating the mainImageIndex, it's nesting a new mainImageIndex key under the original. Anyone know what could be causing that?
The thing you have to check is what does your action contain ?
It seems that it contains something like that
{
type: 'SET_MAIN_IMAGE',
mainImageIndex: {
mainImageIndex: 2
},
}
Your reducer will be like
1. First step
case 'SET_MAIN_IMAGE':
return {
...state,
mainImageIndex: action.mainImageIndex,
};
2. Second step
case 'SET_MAIN_IMAGE':
return {
mainImageIndex: 0,
pageNum: 1,
perPage: 4,
mainImageIndex: action.mainImageIndex,
};
3. Third step
case 'SET_MAIN_IMAGE':
return {
mainImageIndex: 0,
pageNum: 1,
perPage: 4,
mainImageIndex: {
mainImageIndex: 2
},
};
4. Fourth step
case 'SET_MAIN_IMAGE':
return {
pageNum: 1,
perPage: 4,
mainImageIndex: {
mainImageIndex: 2
},
};
What you should do
Change the action payload in order to send something like that
{
type: 'SET_MAIN_IMAGE',
mainImageIndex: 2
}
or better
{
type: 'SET_MAIN_IMAGE',
payload: {
mainImageIndex: 2,
}
}
then use in your reducer
case 'SET_MAIN_IMAGE':
return {
...state,
...action.payload,
};
Hope it helps.
So I was able to solve the problem by creating a new reducer for the slider, and put all actions inside that one reducer, instead of having one function per file. Then I could just do:
export default combineReducers({
searchPhotos,
slider,
form: reduxFormReducer, // mounted under "form"
});
I think there must still be a way to do it with multiple files, but maybe it does make sense to put all the actions for one part of state in the same reducer.
Related
I'm building an app where a "slice reducer" needs to access state of another "slice reducer". The redux docs talks about using a custom combine reducer in order to pass in the root state to the reducer - Beyond combineReducers
Thus far, I have this for my root reducer:
import cats from '../slices/cats'
import dogs from '../slices/dogs'
import status from '../slices/status'
function combinedReducer(state = {}, action) {
return {
status: status(state.status, action),
dogs: dogs(state.dogs, action),
cats: cats(state.cats, action, state),
};
}
export default configureStore({ reducer: combinedReducer });
I don't seem to be able to get the root state for my cats reducer - passed in as the 3rd arg above.
const assetsSlice = createSlice({
name: 'cats',
initialState,
reducers: {
setFetched: (state, { payload }, root) => {
// root is undefined
state.type = payload + root.dogs.legs;
},
},
});
This should work, no?
If I use a vanilla reducer that's not created by createSlice I am able to get the root state
export default (state = initialState, action, root) => {
// root - { status: {}, dogs: {}, cats: {} }
};
This is not possible as a third argument since RTK's reducers only pass the first two arguments to the case reducers.
You could just add it to the action though (but granted, that's hacky):
function combinedReducer(state = {}, action) {
const actionWithFullState = { ...action, meta: {...action.meta, fullState: state }}
return {
status: status(state.status, action),
dogs: dogs(state.dogs, action),
cats: cats(state.cats, actionWithFullState),
};
}
I've created react app with react-reducer.
I've declared types for actions and in the main Reduce I face an issue:
Can't read property type of undefined
import {ADD_USERS, DELETE_USER, GET_USERS} from '../types'
const initialState = {
users: [
{
id: 1,
name: 'Oksana'
},
{
id: 2,
name: 'Serge'
},
],
loading: true
}
export default function(state = initialState, action){
switch(action.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
case ADD_USERS:
const newId = state.users[state.users.length-1] + 1
return {
...state,
users: {
[newId] : {
id: newId,
name: action.name
}
},
loading: false
}
case DELETE_USER :
return {
...state,
users: state.users.filter(i => i.id !== action.id)
}
default: return state
}
}
so here I implement simple get/delete/add methods.
export function getUsers (name) {
return {
type: GET_USERS,
payload: name
}
}
....
and this is actions file
I export all the actions, idk where could I make a mistake
the rest of actions I've not mentioned.
Your switch statement uses action.type, however action is undefined in at least one of the actions you pass to the reducer. Either add a default value to action or add a guard before the switch-statement like so:
export default function(state = initialState, action){
if (!action) {
return state
}
switch(action.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
...
As you have a default condition already, adding a default value to action, e.g. action = {} might be the cleaner solution.
If you use Typescript you could also just do the following:
export default function(state = initialState, action){
switch(action?.type){
case GET_USERS:
return {
...state,
users: action.payload,
loading: false
}
...
I defined a reducer but it always returns NaN value instead of number.I defined a single store in store.js like createStore(reducer,{},applyMiddleWire(). Reducer is working but always return NaN value.
import { INCREMENT, DECREMENT } from "./buttonAction";
const initialState = {
counter: 1
};
const Count = (state = initialState, action) => {
switch (action.type) {
case "INCREMENT":
state = {
...state,
counter: state.counter + 1
};
console.log("Number", state);
break;
case "DECREMENT":
state = {
...state,
counter: state.counter + 1
};
console.log("Number", state);
break;
default:
}
return state;
};
export default Count;
You need to return the new state in SWITCH cases.
The point is that Redux state is not like React state.
When you changing React state, setState() change only specific part of state, while other state content remains untouched.
On the other hand, Redux wants you to always return a new state, a new object. Redux does not merge old state and new state like React do. So the answer is you need to create new object in every switch case and return it, and Redux will replace old state with new one.
switch (action.type) {
case "INCREMENT":
return {
...state,
counter: state.counter + 1
};
console.log("Number", state);
break;
I was looking for the same mistake, I think maybe your problem as others said is because payload is not defined into the Component(method-properties).
I solved and learn a little bit else with this mistake about lifecycle of redux.
https://codesandbox.io/s/react-redux-counter-03-xjqnq
increment = () => {
// this.setState({ count: this.state.count + 1 });
this.props.dispatch({ type: "INCREMENT", payload: 5 });
};
decrement = () => {
// this.setState({ count: this.state.count - 1 });
this.props.dispatch({ type: "DECREMENT", payload: 3 });
}; enter code here
My question relates to redux and more specifically how to handle errors/failures from within reducer functions. I am in reference to the ngrx example app (https://github.com/ngrx/example-app) and the way it handle errors/failures.
Here is the reducer function I am referring to:
export function reducer(state = initialState, action: collection.Actions): State {
switch (action.type) {
case collection.ActionTypes.LOAD: {
return Object.assign({}, state, {
loading: true
});
}
case collection.ActionTypes.LOAD_SUCCESS: {
const books = action.payload;
return {
loaded: true,
loading: false,
ids: books.map(book => book.id)
};
}
case collection.ActionTypes.ADD_BOOK_SUCCESS:
case collection.ActionTypes.REMOVE_BOOK_FAIL: {
const book = action.payload;
if (state.ids.indexOf(book.id) > -1) {
return state;
}
return Object.assign({}, state, {
ids: [ ...state.ids, book.id ]
});
}
case collection.ActionTypes.REMOVE_BOOK_SUCCESS:
case collection.ActionTypes.ADD_BOOK_FAIL: {
const book = action.payload;
return Object.assign({}, state, {
ids: state.ids.filter(id => id !== book.id)
});
}
default: {
return state;
}
}
}
Can someone please explain the necessity for dealing with those two actions from within the reducer function:
REMOVE_BOOK_FAIL
ADD_BOOK_FAIL
For instance why remove the book from the state (in the case of the ADD_BOOK_FAIL action)?
If the add book action has failed, then the book is not present in the store. Is it?
Maybe it's the naming used that makes it a red herring, my guess is that ADD_BOOK_FAIL could be in use somewhere else for a different use case as a fall back mechanism.
I agree the way you describe it this doesnt make sense the developer did it for this reason.
I'm a beginner in react / redux.
I've finished a basic component <HeatMap /> in my app, with its actions / reducer / store and it works well.
And I'll render another <HeatMap /> with different settings (props).
What I'm trying to do is to separate this 2 component, because when i dispatch an update action in one, the other one performed it simultaneously.
Question 1
I tried this to separate the states in store
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
heatMap1: heatMap,
heatMap2: heatMap
});
combineReducers and connectthe 2 heatmap in different object in store
export default connect((state)=> {
return {
onState: state.heatMap1.onState,
config: state.heatMap1.config
}
})(CHMSHeatMap1)
and
export default connect((state)=> {
return {
onState: state.heatMap2.onState,
config: state.heatMap2.config
}
})(CHMSHeatMap2)
is this correct?
Question 2
Because 2 component both react when action is dispatched
I'm thinking about separating the shared actions, but I don't think it's a good idea. Or maybe the issue is not here.
So can you tell me what cause this problem and how to solve it?
Here are my reducer
import * as actionTypes from '../actions/heat-map';
import Immutable from 'immutable';
const onState = {
fetching: 'FETCHING',
error: 'ERROR',
drawn: 'DRAWN'
};
const initialState = {
onState: onState.fetching,
config: {}
};
export default function heatMapReducer(state = initialState, action) {
let immutableState = Immutable.fromJS(state);
switch (action.type) {
case actionTypes.INITIALIZING:
return immutableState.set('onState', onState.drawn).set('config', action.payload.initConfig).toJS();
case actionTypes.FETCH_DATA_REQUEST:
return immutableState.set('onState', onState.fetching).toJS();
case actionTypes.FETCH_DATA_SUCCESS:
return immutableState.set('onState', onState.drawn).setIn(['config','series',0,'data'],Immutable.fromJS(action.payload.mapData.data)).toJS();
case actionTypes.FETCH_DATA_FAILURE:
return immutableState.set('onState', onState.error).set('config', action.payload.mapData).toJS();
default:
return state;
}
}
Action is simple
export function initializeConfig(initConfig) {
return {
type: INITIALIZING,
payload: {
text: 'Initializing',
initConfig
}
}
}
export function requireMapData() {
return {
type: FETCH_DATA_REQUEST,
payload: {
text: 'Loading'
}
};
}
..........
//Async Action for fetching map data and redraw the map
export function fetchMapData(address) {
return function (dispatch) {
//dispatch requireMapData action to set the map in loading state
dispatch(requireMapData());
return fetch(address)
.then(fetchUtil.checkHttpStatus) //check if 404
.then(fetchUtil.parseJSON)
.then(mapData => dispatch(fetchDataSucceed(mapData)))
.catch(error => dispatch(fetchDataFailed(error)));
}
}
Thank you my friend.
You cannot duplicate your reducers in the manner you've depicted. Both are going to respond in the exact same way to the exact same actions.
The solution is to have all of your heat map data in the same reducer state. e.g.
const initialState = {
heatMap1: {},
heatMap2: {}
};
export default heatmap(state = initialState, action) {
// etc
Now if you want to use the same actions for both heat maps, you'll need to have an action property specifying which heap map you're targeting. If you have several heat maps, I'd recommend an array of heat maps with each action containing an index or id to target a particular heat map. e.g.
function updateHeatMap(index, value) {
return {
type: UPDATE_HEATMAP,
index: index,
value: value
}
}
You can also take a look at the multireducer module (https://github.com/erikras/multireducer). It was designed to solve exactly the scenario you propose.
So you would be able to configure your store as such:
import multireducer from 'multireducer';
import heatMap from './heat-map1'
import {combineReducers} from 'redux';
export let reducers = combineReducers({
multireducer: multireducer({
heatMap1: heatMap,
heatMap2: heatMap
})
});
After that, you would then need to use connectMultireducer() instead of redux's standard connect() in order to connect the specific slice of the store to particular components like so:
export default connectMultireducer((state)=> {
return {
onState: state.heatMap.onState,
config: state.heatMap.config
}
})(CHMSHeatMap)
And finally in order to get the correct part of the state to each of those components you would pass in the key when rendering them as such:
<CHMSHeatMap multireducerKey="heatMap1"/>
<CHMSHeatMap multireducerKey="heatMap2"/>
Obviously it's better to read the actual docs at the multireducer repo, but that should give a brief overview. Basically the module is just abstracting the process of adding a key-based lookup to each reducer that is created through the multireducer function.
I suggest original concept of multireducer working without any libraries.
The base idea is unique Symbol action types and self-contained Redux-module like this:
import * as services from './../../../api/services';
const initialState = {
list: [],
};
function getListReducer(state, action) {
return {
...state,
list: action.payload.list,
};
}
function removeItemReducer(state, action) {
const { payload } = action;
const list = state.list.filter((item, i) => i !== payload.index);
return {
...state,
list,
};
}
export default class List {
constructor() {
// action types constants
this.GET_LIST = Symbol('GET_LIST');
this.REMOVE_ITEM = Symbol('REMOVE_ITEM');
}
getList = (serviceName) => {
return async (dispatch) => {
const list = await services[serviceName].get();
dispatch({
type: this.GET_LIST,
payload: {
list,
serviceName,
},
});
};
}
removeItem = (index) => {
return (dispatch) => {
dispatch({
type: this.REMOVE_ITEM,
payload: {
index,
},
});
};
}
reducer = (state = initialState, action) => {
switch (action.type) {
case this.GET_LIST:
return getListReducer(state, action);
case this.REMOVE_ITEM:
return removeItemReducer(state, action);
default:
return state;
}
}
}
More information read there.