Update state of nested array with React Hooks - reactjs

I have been working with React Hooks for a while, but the biggest problem for me is working with arrays.
How to update value of nested array of objects? I want to change dropdown type.
My code is below and changes the state directly. How can I fix this? What is the most correct code?
Thanks for your help.
const [grups, setGrups] = useState([
{
id: 1,
css: "col",
components: [
{ id: 1, type: "textbox" },
{ id: 2, type: "integer" },
],
},
{
id: 2,
css: "col",
components: [
{ id: 1, type: "numerictextbox" },
**{ id: 2, type: "dropdown" },**
],
},
]);
function handleClick(gindex,cindex) {
const newgrups = [...grups];
newgrups[gindex] = {...grups[gindex] ,components: [...grups[gindex].components]};
newgrups[gindex].components[cindex].tip="datetime";
setGrups(newgrups);
}

So you need something like
function handleClick(gindex, cindex) {
// use map to create a new array and at the same time
// modify the contents when required
const newgrups = grups.map((grup, gidx) => {
// if the index is not the one we want return the while grup as is
if (gidx !== gindex) return grup;
// otherwise create a new one by spreading the existing
return {
...grup,
// and override the prop which is changed
components: grup.components.map((component, cidx) => {
// again if the component is not the one we want to update
// return it as is
if (cidx !== cindex) return component;
// otherwise create a new one by spreading the existing
// and adding/modifying the props we want
return {
...component,
tip: 'datetime'
}
})
}
});
setGrups(newgrups);
}
If you just want to go with the code you have, you just need to create a new component as well
newgrups[gindex].components[cindex] = { ...newgrups[gindex].components[cindex],
tip: 'datetime'
}

It is good practice to design your state as you would design a database. In a database normalised design is recommended because you can't just nest states in other states. In your case you could split the state in the same way:
grups
[
{
id: 1,
css: "col"
},
{
id: 2,
css: "col"
}
];
components
[
{ grups_id: 1, id: 1, type: "textbox" },
{ grups_id: 1, id: 2, type: "integer" },
{ grups_id: 2, id: 1, type: "numerictextbox" },
{ grups_id: 2, id: 2, type: "dropdown" }
]
This makes it significantly easier to handle the state and avoid nesting and complex procedues. You can easily work with components by applying componenents.filter() and filter by grups_id.

Related

Update nested React state?

I'm trying to update part of a state object that is nested. This is the object:
const [buttonObject, setButtonObject] = useState({
intro: [
{
id: '123',
name: 'first_intro_name',
selected: false,
},
{
id: '124',
name: 'second_intro_name',
selected: false,
},
],
experience: [
{
id: '789',
name: 'first_experience_name',
selected: false,
},
{
id: '8910',
name: 'second_experience_name',
selected: false,
},
],
});
When a button is clicked I want to toggle the selected state. I'm using a click handler that looks like this:
const handleButtonClick = ({ id, selected }) => {
if (id === '123') {
buttonsObject.intro.map(
pref => (pref.selected = pref.id === id ? !pref.selected : selected)
);
setButtonsObject(buttonsObject);
} else if (id === '124') {
buttonsObject.intro.map(
pref => (pref.selected = pref.id === id ? !pref.selected : selected)
);
setButtonsObject(buttonsObject);
}
};
It would handle experiences as well. The issue is that right now it seems like rather than updating the object it just overwrites the object or creates a new one. It also doesnt pass that information back down to the component even though I have it routed correctly so it should.
Is there better/correct syntax for updating nested state like this?
Thanks.
instead of checking again with if condition use higher order array function and spread operator to get optimal solution.
setButtonObject(previous => {
return {
...previous,
info: previous.info.map(item => item.id === id ? {
...item,
selected: true
} ? item)
}
})

need to implement Undo function using react hooks

need to implement undo function in a state array data. i remove the data by id using filter method but i need to restore the data after click on undo button. how to implement in React Hooks?
const [dataSource, setDatasource] = useState([
{
id: 1,
name: 'john',
gender: 'm'
},
{
id: 2,
name: 'mary',
gender: 'f'
},
{
id: 3,
name: 'loki',
gender: 'M'
},
{
id: 4,
name: 'Thor',
gender: 'M'
},
]);
You will have to keep track of all the actions that the user performs, and calculate the end result from that. When a user clicks "undo", you can just remove the latest action.
const actions = [
{
operation: "create",
name: "Some name",
index: 1
},
{
operation: "delete",
index: 1
}
];
const state = (() => {
let appliedState = [];
for (const action of actions) {
if (action.operation === "create") {
appliedState .push(action.name);
} else if (action.operation === "delete") {
appliedState = appliedState .filter(currentState => currentState.index !== action.index);
}
}
return appliedState ;
})();
You can see a working react example here.

React setState to array element

I want to update list with React.
List I have:
export const arrList = [
{ title: '', elements: [] },
{ title: 'a', elements: ['aaa', ] },
{ title: 'b', elements: ['bbb', ] },
];
const [poreikiai, setPoreikiai] = useState(arrList);
And now, I want to append to first array element: elements { title: '', elements: [] }
I something like this:
{ title: '', elements: ["firstElement"] }
how can I do it?
I understant that I need to use setPoreikiai but I cant figure out sintax;
Something like:
setPoreikiai(prevState => {
return {...prevState, ...newValue};
});```
Deeply clone your way into the data structure and add the new item at the end:
setPoreikiai(prevState => {
const prevClone = [...prevState];
prevClone[0] = {
...prevClone[0],
elements: [...prevClone[0].elements, "secondItem"]
};
return prevClone;
});
It's important that you create copies all the way down to the desired level of state so that you do not accidently mutate any values by reference. Any directly mutated data can become out of sync with other effects and memoised components which might accept them as dependencies.

How can I update a nested state with arrays in React?

I have a nested array, and I am trying to update some properties, but i don't know the syntax in react to do that.
this.state = {
databasesList: {
name: 'Databases',
toggled: true,
children: [
{
name: 'OneDatabase',
children: [
{ name: 'collection1' },
{ name: 'collection2' }
]
}
]
}
}
I am trying to update with this, but it does not work:
this.setState({ this.state.databasesList.children[0].children: newData })
To set nested state in React you should use JS spread operator so in your example it should be something like this:
this.setState((prevState) => ({
...prevState,
databasesList: {
...prevState.databasesList,
children: {
...prevState.databasesList.children[0],
children: {
newData,
},
},
},
}));
If you want to use lodash set here's a pretty nifty solution.
https://twitter.com/stwilz/status/1092958989208317952
But if you just want to use set on it's own this would be the way to go.
const yourFunction = newData => this.setState(state => set(state, ['databasesList', 'children', 0, 'children'], newData));
The spread operator works fine as well. It just gets super verbose when you have a huge state object.

redux reselect selectors for relational data

selectors takes 2 arguments, state and props, but how can we handle selectors for relational data ?
initialState = {
department :{ids:[1],byId:{name:'dep 1',id:1,parent:null}}
sections :{ids:[2],byId:{name:'section 1.1',id:2,parent:1}}
subsections :{ids:[3],byId:{name:'subsection 1.1.1',id:3,parent:2}}
}
here department is aparent of n(sections) and grandparent of n(subsections)
in <Department id="1" /> i want to select department 1 and all its children.
how can i write such selector without passing the id of the department i need to the selector ?
You might use a router (react-router) to pass such keys by url segments, then you get access to the ids you need, ie:
mapStateToProps(state, ownProps) {
// then get access to id here
console.log(ownProps.params.department_id);
}
Here you'll find an implementation that - i hope - will answer to your first question : "how can we handle selectors for relational data ?"
import { createSelector } from "reselect";
const state = {
departments: [{ id: 1, byId: { name: "dep 1", id: 1, parent: null } }],
sections: [{ id: 2, byId: { name: "section 1.1", id: 2, parent: 1 } }],
subsections: [
{
id: 3,
byId: { name: "subsection 1.1.1", id: 3, parent: 2 }
}
]
};
const clone = obj => JSON.parse(JSON.stringify(obj));
const bottomUp = (...levels) =>
levels.reduce((children, parents) => {
const p = clone(parents);
const addToParent = child => {
const parent = p.find(par => par.id === child.byId.parent);
if (parent) {
if (parent.children) {
parent.children.push(child);
} else {
parent.children = [child];
}
}
}
children.forEach(addToParent);
return p;
});
const selectSubs = state => state.subsections;
const selectSecs = state => state.sections;
const selectDeps = state => state.departments;
const selectHierarchy = createSelector(
selectSubs,
selectSecs,
selectDeps,
bottomUp
);
console.log(selectHierarchy(state));
This new selector will return :
As you can see, since I didn't fully understand your store structure, i performed few changes in it : each domain became an array and each id became a number.
I hope it will help ...
Cheers

Resources