React Redux infinite loop update - Updated with combineReducers started the problem - javascript

Ok so i'll try to sum it up. I've modified my code to have 2 reducers instead of 1 (for better reading) and i started getting an infinite loop.
The redux props seem to keep updating, however there's no change in them.
This is my reducer file.
import { ADD_LISTITEM } from "../constants/action-types.jsx";
import { SET_SPOTOKEN } from '../constants/action-types.jsx';
import { SET_LISTVIEW } from '../constants/action-types.jsx';
import { CHECK_SPOTOKEN } from '../constants/action-types.jsx';
import { SET_FORMPANEL } from '../constants/action-types.jsx';
import { SET_FORMPANELTYPE } from '../constants/action-types.jsx';
import { combineReducers } from 'redux'
const initialState = {
listItems: [],
spoToken: '',
listView: "All Items",
checkToken: "200",
showNotice: true,
//showFormPanel: false,
//formPanelType: ''
};
const listState = {
showFormPanel: false,
formPanelType: ''
}
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_LISTITEM:
return { ...state, listItems: [...state.listItems, action.payload] };
case SET_SPOTOKEN:
return { ...state, spoToken: action.payload };
case SET_LISTVIEW:
return { ...state, listView: action.payload };
case CHECK_SPOTOKEN:
return { ...state, checkToken: action.payload };
default:
return state;
}
};
const listReducer = (state = listState, action) => {
switch (action.type) {
case SET_FORMPANEL:
return { ...state, showFormPanel: action.payload };
case SET_FORMPANELTYPE:
return { ...state, formPanelType: action.payload };
default:
return state;
}
}
const allReducers = combineReducers({
rootReducer,
listReducer
});
export default allReducers;
Any idea why this might cause a problem?
Edited with Redux State ->
{
root: { … }, newItem: { … }, forms: { … }
} forms: {
$form: { … }, newItem: { … }
} newItem: { Title: "", ItemID: "", OfferingID: "", DeliveryModality: "", Status: "", … }
root: listReducer: {
showFormPanel: false, formPanelType: ""
} rootReducer: {
listItems: Array(4), spoToken: { … }, listView: "All Items", checkToken: "200", showNotice: true
}
Puttin the above into words since it looks kinda messed up.
State contains -> forms, newItem, root. Root contains listReducer and rootReducer.
FIXED: only 1 combineReducers allowed.
Apparently since i didnt have different reducers before adding this one i had another combineReducers in my redux-form file. I assume that was the problem (having 2 combineReducers) because it seems to work fine after modifying it.

Related

Dispatching multiple actions in redux duplicates the state parameters

I have used created two actions and their respective reducers. When i dispatch any single action, both actions initial states are being saved to state where the parameters of the states are duplicated.
actions/index.js
import { COUNTER_CHANGE, UPDATE_NAVIGATION } from "../constants";
export function changeCount(count) {
return {
type: COUNTER_CHANGE,
payload: count,
};
}
export function updateNavigation(obj) {
return {
type: UPDATE_NAVIGATION,
payload: obj,
};
}
reducers.js
import { COUNTER_CHANGE, UPDATE_NAVIGATION } from "../constants";
import logger from "redux-logger";
const initialState = {
count: 0,
navigation: {},
};
export const countReducer = (state = initialState, action) => {
switch (action.type) {
case COUNTER_CHANGE:
return {
...state,
count: action.payload,
};
default:
return state;
}
};
export const updateNavigation = (state = initialState, action) => {
switch (action.type) {
case UPDATE_NAVIGATION:
return {
...state,
navigation: action.payload,
};
default:
return state;
}
};
// export default countReducer;
reducer/index.js
import { countReducer, updateNavigation } from "../reducers/countReducer";
import { combineReducers } from "redux";
const allReducers = combineReducers({
countReducer,
updateNavigation,
});
export default allReducers;
Dispatching actions
componentDidMount = () => {
const { navigation } = this.props;
this.props.updateNavigation(navigation);
};
const mapDispatchToProps = (dispatch) => {
return { ...bindActionCreators({ changeCount, updateNavigation }, dispatch) };
};
As we can see here I have triggered only updateNavigation action. But it updates states with duplicate parameters in redux state as shown below
The expected o/p will be
countReducer : {count : 0}
updateNavigation : {navigation :{}}
The shape of state for each reducer is incorrect. See defining-state-shape docs and try this:
export const countReducer = (state = { count: 0 }, action) => {
switch (action.type) {
case COUNTER_CHANGE:
return {
...state,
count: action.payload,
};
default:
return state;
}
};
export const updateNavigation = (state = { navigation: {} }, action) => {
switch (action.type) {
case UPDATE_NAVIGATION:
return {
...state,
navigation: action.payload,
};
default:
return state;
}
};
import { countReducer, updateNavigation } from "../reducers/countReducer";
import { combineReducers } from "redux";
const allReducers = combineReducers({
countReducer,
updateNavigation,
});
const store = createStore(allReducers);
console.log(store.getState());
Output:
{ countReducer: { count: 0 }, updateNavigation: { navigation: {} } }
In your action/index.js
import { COUNTER_CHANGE, UPDATE_NAVIGATION } from "../constants";
export function changeCount(count) {
dispatch( {
type: COUNTER_CHANGE,
payload: count,
});
}
export function updateNavigation(obj) {
dispatch({
type: UPDATE_NAVIGATION,
payload: obj,
});
}
Dispatch the data without returning it

How to update initialState in redux reducer

Hi i update the initial state in reducer by adding new array variable to it but when i console this reducer state it show me the old state and that array is not in the state which is causing the crash when i try to access that array.
import {
USER_SET_LOC,
USER_SET_FIRST,
} from '../actions/userActions'
const initialState = {
location: {
region: 'ABC',
isDefault: true
},
first: [],
second : []
}
function userReducer (state = initialState, action) {
switch (action.type) {
case USER_SET_LOC: {
return {
...state,
location: {
...action.payload,
isDefault: false
}
}
}
case USER_SET_FIRST: {
return {
...state,
first: action.payload
}
}
default:
return state;
}
}
export default userReducer
i added the second array later.
when i console the state in another class i get this
{"first": [], "location": {"isDefault": true, "region": "ABC"}}
Whatever i change in the initialState it does not appear.
When i uninstall it and reinstall the app then new changes appear

how to change the reducer case function to immutable js

i have a code to update the count value of object. now i need to convert it to immutable.js. i am pretty much new to immutable.js. so how can i convert this code to immutable.js format.
import * as actionTypes from '../actions/actionTypes';
const initialState={
ingredients: {
salad:0,
meat:0,
cheese:0,
},
totalprice:40
}
const ingredientCost = {
cheese:20,
meat:30,
salad:10,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_INGREDIENT:
return {
...state,
ingredients:{
...state.ingredients,
[action.ingredientName] : state.ingredients[action.ingredientName]+1
},
totalprice: state.totalprice + ingredientCost[action.ingredientName],
};
case actionTypes.REMOVE_INGREDIENT:
return {
...state,
ingredients:{
...state.ingredients,
[action.ingredientName] : state.ingredients[action.ingredientName]-1,
},
totalprice: state.totalprice - ingredientCost[action.ingredientName],
};
default:
return state;
}
};
export default reducer;
i tried to change the code to immutable format:
import * as actionTypes from '../actions/actionTypes';
import Immutable from 'immutable';
const initialState=Immutable.fromJS({
ingredients: {
salad:0,
meat:0,
cheese:0,
},
totalprice:40
})
const ingredientCost = {
cheese:20,
meat:30,
salad:10,
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.ADD_INGREDIENT:
return state.ingredients[action.ingredientName].merge(state.ingredients[action.ingredientName]+1)
case actionTypes.REMOVE_INGREDIENT:
return {
...state,
ingredients:{
...state.ingredients,
[action.ingredientName] : state.ingredients[action.ingredientName]-1,
},
totalprice: state.totalprice - ingredientCost[action.ingredientName],
};
default:
return state;
}
};
export default reducer;
but while doing this i am not able to update the state. i am getting initialstate but i dont know how to update it.
thanks in advance
since initialState is immutable object, You cant use state.ingredients, You should use
state.set() / state.setIn() or
state.update() / state.updateIn() or
state.merge() / state.mergeIn()
read: https://facebook.github.io/immutable-js/docs/#/Map

Correct way to update byId and allIds in reducer

This is my state shape:
export default {
app: {
loading: false,
loadError: null
},
search: {
type: null,
text: ''
},
things: {
byId: {
},
allIds: []
}
}
When I fetch new things from the server, I want to update both things.byId and things.allIds - as recommended by Redux docs. I'm doing it in the following way but I feel like there's a better way:
import { LOAD_THINGS_SUCCESS } from '../actions/actionTypes'
import initialState from './initialState'
export default function things(state = initialState.things, action) {
switch(action.type) {
case LOAD_THINGS_SUCCESS:
const thingsById = {}
action.things.forEach(thing => {
thingsById[thing.id] = thing
})
return {
...state,
byId: thingsById,
allIds: action.things.map(thing => thing.id)
}
default:
return state
}
}
I'm using combineReducers and the above reducers/things.js is one of them. The part I'm concerned about is action.things.forEach... I have a feeling there's a cleaner / more efficient way of doing this.

Redux: Create root reducer from combineReducers and "loose" properties

I want to create a Redux store that has this shape:
store = {
loaded: Boolean,
loading: Boolean,
view: Object, // uses combineReducers
layers: Object // uses combineReducers
}
So far, my root reducer looks like this:
rootReducer.js
import view from './view';
import layers from './layers';
const initialState = {
loaded: false,
loading: false,
};
function loadState(state = initialState, action = {}) {
switch (action.type) {
case 'LOADED':
return {
...state,
loaded: true,
loading: false,
};
case 'LOADING':
return {
...state,
loaded: false,
loading: true,
};
default:
return state;
}
}
export default combineReducers({
view,
layers,
// hmmmm, putting loadState here would give me a loadState object property,
// not loose 'loaded' and 'loading' properties
});
How do I also have these "loose" properties like loaded and loading alongside them?
Sometimes I find writing individual reducers for a few simple properties to be obnoxious overhead, so I have a combineReducersWithRoot utility I use sometimes.
export function combineReducersWithRoot(rootReducer, reducers) {
return (state, action) => {
// Ensure the root state object is a new object; otherwise
// React may not re-render.
let newState = {...rootReducer(state, action)};
Object.keys(reducers).forEach(domain => {
let obj = state ? state[domain] : undefined;
newState[domain] = reducers[domain](obj, action);
});
return newState;
};
}
Now, given a state structure something like this:
{
loading: bool
loaded: bool
data: {
filter: string
arr: object[]
}
}
You can do this:
function rootReducer(state = {loading: false, loaded: false}, action) {
switch(action.type) {
case STARTED_LOADING:
return {...state, loading: true, loaded: false};
case FINISHED_LOADING:
return {...state, loading: false, loaded: true};
default:
return state;
}
}
function dataReducer(state = {filter: '', arr: []}, action) {
switch (action.type) {
case SET_FILTER:
return {...state, filter: action.value};
case SET_DATA:
return {...state, arr: action.arr};
default:
return state;
}
}
export default combineReducersWithRoot(rootReducer, {data: dataReducer});
#PhiNguyen is right, I need to turn these loaded/loading properties into their own reducers!
import { LOADED, LOADING } from '../ActionTypes';
export function loaded(state = false, action = {}) {
switch (action.type) {
case LOADED:
return true;
case LOADING:
return false;
default:
return state;
}
}
export function loading(state = false, action = {}) {
switch (action.type) {
case LOADED:
return false;
case LOADING:
return true;
default:
return state;
}
}
rootReducer.js
import { loaded, loading } from './load';
import view from './view';
import layers from './layers';
export default combineReducers({
loaded,
loading,
view,
layers
});

Categories