Immutable js UpdateIn is storing number instead of object - reactjs

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

Related

react facing issues to find out specific data from an array of objects

I have a few collections of data that are in JSON format and want to access specific data conditional data, an example I want to access which one has teacher:true and put it in a state.
I have tried it with conditions inside the loop and filter function.
[
0:{name:"Rou",id:"121"}
1:{name:"Sou",id:"122",teacher:"true"}
2:{name:"Tou",id:"123"}
3:{name:"Vou",id:"124",teacher:"true"}
4:{name:"Kou",id:"125",teacher:"false"}
5:{name:"Wou",id:"126"}
]
here I want to get only
1:{name:"Sou",id:"122",teacher:"true"}
3:{name:"Vou",id:"124",teacher:"true"}
which means only where the teacher is true.
This has nothing to do with react. It's a javascript array problem.
let people = [
{name:"Rou",id:"121"},
{name:"Sou",id:"122",teacher:"true"},
{name:"Tou",id:"123"},
{name:"Vou",id:"124",teacher:"true"},
{name:"Kou",id:"125",teacher:"false"},
{name:"Wou",id:"126"}
];
let teachers = people.filter((person) => person.teacher == "true");
Note that you need it's strongly recommended to use a boolean type instead of a string:
// Strongly discouraged
{name:"Sou",id:"122",teacher:"true"};
// Better
{name:"Sou",id:"122",teacher: true};

Cannot assign to read only property of Object in TypeScript

can anyone explain to me please why and how this might happen:
I have a typescript app with Zustand state management.
Somewhere during the app I am updating certain elements by extracting them from the state and cloning via simple Object.Assign :
let elemToUpdate = Object.assign({},story?.content?.elementsData[nodeId]);
console.log(elemToUpdate);
if(elemToUpdate) {
if(elemToUpdate.title) elemToUpdate.title[editorLang] = newName;
else elemToUpdate.title = {[editorLang]:newName} as TextDictionary;
updateElement(nodeId,elemToUpdate);
}
Now the interesting part is on my first try the update goes through without fail, but the next object I am trying to update fails with the following message:
Tree.tsx:39 Uncaught TypeError: Cannot assign to read only property 'en' of object '#<Object>'
I can't understand WHY the first one comes through, but the second gets blocked.
(I know HOW to fix it, need to do deep clone, I just want to understand WHY)
Thanks
First, let's start from why some objects in your code are readonly. Based on what you described in the question, you use a Zustand state manager. Such managers traditionally wraps you stored data to readonly objects to prevent it's manual mutation (expecting, you will change the state only via built-in mechanisms), to guarantee data stability. So, if the story?.content?.elementsData[nodeId] is the Zustand state object, it self and all it's nested objects are converted to readonly.
Second, let's define, which objects will be blocked. I see at least two objects here: elemToUpdate: { ..., title: { [lang]: string }} (elemToUpdate and it's title). Both will be converted to readonly.
Third, you use Object.assign({}, ...) which creates a new object (new reference) and clones all properties of the source object. It happens only for first level of properties, no deep clone. So, as the title is a reference to another object, it will be cloned as is and in the new object it still leads to the existing { [lang]: string } object. There are several way to solve that: 1) deep clone as you mentioned; 2) manually clone title property, for instance {..., title: { ... elemToUpdate.title }} or via Object.assign
But I would suggest don't mutate you object this way. Probably, your entire algorithm has some architectural issues in general.
That is expected because in the first case you are not assigning value to the title you are only changing the value of the title property. In the second case, you are reassigning the value of the title property,
it's the read-only value you cant change it. Let's understand with a simple example
Javascript: Only for example not related to problem
const user = {
name: 'John',
}
user.name = "Pete"; // This works
const user = {
name: 'John',
}
user = { name: 'Pete'} // This doesn't work
Typescript:
const user: Readonly<{
a: {
name: string
}
}> = {
a:{ name: 'John',}
}
user.a.name = "Pete"; // This works
user.a = { name: 'John',} // not work
The same is happening there, Typescript does not check deep Readonly prop. check here

React.js: How to properly append multiple items to an array state using its use-state hook setter?

My component has a state that should store an array of objects. I initialized it to be empty.
const [tableDataSource, setTableDataSource] = useState([])
After a couple successful fetching and reorganizing of fetch results, one or more object literals should be appended to the tableDataSource state. Desired output should be
[
{
key: 1,
key2: 'sample string value',
key3: false,
},
{
key: 'sample string value',
key2: true,
}
]
My current fetch result is another array containing 2 object literals. I tried to append all items from this array to the state setter.
let dataSource = generateRows(showCompletedItems, fetchResult);
The code above reorganizes the fetch results into desired output. I logged dataSource in the console and verified if it is an Array object and it returned true.
Somewhere inside the component declaration I also have a useEffect that logs the current tableDataSource to output the changes.
console.log('TABLE DATA SOURCE >>>>', tableDataSource, typeof tableDataSource);
I have a difficult time appending the array items into the state array using its setter. I tried Approach # 1:
setTableDataSource(dataSource);
The console does not log anything.
Approach # 2:
setTableDataSource((tableDataSource) => [...tableDataSource, dataSource])
Returns a nested array as shown below:
[[{...},{...}]]
If I used Approach # 2 and initialized the component state like this:
const [tableDataSource, setTableDataSource] = useState([{}])
Result:
[{}, [{...}, {...}]]
Approach # 3 does not log anything like the first one
setTableDataSource((tableDataSource) => [...tableDataSource, ...dataSource])
Figured it out.
setArrayOfObjects((currentObjects) => currentObjects.concat([ ...anotherArrayOfObjects]))
I hope this very specific problem will help others. It was very annoying, it took me hours to figure it out.
You can use Set.
setTableDataSource((tableDataSource) => [... new Set([...tableDataSource, ...dataSource])])
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set

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.

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