Add / Update Deep Array of Objects in React / Redux - reactjs

In my react redux reducer, how do I add or update a deep nested array of objects with the spread operator? I’m having trouble getting the syntax right.
My state is roughly in this shape:
state: {
fields...
ups: {
fields...
ftr: {
fields...
arts: [
{
artId: 12,
name: ‘joe’,
phones: [
{
phoneId: 58,
number: ‘nnn’
}
]
}
]
}
}
}
I come in with artId and either with an empty phone object or an existing one to add/update the phones array. Likewise the same for the parent arts. Can't do byId and too late to switch to 'normilizr`.

To start, it's a little difficult comprehending the exact issue you are having.. It would be extremely useful if you could supply some of the code you are using to update your state inside of your reducer, not just the shape of your state.. In order to accurately assist, we need to see more code..
With that being said, if I am understanding this correctly, when you are updating state in a reducer, you need to make a deep copy first.. or are you trying to use the spread operator like this: fields...?...or is that just for brevity?
The spread operator offers a shallow copy, so you'll have to do something like this to get a deep[er] copy:
case types.SOME_CONSTANT: {
let _newstate = JSON.parse(JSON.stringify(state));
_newstate.some.deep.nested.property = action.payload;
return _newstate;
}

Related

Recoil - single atom vs multiple atoms

I have an index file that contains a lot of atoms for a wizard I've created.
I thought about moving all the creations -
export const foo1State = atom<string>({
key: "foo1State",
default: "",
});
export const foo2State = atom<boolean>({
key: "foo2State",
default: false,
});
into one using JSON -
export const fooStates = atom<fooState>({
key: "fooStates",
default: {
foo1State: string = "",
foo2State: boolean = false,
}
});
Is that a better approach?
I'll mention that all that inputs are changing frequently so need to consider the renders.
What do you think?
Thanks
If you have separate atoms for each state, then you can independently subscribe to each one. If you combine them all into one, a component that renders foo1State will re-render every time you update foo2State and vice versa. This can be problematic if the atom gets really big.
If you don't have any particular reason why you would need to hold foo1State and foo2State in single atom, go with your first approach, and keep them in separate atoms.

Add nested property in Redux reducer

I've been a few days trying to get my head around Redux to be used with my Reactjs project. I'm working on a reducer and trying to add a nested property into state. To do this, I'm using spread operator at different levels. The problem is that initially the state slice this reducer manages is empty and therefore I run into the following problem... taking this as an example of what I'm trying to do:
var foo = {}; // Initially the state slice is an empty object.
var bar = {...foo,
1: {
...foo[1],
2: {
...foo[1][2],
baz: 100 // New property I'm trying to add.
}
}
};
console.log(bar);
(property keys 1 and 2 are two variables provided to the reducer in my actual app)
I you run the above piece of code it will yield en error saying Cannot read property '2' of undefined at line ...foo[1][2],. Alright, foo[1][2] is not defined yet, but how am I supposed to accomplish this? Maybe I'm missing something very basic...
something like that works for your example but it seems pretty hacky.
bar = {
...foo,
1: {
...foo[1] ? foo[1] : {},
2: {
...foo[1] && foo[1][2] ? foo[1][2] : {},
baz: 100 // New property I'm trying to add.
}
}
}

React-Redux state not updating as expected—am I mutating reducer arguments?

thanks in advance for your attention with this (I believe) very basic question. I'm working on building my first "full-stack" application, and am running into something I can't quite wrap my head around with React-Redux. A brief explanation of the project: users can submit band idea names, and up or down vote others' submissions. Now, I believe that my problem is I'm not interacting with the state appropriately in my reducer dealing with MODIFY_BAND_SCORE actions. Here's the git repository, and I'll also copy and paste my store reducers here:
export const store = createStore(
combineReducers({
bands(bands = defaultState.bands, action) {
switch (action.type) {
case mutations.CREATE_BAND:
return [
...bands,
{
id: action.id,
owner: action.owner,
name: action.name,
score: 0,
flags: 0,
},
];
case mutations.MODIFY_BAND_SCORE:
let targetBandIndex = bands.findIndex(
(band) => band.id === action.bandID
);
let targetBand = bands.splice(targetBandIndex, 1)[0];
targetBand.score = targetBand.score + action.value;
bands.splice(targetBandIndex, 0, targetBand);
return bands;
}
return bands;
},
users(users = defaultState.users, action) {
return users;
},
}),
applyMiddleware(createLogger(), sagaMiddleware)
);
Hopefully that's enough context to make informed suggestions about what's going on here—my apologies for not having a truly minimal working example for this! The behavior I'm seeing from Redux-Logger when I dispatch an action of type MODIFY_BAND_SCORE is that I am (in a way) seeing the change reflected in that the correct band is having its score modified by the correct amount, but it is showing somehow in the previous and next states! Here's a screenshot:
I feel like I've maybe made this post longer than what it needs to be, am I correct in thinking that in my case for mutations.MODIFY_BAND_SCORE I'm actually modifying the state directly? This is probably occurring with my calling of .splice() on bands isn't it?
Like Siddharth mentioned,
let copyOfBands = [...bands]
will create a copy for you. It's important to remember that one of the key parts of Redux is that the store is read-only. It can be easy to forget that when dealing with non-primitive data (I've certainly done that a bunch), but you should always try to remember to make copies of the data, modify the copy, and then push the copy to store. This helps prevent you from getting really weird and hard to debug errors.
It is important to remember that the spread operator here will creates a shallow copy of the array, which means if you have other non-primitive objects inside the array (such as other arrays), you will have to copy those as well.

Ordering time-stamped date in react redux?

I am struggling to find a good way to organise my state in Redux with React. It is a simple Blog with posts. I am getting an array from the api that is already ordered from oldest to newest. Since the app is a bit more complicated, I'd like to store the state in an object with the uuid as keys, so I can access it easily.
{ 
uuid_post1: { ...post1 },
uuid_post2: { ...post2 },
...
}
With the format above it is easy for me to sync the state between API and React without refetching data all the time. But I do need to display the output in an ordered form from newest to oldest.
Is there an easy way to solve this and keep the date info in the object? Is there another good way to organise state for this use case?
Thanks.
In your reducer you'll want to index the posts by ID and also save the sorted IDs. This will let you look up the posts efficiently, and also maintain a list of them in the order that you received them (oldest to newest). You can get them in the reverse order using a selector.
switch (action.type) {
case 'POSTS_RECEIVED':
return {
...state,
orderedPostIDs: posts.map(p => p.id),
postsById: posts.reduce((acc, post) => {
acc[post.id] = post;
}, {});
}
}
With this orderedPostIDs is an array of Post IDs and postsById is an object where the keys are Post IDs and the values are the posts.
function getPostByID(state, postId) {
return state.posts.postsById[id];
}
// Should use reselect here because it's returning a new array with every call
// oldest to newest - post are received from API in this order
function getPostsSortedByDateAscending(state) {
return state.posts.orderedPostIDs.map(id => getPostByID(state, id));
}
// Should use reselect here because it's returning a new array with every call
// newest to oldest
function getPostsSortedByDateDescending(state) {
// copy to new array, because Array.reverse mutates the value
return [].concat(getPostsSortedByDateAscending(state)).reverse();
}
Another approach which makes your state simpler is to only store postsById, as Max commented below. To get the sorted posts, you'll use Object.values() and then sort them as needed.
// oldest to newest - need to use reselect here
function getPostsSortedByDateAscending(state) {
return _.sortBy(Object.values(state.posts.postsById), p => p.date)
}
// oldest to newest - need to use reselect here
function getPostsSortedByDateDescending(state) {
// copy to new array, because Array.reverse mutates the value
return [].concat(getPostsSortedByDateAscending(state)).reverse();
}

Should I store static configuration in redux?

I am building a react/redux web app and am wondering where I should static configuration information that never changes (while the webapp is running anyway).
This is the data in question
This information is used in different parts of the app, for example: there is a form where you are able to select any item out of the main array, and by doing so populating another select field with properties of the selected array:
<select>Choose an exchange</select>
<select>Choose a market (that is available in the above exchange)</select>
This would lend itself nicely to some reducer logic (that sets state.markets based on what is selected in the first select), but should it filter based on other state in the tree, or just load the data in a closure inside the reducer (keeping everything unrelated outside of state tree)? Or is this not state at all (and should the container load this file in and filter based on a single state.exchange state prop)?
When the form is filled in the result will be handled like:
{exchange: 'a', market: 'b'}
So that would be state too (I guess?)
My understanding of redux is that we should only be storing stateful data in the store, that is, data that is subject to change. Static data by definition does not have state, and therefore does not need to be tracked as such.
As a result, I typically have a /common/app-const.js file where I store these types of static objects. In your case, you can simply move all the static data from exchange.js into a common file that you then import wherever you need it.
/common/app-const.js
export default {
markets: [
{ pair: ['USD', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
{ pair: ['RUR', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
{ pair: ['EUR', 'BTC'], minimalOrder: { amount: 0.01, unit: 'asset' } },
...
}
I understand your approach however, it would be nice to simply inject your data, by way of connect() via react-redux, however its a bit more straightforward to just import the static data from a file where needed.

Resources