Updating nested redux state - reactjs

I have a reducer that receives an action with a payload that I need to update the state with. The problem is the data I need to update in the state is nested data.
I've added my reducer below with some comment and what i tried to do so far.
export default function(state=data, action){
switch (action.type) {
case 'UPDATE_CONTACT_INFO':
let appointment = state[action.appointmentIndex]; // This is the appointment that needs to be updated
appointment.notification.contactInfo = action.payload; // this is the data that needs to be updated with the payload. I tried updating it like this but then not sure how to add it to the state.
return state; // Somehow need to update the state with the new state
break;
default:
return state;
}
}
Below is my initial data structure which I pass into the reducer as the default state.
data = [
{
date: 'Friday, January 6',
time: '4:00 PM-5:00 PM',
notification:
{
contactInfo: [
{
displayMethod:"Phone Call",
method:"Phone",
value:"3473686552"
},
{
displayMethod:"Email",
method:"Email",
value:"memedoe#gmail.com"
}
]
}
},
{
date: 'Saturday, January 7',
time: '2:00 PM-6:00 PM',
notification:
{
contactInfo: [
{
displayMethod:"Phone Call",
method:"Phone",
value:"2123686552"
},
{
displayMethod:"Email",
method:"Email",
value:"johndoe#gmail.com"
}
]
}
}
];
The action.payload in the reducer data is the same structure as contactInfo array in one of the appointments. [Object, Object]

With redux you never update the state. You will have to return a new state object, with the updated data.
In order to do that, you need to use either Object.assign() or the ES6 spread operator {...}
I have provided links to both:
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Operators/Spread_operator
Read up on the reducers here:
http://redux.js.org/docs/basics/Reducers.html
Pay specific attention to We Dont Mutate the state point.

All problems of this type may be solved using react-addons-update package. Read here.
This case can be solved that way:
export default function(state=data, action){
switch (action.type) {
case 'UPDATE_CONTACT_INFO':
return update(state, {[action.appointmentIndex]:{notification: {contactInfo: {$set: action.payload}}}});
default:
return state;
}
}

You need to use object.assign to change the data in your store
const newstateobject = Object.assign({}, state[someIndex], {notification: Object.assign({}, state[someindex].notification, {contactInfo: action.payload})});
return Object.assign({}, state, {[someindex]: newstateobject);

Related

Change state from reducer not from root state using React-Redux when data is inherited

Good afternoon. I am writing an application using react-redux and faced a dilemma. I have already re-thought it several times and can't choose how to organize the project and data structure correctly and conveniently. Design data by inheriting or composing data. I initially went along the path of composition, but I realized that it is inconvenient when there is a one-to-one relationship. I decided to change it to inheritance, because it seemed logical from the point of view of data organization, but there was a big difficulty with reducers, more precisely, I am confused that it turns out to be a single root reducer with a lot of actionTypeskeys . I remember about performance, when elements inherit a data chain from a common ancestor, that this is very bad. And yet I chose this path and I have a question: Please tell me if it is possible to split into several reducers for each level of nesting data. Example
onst initState: IPages = {
idActive: 0,
pages: [
{
id: 1,
title: `Tab #1`,
workspace: {
idActiveDraggableElements: [],
idActiveLines: [],
attributes: {
height: string,
width: string,
viewBox: [0, 0, 5000, 5000]
},
draggableElements: [], // more data
lines: [], // more data
}
},
]
}
Reducer:
export function pagesReducer(
state: IPages = initState,
action: IPageActionTypes
) {
switch (action.type) {
case "ADD_PAGE":
let uniqId = getUniqKeyIdOfArrayList(state.pages);
return {
...state,
pages: state.pages.concat({id:uniqId, title:`Вкладка - ${uniqId}`})
}
case "REMOVE_PAGE": return {
...state,
pages: state.pages.filter(item => item.id !== action.id)
}
case "CHOSE_PAGE": return {
...state,
idActive: action.id
}
case "RENAME_PAGE":
let indexPage = state.pages.findIndex(item => item.id === action.id);
state.pages[indexPage].title = action.title;
return {
...state
}
// ===================
// LONG LIST WHAT BAD...
// It's a bad idea to add editing to the `workspace` field and then `draggableElements`. `lines`
// ... but I understand that this will happen, because I don't know if there is another way.
default:
return state
}
}
Can I edit the `workspace' node without updating the entire application state?
Thanks you for any help.
For data modeling aspect for a 1-to-1 relationship, you can choose either to reference by id or to embed the data. It depends on your query pattern.
In your case which is embedding, you can make use of memoized selectors.
Ideally, since you have an idActive, update your pages data structure to be an object instead of a list.
Like so:
{
pages: {
'1': {
workspace: { ... },
}
}
}
Then for your reducer, think of it as slicing a tree (or nested attribute). Your reducer would then look something like:
function workspaceReducer(state, action) {
// TODO
}
function pagesReducer(state, action) {
switch (action.type) {
case 'UPDATE_WORKSPACE': {
const { id } = action;
const page = Object.assign({}, state.pages[id]);
return {
...state,
pages: {
...state.pages,
[id]: {
...page,
workspace: workspaceReducer(page.workspace, action)
}
}
}
}
}
}
Then to prevent unnecessary re-renders, using memoized selectors,
it would be like:
import { createSelector } from 'reselect';
const pages = state => state.pages;
const activePage = state => state.idActive;
const getActivePage = createSelector(
activePage,
pages,
(id, pages) => pages[id]
);
const getWorkspace = createSelector(
getActivePage,
page => page.workspace
);

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 nested state issue

I am new to React/Redux and I am trying to store one of my objects in Redux state as a Map/Hash with the keys being the primary keys from the objects from the db and the values being the object itself.
However the state seems to get overidden each time I am updating and the new value I am adding is the only one that remains. Here is my code:
import { RECEIVE_CURRENT_SCAN_RESULT } from '../constants';
const initialState = {
currentScanResult: {info:{}, results:[]},
};
export default createReducer(initialState, {
[RECEIVE_CURRENT_SCAN_RESULT]: (state, payload) =>
Object.assign({}, state, {
currentScanResult: payload
})
});
export function createReducer(initialState, reducerMap) {
return (state = initialState, action) => {
const reducer = reducerMap[action.type];
return reducer
? reducer(state, action.payload)
: state;
}
}
I would like to just pass in my object:
{id: 1, thing: "blue"}
and have the state be updated with it. Then if I pass in:
{id: 2, thing: "red"}
I would like my redux state to reflect:
currentScanResult: {1: {id: 1, thing: "blue"}, 2: {id: 2, thing: "red"}}
Is there any easy way for me to do this? Will redux re-render if I am updating a nested value? For example if I pass in:
{id: 2, thing: "purple"}
=> currentScanResult: {1: {id: 1, thing: "blue"}, 2: {id: 2, thing: "purple"}}
I would like to see a behavior like this. I've looked into Immutable JS I am just wondering if I can make this simple use case work without it?
When you do
Object.assign({}, state, {
currentScanResult: payload
})
you are overriding state.currentScanResult. If you want to update it, you need to do something like
Object.assign({}, state, {
currentScanResult: Object.assign({}, state.currentScanResult, payload)
})

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

Resources