React Native redux reducers best way to push array - reactjs

I have the following state and reducer but it's not pushing in the new array object.
const initialState = {
photos: [],
selectedPhoto:{},
photosTeamId:'',
photosProjectId:''
};
case actionTypes.PHOTOS_UPDATE:
return Object.assign({}, state, {
photos:action.data.photos,
photosTeamId:action.data.photosTeamId,
photosProjectId:action.data.photosProjectId
})
photos is not getting pushed but overwritten

Here's a more cleaner way using javascript spread syntax:
const initialState = {
photos: [],
selectedPhoto:{},
photosTeamId:'',
photosProjectId:''
};
case actionTypes.PHOTOS_UPDATE:
return {
...state,
photos: [...state.photos, ...actions.data.photos],
photosTeamId: action.data.photosTeamId,
photosProjectId: action.data.photosProjectId
}

case actionTypes.PHOTOS_UPDATE:
return {
...state,
photos: state.photos.concat(action.data.photos),
photosTeamId: action.data.photosTeamId,
photosProjectId: action.data.photosProjectId
};

Here's the spread operator […]. The spread operator can be used to take an existing array and add another element to it while still preserving the original array.
Example:
case actionTypes.PHOTOS_UPDATE:
return [
...state,
Object.assign({}, {
photos:action.data.photos,
photosTeamId:action.data.photosTeamId,
photosProjectId:action.data.photosProjectId
})
];

Related

Update deeply nested state object in redux without spread operator

I've been breaking my head for a week or something with this !!
My redux state looks similar to this
{
data: {
chunk_1: {
deep: {
message: "Hi"
}
},
chunk_2: {
something: {
something_else: {...}
}
},
... + more
},
meta: {
session: {...},
loading: true (or false)
}
}
I have an array of keys like ["path", "to", "node"] and some data which the last node of my deeply nested state object should be replaced with, in my action.payload.
Clearly I can't use spread operator as shown in the docs (coz, my keys array is dynamic and can change both in values and in length).
I already tried using Immutable.js but in vain.. Here's my code
// Importing modules ------
import {fromJS} from "immutable";
// Initializing State ---------
const InitialState = fromJS({ // Immutable.Map() doesn't work either
data: { ... },
meta: {
session: {
user: {},
},
loading: false,
error: "",
},
});
// Redux Root Reducer ------------
function StoreReducer(state = InitialState, action) {
switch (action.type) {
case START_LOADING:
return state.setIn(["meta"], (x) => {
return { ...x, loading: true };
});
case ADD_DATA: {
const keys = action.payload.keys; // This is a valid array of keys
return state.updateIn(keys, () => action.payload); // setIn doesn't work either
}
}
Error I get..
Uncaught TypeError: state.setIn (or state.updateIn) is not a function
at StoreReducer (reducers.js:33:1)
at k (<anonymous>:2235:16)
at D (<anonymous>:2251:13)
at <anonymous>:2464:20
at Object.dispatch (redux.js:288:1)
at e (<anonymous>:2494:20)
at serializableStateInvariantMiddleware.ts:172:1
at index.js:20:1
at Object.dispatch (immutableStateInvariantMiddleware.ts:258:1)
at Object.dispatch (<anonymous>:3665:80)
What I want ?
The correct way to update my redux state (deeply nested object) with a array containing the keys.
Please note that you are using an incredibly outdated style of Redux. We are not recommending hand-written switch..case reducers or the immutable library since 2019. Instead, you should be using the official Redux Toolkit with createSlice, which allows you to just write mutating logic in your case reducers (and thus also just using any helper library if you want to use one).
Please read Why Redux Toolkit is how to use Redux today.
you could use something like that:
import { merge, set } from 'lodash';
export default createReducer(initialState, {
...
[updateSettingsByPath]: (state, action) => {
const {
payload: { path, value },
} = action;
const newState = merge({}, state);
set(newState, path, value);
return newState; },
...}

ReactJS - Proper way for using immutability-helper in reducer

I have the following object which is my initial state in my reducer:
const INITIAL_STATE = {
campaign_dates: {
dt_start: '',
dt_end: '',
},
campaign_target: {
target_number: '',
gender: '',
age_level: {
age_start: '',
age_end: '',
},
interest_area: [],
geolocation: {},
},
campaign_products: {
survey: {
name: 'Survey',
id_product: 1,
quantity: 0,
price: 125.0,
surveys: {},
},
reward: {
name: 'Reward',
id_product: 2,
quantity: 0,
price: 125.0,
rewards: {},
},
},
}
And my reducer is listening for an action to add a reward to my object of rewards:
case ADD_REWARD:
return {
...state, campaign_products: {
...state.campaign_products,
reward: {
...state.campaign_products.reward,
rewards: {
...state.campaign_products.reward.rewards,
question: action.payload
}
}
}
}
So far so good (despite the fact that every object added is named "question")... its working but its quite messy. I've tried to replace the reducer above using the immutability-helper, to something like this but the newObh is being added to the root of my state
case ADD_REWARD:
const newObj = update(state.campaign_products.reward.rewards, { $merge: action.payload });
return { ...state, newObj }
return { ...state, newObj }
First, you must understand how the object shorthand works. If you're familiar with the syntax before ES2015, the above code translates to:
return Object.assign({}, state, {
newObj: newObj
});
Note how the newObj becomes a key and a value at the same time, which is probably not what you want.
I assume the mentioned immutability-helper is this library: https://www.npmjs.com/package/immutability-helper. Given the documentation, it returns a copy of the state with updated property based on the second argument.
You're using it on a deep property so that it will return a new value for that deep property. Therefore you still have to merge it in the state, so you have to keep the approach you've labelled as messy.
What you want instead is something like:
const nextState = update(state, {
$merge: {
campaign_products: {
reward: {
rewards: action.payload
}
}
}
});
return nextState;
Note how the first argument is the current state object, and $merge object is a whole object structure where you want to update the property. The return value of update is state with updated values based on the second argument, i.e. the next state.
Side note: Working with deep state structure is difficult, as you've discovered. I suggest you look into normalizing the state shape. If applicable, you can also split the reducers into sub-trees which are responsible only for the part of the state, so the state updates are smaller.

Update immutable state with Redux

I am using Redux to update my state which is immutable. I want to update nested array of object in my reducer by simply targeting list[] as I need to update it with new object. My first item's (board1) list does get updated as I dispatch action but once I dispatch for the next item(s) board2 and above, they overwrite my state and it return single item. Your help would be highly appreciated.. Thanks
const initialState = {
board: [
{ boardId: 1, boardname: "board1", list: [] },
{ boardId: 2, boardname: "board2", list: [] }
]
};
export default function(state = initialState, action) {
switch (action.type) {
case "ADD_LIST":
state = {
...state.board,
board: [
...state.board[action.payload.boardId - 1],
{
...state.board[action.payload.boardId - 1],
list: [
...state.board[action.payload.boardId - 1].list,
{
listId: state.board[action.payload.boardId - 1].list.length + 1,
listname: action.payload.listname
}
]
}
]
};
break;
default:
break;
}
return state;
}
My choice is to use dotprop immutable.
https://github.com/debitoor/dot-prop-immutable.
In addition. For updating different keys at once. I write a wrapper function to do it.
You are using ES6 spread operator which isn't bad, but it starts to get annoying when working with nested objects. My advice is to try immer, it will make your life much easier!!!

React Redux reducer is overwriting values LOAD_SUCCESS?

I have a profilesReducer that I want to use to store 1 or more user profiles in my redux store. As an example think Twitter, which needs to store my profile as well as other profiles.
Here is my profilesReducer.js:
import * as types from '../actions/actionTypes';
const initialState = []
export default function profilesReducer(state = initialState, action) {
switch (action.type) {
case types.LOAD_PROFILE_SUCCESS:
return [Object.assign({}, action.profile)]
case types.UPDATE_PROFILE_SUCCESS:
return [
...state.filter(profile => profile.id !== action.profile.id),
Object.assign({}, action.profile)
]
default:
return state;
}
}
The problem is LOAD is receiving more than one profile (distinguished by profile.id) but is overwriting the existing profile in the store instead of appending/updating.
I need LOAD_PROFILE to allow for more than 1 user's profile in the store. Any suggestions?
Spread existing state and just add new profile at the end:
case types.LOAD_PROFILE_SUCCESS:
return [...state, action.profile]
Object.assing({}, action.profile) is unnecessary, since all that it does here is copying action.profile into a new empty object {} and returns it.
Pretty sure your code needs to change to this.
import * as types from '../actions/actionTypes';
const initialState = []
export default function profilesReducer(state = initialState, action) {
switch (action.type) {
case types.LOAD_PROFILE_SUCCESS:
return [...Object.assign({}, action.profile)]
case types.UPDATE_PROFILE_SUCCESS:
return [
...state.filter(profile => profile.id !== action.profile.id),
Object.assign({}, action.profile)
]
default:
return state;
}
}
The way you had it, you were simply returning an array containing this one item in action.profile, but you need to add your item in to the array that already has the other items. You also cannot simply push as that would mutate the array. This line is all you need to change.
return [Object.assign({}, action.profile)]
Does not append
return [...Object.assign({}, action.profile)]
does append and does not mutate.
You can achieve what you want with either using Object.assign or the spread operator syntax, You need not use both
With Object.assign,
case types.LOAD_PROFILE_SUCCESS:
return Object.assign([], state, action.profile)
With Spread operator syntax
case types.LOAD_PROFILE_SUCCESS:
return [...state, action.profile]
However React docs advise you to use Spread syntax over Object.assign, See this post:
Using Object.assign in React/Redux is a Good Practice?
Edit
I have changed my solution, it includes a way to update an array.
Here is a CodeSandbox to test: https://codesandbox.io/s/zk9r6k1z4p
import * as types from '../actions/actionTypes';
const initialState = []
export default function profilesReducer(state = initialState, action) {
switch (action.type) {
case types.LOAD_PROFILE_SUCCESS:
return [...state, action.profile]
case types.UPDATE_PROFILE_SUCCESS:
return state.map( (profile) => {
if(profile.id !== action.profile.id) {
return profile;
}
return {
...profile,
...action.profile
};
});
default:
return state;
}
}

reducer: adding to array data

if i pull some data from an external source fro the initial state, then want to add additional information like for example 'liked'?
i've tried adding to the products array but its go messy, I'm thinking i should have an additional array for liked items then put the product id in this, the only thing is i need it to reflect in the product that it has been liked and I'm mapping the product data to the item.
whats the best way to go about this ?
const initialState = {
isFetching: false,
products: [],
};
should i add favs: [] ?
how would i reflect the liked state to my product as I'm mapping the products array to the product component? and the liked state is now in the favs?
i tried doing this to add it to the product array but it got really messy (something like this)
case ADD_LIKED:
state.products[action.index]['liked'] = true;
return state;
state.products[action.index]['liked'] = true;
The problem here is that you are mutating the state inside the reducer which is one of the things you should never do inside a reducer.
You'll find that writing functions which don't mutate the data are much easier if you break them down into smaller parts. For instance you can start to split your application up.
function productsReducer(products = [], action) {
// this reducer only deals with the products part of the state.
switch(action) {
case ADD_LIKED:
// deal with the action
default:
return products;
}
}
function app(state = {}, action) {
return {
isFetching: state.isFetching,
products: productsReducer(state.products, action)
}
}
In this case I would definitely want to write a little immutability helper.
function replaceAtIndex(list, index, replacer) {
const replacement = replacer(list[index]);
const itemsBefore = list.slice(0, index),
itemsAfter = list.slice(index + 1);
return [...itemsBefore, replacement, ...itemsAfter];
}
You can complement this with a generic function for changing objects in lists.
function updateInList(list, index, props) {
return replaceAtIndex(list, index, item => {
return { ...props, ...item };
});
}
Then you can rewrite your function in the immutable form
switch(action) {
case ADD_LIKED:
return updateInList(products, action.index, { liked: true });
default:
return products;
}
You could even get fancy by partially applying the function. This allows you to write very expressive code inside your reducers.
const updateProduct = updateInList.bind(this, products, action.index);
switch(action) {
case ADD_LIKED:
return updateProduct({ liked: true });
case REMOVE_LIKED:
return updateProduct({ liked: false });
default:
return products;
}

Resources