Adding/Removing from array doesnt trigger rerender via redux - reactjs

I am currently having an issue adding/reducing to an array via redux. Just to make sure my redux state works, i hard coded values in and it triggers every time i press a button.
Code that works:
import * as actionType from '../actions/ActionType';
const counterReducer = (state = [], action) => {
let newState = [];
switch (action.type) {
case actionType.ADD_FILTER:
if (!state.includes(action.payload)) {
return newState = ['test'];
}
return newState = ['test'];
default:
return state;
}
};
export default counterReducer;
Code that doesnt trigger a rerender:
import * as actionType from '../actions/ActionType';
const counterReducer = (state = [], action) => {
let newState = [];
switch (action.type) {
case actionType.ADD_FILTER:
if (!state.includes(action.payload)) {
const current = state;
current.push(action.payload);
return newState = current;
}
return newState = state;
default:
return state;
}
};
export default counterReducer;
The redux store however updates? Help?

The code that doesn't work, the reason for that is you are mutating your state(using push method on an array which has reference to the old state), which redux will not register as a change because you are again passing the reference of the old state. Read on cloning arrays and slice method.
const current =state.slice();
current.push(action.payload)
return current
Now that you have a proper clone, return that array. That would trigger re-render.
Also, the first case works because you are always creating a new array and its reference.

Related

How to replace an entire array in a redux reducer while allowing application to re-render?

In one of my application's reducers, I require to replace an entire array. Hence, I have written my reducer like this:
const arrayReducerDefaultState = []
const arrayReducer = (state = arrayReducerDefaultState, action) =>{
switch(action.type){
case "CHANGE_ARRAY":
return{
...state,
array: action.array
}
default:
return state
}
}
export default arrayReducer
When the action is called, I see the array is being updated (using Redux Dev Tools). But the application does not render again when the 'array' value is updated.
How do I change my reducer so that I can keep replacing my entire array while allowing the application to re-render when the store changes.
EDIT 1:
const arrayReducerDefaultState = {
array: []
}
const arrayReducer = (state = arrayReducerDefaultState, action) =>{
switch(action.type){
case "CHANGE_ARRAY":
return {
...state,
array: action.array
}
default:
return state
}
}
Since I was having problems working with the default state being an array, I made it such that it is an object with an array in it. 'array' now is not undefined but changing it still does not re-render the application.
Tour state is array, but you return object in "CHANGE_ARRAY" case.
const arrayReducerDefaultState = []
const arrayReducer = (state = arrayReducerDefaultState, action) =>{
switch(action.type){
case "CHANGE_ARRAY":
return action.array
default:
return state
}
}
export default arrayReducer
Actually you can have mistake in selector.
PS action has struct like object where must be type (required), payload (optinal) - there is you data, meta (optinal) - another additional inforamtion
The above solution works, but my problem was somewhere else.

Which is better practice for setting state in react redux

I am learning Redux. I am kind of new so I dont want to develop a bad habit at the start which is why i am writing this.
The doc i am reading says that in the reducers we need to always set the state where we change it - in other words do the following:
const someReducer = (state=initState, {type, payload}) => {
switch(type) {
case 'CHANGE_VALUE_ONE' : {
state = {...state, valueOne: payload }
break
}
case 'CHANGE_VALUE_TWO' : {
state = {...state, valueTwo: payload }
break
}
default:
state = state
}
return state
}
My approach was creating a newState constant which is identical to the state we receive, make change to the dummy and return it - or in code:
const userReducer = (state= {name: null, age: null}, {type, payload}) => {
const newState = {...state}
switch(type) {
case 'CHANGE_VALUE_ONE' : {
newState.valueOne= payload
break
}
case 'CHANGE_VALUE_TWO' : {
newState.valueTwo= payload
break
}
}
return newState
}
I think the way i do it is cleaner - if i have a complicated state i can easily change what i need to change without mutating the original state.
With first snippet, you are violating the principle of pure reducer. That means instead of updating the value directly in state, create a new copy of it and do the changes in that.
As per Doc:
Reducers are just pure functions that take the previous state and an
action, and return the next state. Remember to return new state
objects, instead of mutating the previous state.
Which you are clearly violating by assigning new value to state, here:
state = {...state, valueOne: payload }
Note: Don't forget const newState = {...state}, will only create the shallow copy, so if you have nested state, this will also violate the principle.
In case of nested state, update it in this way:
var obj = { car: { model: '', num: '' } };
function update() {
return {
...obj,
car: {
...obj.car,
model: 'abc'
}
}
}
console.log('new obj = ', update());
console.log('old obj = ', obj);
I think you overcomplicate yourself. Here's a reducer that I use (as an example):
import * as types from '../actions/types';
import { Enums } from '../helpers/enums';
const initialState = {
testState: Enums.command.STOP
};
export default function(state = initialState, action) {
switch (action.type) {
case types.SET_CREEP_TEST_STATE:
return {...state, testState: action.testState};
case types.SET_CREEP_TEST_TIME:
return {...state, testime: action.testime};
default: return state;
}
};
It shouldn't be any more complex than that.
Always keep the state immutable, use pure function. And there is a performance issue for your approach.
Your approach is immutable. But remember, any dispatched action will go through all reducers. That means even the action is not related to your reducer, you still create a new shallow copy of it. It's not necessary. Don't do this
// This newState will be created by every action
const newState = {...state}
switch(type) {
case 'CHANGE_VALUE_ONE' : {
newState.valueOne= payload
break
}
case 'CHANGE_VALUE_TWO' : {
newState.valueTwo= payload
break
}
}
return newState
Your solution isn't immutable, the doc's one is. In more complex cases, I recommend to you use immer, the lib approach is similar to yours, but you will mutate a copy of the state and then immer will merge this copy with the actual state to create a new one. Helps you to work with nested data and bring immutability to reducers. Your case, with immer:
const userReducer = produce(state, draft => {
switch(type) {
case 'CHANGE_VALUE_ONE' : {
draft.valueOne= payload
break
}
case 'CHANGE_VALUE_TWO' : {
draft.valueTwo= payload
break
}
}
},{name: null, age: null})

How to reset the state to it's initial state in redux store?

In redux store I have set initial state to some value as shown below:
const search_initial_state = {
FilterItem:18
}
I am changing this state value in another function manually
But after click on reset button I want to set "FilterItem" state to it's initial value.(i.e. 18)
If i understood you correctly, you want to reset only one value to its original/initial value.
Define an action creator that dispatches action type of RESET_FILTER_ITEM and then whenever you want to reset filter item dispatch this action.
const resetFilterItem = () {
return {
type: "RESET_FILTER_ITEM"
}
}
and your reducer will look like this after implementing reset case
const myReducer = (state = initialState, action) => {
switch(action.type){
case "OTHER_CASE":
return {...state}
case "RESET_FILTER_ITEM":
return {
...state,
FilterItem: initialState.FilterItem
}
default:
return state
}
}
Hope this will help you.
Reducers will update their state when an action has been dispatched for them to process. So likewise, you have to dispatch an action that tells your reducer to return the initial state.
//Reducer code
const initialState = {}
const myReducer = (state = initialState, action) => {
switch(action.type){
case "FILTER_ITEM":
return {...someUpdatedState}
case "RESET_ITEM":
return initialState
default:
return state
}
}
Your reducer will return a new state depending on the action type.
So just define an action creator that dispatches action type of RESET_ITEM or something else you may want to call it. Then use it when you want to reset.
const resetItem = () => {
return {
type: "RESET_ITEM"
}
}

React Redux state.set not refreshing my components

I am using a immutable state in my reducer, initially i am dispatching 'REQUESTLOGINDATASUCCEEDED' event which calls state.set() it was refreshing my components, then i want to update the same state variable i dispatched "USERPREFERENCESUCCEEDED" and call the same state.set() my component is not refreshing now. PFB the code.
import { fromJS } from 'immutable';
const initialState = fromJS({
loginData : {}
});
function DataGridContainerReducer(state = initialState, action) {
switch (action.type) {
case 'REQUESTLOGINDATASUCCEEDED':
return state.set('loginData', action.loginData);
case 'USERPREFERENCESUCCEEDED':
return updateUserPreference(state, action.userPreference);
default:
return state;
}
}
function updateUserPreference(state, userPreference) {
debugger;
let loginData = state.get('loginData');
let searchDataIndex = 0;
loginData.userPreferences.splice(searchDataIndex, 1);
loginData.userPreferences.push(userPreference);
return state.set('loginData', loginData);
}
export default DataGridContainerReducer;
Please check and let me know what is the issue here.
Thanks in advance.

How can I toggle property in reducer?

I have built a cart app with this reducer in reactjs/redux:
const initialState = {
items: [],
cartOpen: false,
total: 0
}
const Cart = (state = initialState, action) => {
switch (action.type) {
case 'ADD_TO_CART':
let newstate = [...state, action.payload];
var newTotal = 0;
newstate.forEach(it => {
newTotal += it.item.price;
});
newstate.total = newTotal;
newstate.cartOpen =true
return newstate;
case 'TOGGLE_CART':
debugger;
return !state.cartOpen;
default:
return state
}
}
export default Cart;
I am trying to set the state for the cart ie open but when I check the logs the cart property is updated and not the cartOpen property?
Redux assumes that you never mutate the objects it gives to you in the
reducer. Every single time, you must return the new state object.
Even if you don't use a library like Immutable, you need to completely
avoid mutation.
case 'TOGGLE_CART':
return !state.cartOpen;
Doing ^^ this is mutating your state (corrupting your state object). When you don't guarantee immutability, Redux loses its predictability and efficiency.
To achieve immutable state, we can use vanilla Object.assign or its more elegant alternative object spread syntax.
case 'TOGGLE_CART':
return {
...state,
cartOpen: !state.cartOpen
}
Your reducer must always return the complete slice of the app's state for which it is responsible. For TOGGLE_CART, you are only returning the boolean value for openCart.
Instead, create a copy of the previous state object and only update the single property you want to change:
case 'TOGGLE_CART':
return Object.assign({}, state, {
cartOpen: !state.cartOpen
});

Resources