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

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.

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 / Update Deep Array of Objects in React / Redux

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

Immutable js UpdateIn is storing number instead of object

I am trying to update a immutablejs object;
//action.vals = {element: "p", type: "text", content: "test", className: "paragraph-topic"}
return state
.updateIn(['contents'], list => list.push(action.vals)) //<<<<<THis is failing
.set('loading', false)
.set('error', false)
break;`
But instead it is storing 10 an integer .
I am really confused and i am in need of help suggestion.
Here is my full code
Any help is much appreciated! I am going nuts due to lack of proper usage documentation
I've noticed that on the LOAD_DATA reducer, you're setting contents to become an array instead of an immutable List:
case LOAD_DATA:
return state
.set('loading', true)
.set('error', false)
.setIn(['contents'], [])
This would cause quite a few problems. What's happening is that you're using the vanilla JS push function on your update, and that returns the length of the array. So I'm supposing contents has 10 elements?
You just need to change the LOAD_DATA reducer to have this instead:
.setIn(['contents'], fromJS([]))
or
.setIn(['contents'], new List())
If using List(), that has to be explicitly imported as well!
Do some thing like this using ....
.updateIn(['contents'], list => [...list, action.vals])

Append/push in seamless immutable js - react js

In Reducer for initial state I am using a seamless-immutable.
export const INITIAL_STATE = Immutable({
foo:[],
})
function orderReducer(state = initialState, action) {
console.log('reducer is runnig');
switch (action.type) {
case GET_MENU: {
return state.foo.push("newData") // how to achive this <----
}
default:
return state;
}
}
How do I to push new data into the foo?
If you want to stick with ImmutableJS you can use new List() that has a push method. Here are the docs. There are a bunch of data sets with different APIs that always return immutable objects. You will need to start learning those.
However if you are looking for a familiar way to deal with immutability I'd recommend you to switch to Immer. It let's you use plain javascript to return immutable objects, so you don't need to learn any new APIs. Take a look here:
https://github.com/mweststrate/immer
I personally always use plain javascript with the spread operator. I find it comfortable, fast, without any extra libraries. Very verbose when you get use to the syntax:
const pushedArr = [...oldArray, newElement]
const mergedObj = {...objA, ...objB}
Now that you know some alternatives, find the way that fits you and your team the most.

Immutable JS - How to replace a list with a new list

I have a React + Redux app which uses Immutability.js. In my reducer I have state defined as an Immutable List
const initialSuggestedResponseState = Immutable.List([]);
Every time a specific action happens, I would like to replace this list with a new list. I think I am missing something with Immutability.js as I have not been able to figure out a working solution. So far I have tried things like this:
state.clear();
action.messages.forEach(message => {
let newResponse = new suggestedResponseRecord({
body: message.body,
responseId: message.response_id,
type: message.type
});
state.push(newResponse);
});
return state;
This however does not push anything to my list. Why does this not work, and what is the appropriate methodology? I'm sure there is something MUCH simpler.
Thanks in advance :)
Doing state.push on any sort of immutable object returns a new object. The existing state is not modified, which is why returning the state after pushing to it doesn't work. A quick hack would be to change state.push(newResponse); to state = state.push(newResponse);. I suggest you read up more on how immutable structures work :)
As a follow-up to zackify's answer, here's one approach you could take to simplifying your code:
return new List(action.messages.map(message => {
return new suggestedResponseRecord({
body: message.body,
responseId: message.response_id,
type: message.type
});
});
This creates an array of records from your array of messages (using Array.prototype.map), then converts that new array into an immutable list. Not sure if this is the most efficient way of doing things, but it's quite readable.

Resources