Update a nested value in a store - reactjs

I have my state with the following structure:
{ messages:
[
{ id: 1, comments: []}
]
}
And I would like to add a new comment in my message, I have the message id so I can easily create a new state, loop over the messages, and add the new comment, but it doesn't seem to be the right way...
Thank you for your help.

Try this:
var commentToAdd = { id: 1, comment: "text" };
this.setState({ messages: [...this.state.messages.map(i => i.id === commentToAdd.id ? { id: i.id, comments: [...i.comments, commentToAdd.comment] } : i) ] });

The best practice is to keep state in the normalized shape, so instead of arrays, you would have objects indexed by id, and instead of storing comments inside messages you would store only ids.
For your example the state would look like this:
{
messages: {
byId: {
'1': { id: '1', comments: ['comment1'] },
...
},
allIds: ['1', ...]
},
comments: {
byId: {
'comment1': { id: 'comment1', ... },
...
},
allIds: ['comment1', ...]
}
}
In such structure in order to add a new comment you will have to update comments part and add a new id in messages[id].comments... but it's now much easier to update single comment.
For normalizing objects to such form I recommend normalizr.
More information about normalizing state shape in redux documentation Normalizing State Shape

Related

React Double array statement UI

I want to show the comment array in the UI using the regid and the comment map. What should I do. Help
I have an array inside an array, so I don't know how to make it visible in the UI.
In case you have array inside an array, you will need two loops or two map statements to ensure that the same is returned. Below I have tried to take a similar object as yours to show how it should look like
// assuming you have below object
const obj = {
product: {
Comments: [
{
User: { regId: 1, content: "Comment 1 Content" }
},
{
User: { regId: 2, content: "Comment 2 Content" }
}
]
}
}
const renderComments = obj.product.Comments.map(({ User: { regId, content }) => <label>{regId}: {content});

Optimize updating redux state

I have a web application that received an update from websocket, like 100 messages per second.
I am using immutable helper and tried this
const parentIndex = action.payload.data.findIndex( i => i.id===action.id)
if(parentIndex !== -1) {
const childIndex = action.payload.data[parentIndex].child.findIndex(c=>i.id===action.childId)
if(child !== -1) {
const lastChildIndex = action.payload.data[parentIndex].child[childIndex].lastChild.findIndex(l=>l.id===action.lastChildId)
return lastChildIndex=== -1
? update(state, { // insert
data: {
[parentIndex]: {
child: {
[childIndex]: {
lastChild: {
$push: [{
parentId: action.id,
childId: action.childId,
lastChildId: action.lastChildId,
price: action.payload.price
}]
}
}
}
}
}
})
: update(state, { // update
data: {
[parentIndex]: {
child: {
[childIndex]: {
lastChild: {
[lastChildIndex]:{
price: { $set: action.payload.price},
isUpdated: { $set: true}
}
}
}
}
}
}
})
}
}
Example Data:
data = [
{
parentId: 123,
itemName: 'John Doe',
child: {
childId: 456,
childName: 'I am child one',
lastChild: {
lastChildId: 789,
price: 143
}
}
},
{
parentId: 321,
itemName: 'John Wick',
child: {
childId: 654,
childName: 'I am child wick',
lastChild: {
lastChildId: 987,
price: 44
}
}
}
]
This seems to be work with 5 array of data at least but when the data is more than 15, the browser seem to be slow, memory leak and soon crashed..
Finding an index everytime there is a message that being pushed to the app
will kill the browser.
I am using redux-thunk as middleware.
If you can recommend me something that will update/insert faster, better way and seamless. That would be super cool.
First address this:
a web application that received an update from websocket, like 100 messages per second
You should throttle or debounce these so you are not updating the state for every message. Or reduce the amount of messages if you can. Or both.
Once you get this out of the way, the app should probably work fine. But you could still make some improvements:
Given this action:
{
payload: {
parentId: 123,
childId: 321,
lastChildId: 555,
price: 50
}
}
Your reducer will look like this:
const { parentId, childId, lastChildId } = action.payload;
const childItem = state[parentId][childId][lastChildId];
const newState = {...state}
newState[parentId][childId][lastChildId] = {...childItem, ...action.payload};
return newState;
If I know I will need to be finding the specific item like you are doing here, I choose a map instead of an array.
How about ditching arrays and making a store like this.
{
id: {
childId:{...data,etc..},
childId2:{},
...
},
id2: {...},
...
}
You can access ids with store.data[id].child[index]
In short that would be
if(store.data[parentIndex]&&store.data[parentIndex][childIndex]){
!!!
}else{
:(((
}
Three observations:
You probably don't need to be updating the UI 100 times per second. You should throttle or batch the websocket updates so that there's fewer updates to the state, and thus fewer updates to the UI.
Rather than using immutable-helper for immutable updates, I'd encourage you to use Immer. It's much simpler and easier to use. Even better, use our new Redux Starter Kit package, which has Immer built-in.
as #Aleks said, you should try to normalize your state shape so you aren't dealing with as much nested data.

Is there any way to update state by find Item and replace on a nested state?

I am building an order functionality of my modules in the component state on react
so the state object looks like that
"activity": {
"name": "rewwerwer",
"description": "werwerwerwerwer",
"modules": [
{
"name": "Text",
"order": 1,
"module_id": 1612,
},
{
"name": "Text2",
"order" 2,
"module_id": 1592,
}
]
}
handleSortUp = (moduleid ,newOrder) => {
const { modules } = this.state.activity;
const module = modules.find(element => element.module_id === moduleid);//Thios returns the correct object
this.setState({ activity: { ...this.state.activity.modules.find(element => element.module_id === moduleid), order: newOrder } });
}
I tried this but it updates the order field and object
but also removes all other objects from modules array :<
I like just to replace only the order field on each module by module id
and leave rest data there
the required response from the state that i need when the handleSortUp(1612,14); is fired
handleSortUp(1612,2);
{
"name": "rewwerwer",
"description": "werwerwerwerwer",
"modules": [
{
"name": "Text",
"order": 2,
"module_id": 1612,
},
{
"name": "Text2",
"order": 1,
"module_id": 1592,
}
]
}
I can do this on a simple array the question is how to update the State on react
Also the one way to change the order is answered fine but how also to change the field that had that order registered
So when we fire Change Item 1 order to 2 the Item 2 needs to take the Order 1
Thank you
Sure! This would be a great place to use the built-in .map method available for arrays. Consider the following example.
let array = [{order: 1, type: "food"}, {order: 2, type: "notfood"} ]
const newArray = array.map((item) => {
//CHECK TO SEE IF THIS IS THE ITEM WE WANT TO UPDATE
if(item.order == 1){
return {
...item,
newField: "newData"
}
} else {
return item
}
})
Output is:
[{order: 1, type: "food", newField: "newData"}
{order: 2, type: "notfood"}]
So yes you could totally update the module you're looking for without mutating the rest of your array. Then use your findings to update the component state using some good ol spread.
this.setState({
activity: {
...this.state.activity,
modules: newArray}
})
Of course they get all eliminated. Pay attention to what you wrote here:
this.setState({ activity: { ...this.state.activity.modules.find(element => element.module_id === moduleid), order: newOrder } });
What are you doing with that find? Let's see what Array.prototype.find() returns: https://developer.mozilla.org/it/docs/Web/JavaScript/Reference/Global_Objects/Array/find
It returns an index, why would you insert an index into the state?
The answer partially came from yourfavoritedev.
As he said you can use the built-in Array.prototype.map() and do it like this:
handleSortUp = (moduleid ,newOrder) => {
const { modules } = this.state.activity;
const newModules = modules.map(module => module.module_id === moduleid ? { ...module, order: newOrder } : module)
this.setState({ activity: { ...this.state.activity, modules: newModules } });
}
This should work, let me know but I strongly advice to ask me or search on the web if you don't understand what is happening there (syntactically or semantically speaking).

Multidimensional Arrays, Vuex & Mutations

I'm attempting to both add and remove items in a multidimensional array stored in Vuex.
The array is a group of categories, and each category and have a sub-category (infinity, not simply a two dimensional array).
Example data set is something like this:
[
{
id: 123,
name: 'technology',
parent_id: null,
children: [
id: 456,
name: 'languages',
parent_id: 123,
children: [
{
id:789,
name: 'javascript',
parent_id: 456
}, {
id:987,
name: 'php',
parent_id: 456
}
]
}, {
id: 333,
name: 'frameworks',
parent_id 123,
children: [
{
id:777,
name: 'quasar',
parent_id: 333
}
]
}
]
}
]
....my question is, how do I best add and remove elements to this array, which is inside of a Vuex Store?
I normally manipulate simple arrays inside the Vuex Store using Vue.Set() to get reactivity. However, because I'm not sure how deep the nested array being manipulated is - I simply can't figure it out.
Here's an example of how I thought I could add a sub-category element using recursion:
export const append = (state, item) => {
if (item.parent_uid !== null) {
var categories = []
state.data.filter(function f (o) {
if (o.uid === item.parent_uid) {
console.log('found it')
o.push(item)
return o
}
if (o.children) {
return (o.children = o.children.filter(f)).length
}
})
} else {
state.data.push(item)
}
}
The first thing to understand is that vuex, or any other state management library based on flux architecture, isn't designed to handle nested object graph, let alone arbitrary/infinity nested objects that you mentioned. To make the matter worse, even with shallow state object, vuex works best when you define the shape of the state (all desired fields) upfront.
IMHO, there are two possible approaches you can take
1. Normalize your data
This is an approach recommended by vue.js team member [here][2].
If you really want to retain information about the hierarchical structure after normalization, you can use flat in conjunction with a transformation function to flatten your nested object by name to something like this:
const store = new Vuex.Store({
...
state: {
data: {
'technology': { id: 123, name: 'technology', parent_id: null },
'technology.languages': { id: 456, name: 'languages', parent_id: 123 },
'technology.languages.javascript': { id: 789, name: 'javascript', parent_id: 456 },
'technology.languages.php': { id: 987, name: 'php', parent_id: 456 },
'technology.frameworks': { id: 333, name: 'frameworks', parent_id: 123 },
'technology.frameworks.quasar': { id: 777, name: 'quasar', parent_id: 333 },
}
},
});
Then you can use Vue.set() on each item in state.data as usual.
2. Make a totally new state object on modification
This is the second approach mentioned in vuex's documentation:
When adding new properties to an Object, you should either:
Use Vue.set(obj, 'newProp', 123), or
Replace that Object with a fresh one
...
You can easily achieve this with another library: object-path-immutable. For example, suppose you want to add new category under languages, you can create a mutation like this:
const store = new Vuex.Store({
mutations: {
addCategory(state, { name, id, parent_id }) {
state.data = immutable.push(state.data, '0.children.0.children', { id, name, parent_id });
},
},
...
});
By reassigning state.data to a new object each time a modification is made, vuex reactivity system will be properly informed of changes you made to state.data. This approach is desirable if you don't want to normalize/denormalize your data.

Normalizr - How to handle nested entities that are already normalized

I have entities in very nested JSON that already follow the normalizr format where the idAttribute is already the key where the object is defined:
groups: [{
id: 'foo',
families: {
smiths: {
people: [{
id: 'sam',
}, {
id: 'jake',
}],
},
jones: {
people: [{
id: 'john',
}, {
id: 'sue',
}],
},
},
}];
In this example, notice that the families attribute is using the id (smiths, jones) to identify the people who are an array of objects with ids.
The schemas for this might look like:
const person = new Entity('person');
const family = new Entity('family', {
people: [person],
}, {idAttribute: ???});
const group = new Entity('family', {
family: [family],
});
QUESTION: Is there a way to specify that a schema's idAttribute is the key where it is defined? In other words, how would I define the schema for Family as it's related to groups and people?
Another question, is there a way to denormalize a flattened state so that the families families: {[id]: obj} pattern stays the same as it is in the example json above?
Is there a way to specify that a schema's idAttribute is the key where it is defined?
Yes. The idAttribute function takes 3 arguments: value, parent, and key. Please read the docs. In your case, you can use the key, along with schema.Values
const family = new schema.Entity('families', {
people: [ person ]
}, (value, parent, key) => key);
const families = new schema.Values(family);
const group = new schema.Entity('groups', {
families
});
For denormalize, you'll need a separate schema for family, since the ID can't be derived from the key.

Resources