Update immutable state with Redux - reactjs

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!!!

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

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

React Native redux reducers best way to push array

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

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

Updating nested redux state

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

Resources