Setting Nested Array in React Context - reactjs

I am trying to figure out how to update an array that is nested within a React Context. Below is the context I am working with. It consists of "lists". Each list contains an array of "items".
import React, {useState, createContext} from 'react';
export const ListerContext = createContext();
export const ListerProvider = (props) => {
const [lists, setLists] = useState([
{
id: 1,
items: [{
itemid: 1,
text: 'Hello'
},
{
itemid: 2,
text: 'world'
}]
},
{
id: 2,
items: [{
itemid: 2,
text: 'Test'
}]
}
]);
return(
<ListerContext.Provider value={[lists, setLists]}>
{ props.children }
</ListerContext.Provider>
);
}
I have been trying to change the nested arrays using the "setLists" method below but it is not working. What am I doing wrong here?
const removeListItem = (e) => {
setLists((prevList)=>{
for(var i = 0; i < prevList.length; i++){
if(prevList[i].id === parseInt(e.target.value[2])){
prevList[i].items = (prevList[i].items.filter(function(item) {
return item.itemid !== parseInt(e.target.value[0]);
}))
}
}
return prevList;
});
}

As #zero298 mentioned in their comment, you need to pass the entire list into your state update function setLists. You are initializing the state and its update function with a "list of dictionaries", so you need to keep treating it as such, you can't selectively change few properties inside the dictionary or few elements in the list without having to update the entire list.
Easy correction of your code will be to conditionally make a new copy of your list with updated values, and then setting that list as your new state using your state update function.
Cheerio!

Related

useState hook not "reacting" when state variable modified

I have created a React application to test out the useState hook.
This the variable of concern:
let [blocks, setBlocks] = useState([
{text: "Hello", id: 1},
{text: "This is google", id: 2},
{text: "Wassup", id: 3},
{text: "Last One", id: 4}
]);
I've displayed this using the map function as follows:
return (
<div className="App">
{blocks.map((block) => (
<div className='block-element'>{block.text}</div>
))}
<button onClick={clickFunc}>ClickToChange</button>
</div>
);
As far as I've understood, to make any change in the webpage we have to pass the new-data into setBlocks() and wherever "blocks" was used will be updated.
I tried the following clickFunc() to do so:
const clickFunc = ()=>{
blocks[1].text = "Go Home";
setBlocks(blocks);
console.log(blocks);
}
I expected the output (onclicking the button) to be:
Hello
Go Home
Wassup
Last One
But nothing changed.
Surprisingly when I used the following (similar looking) clickFunc():
const clickFunc = ()=>{
blocks = [
{ text: "Hello", id: 1 },
{ text: "Go Home", id: 2 },
{ text: "Wassup", id: 3 },
{ text: "Last One", id: 4 }
];
setBlocks(blocks);
console.log(blocks);
}
And it worked perfectly as expected.
On click output:
Hello
Go Home
Wassup
Last One
NEVER mutate state directly.
The following code block is mutation and wrong. Javascript is kind of like some other programming languages where only the reference of the object is stored in the memory. So when you mutate the object instead of creating a new one, you are not really changing the content of the object in memory, because it still has the same value(reference) as before.
const blocks = [{text: 'one'}];
blocks[0].text = 'two';
You should create a new object and assign it to state using React setState callback:
setBlocks(blocks => {
clonedBlocks = [ ...blocks];
clonedBlocks[1]= { ...blocks[1], text: "Go Home" };
return clonedBlocks;
});
Or any other way that does not mutate the state directly.
React state is immutable
if you mutate the state react will not know that state is changed
first you need to copy the state using Spread syntax and change it
const clickFunc = () => {
setBlocks(prevBlocks => {
const shallowCopy = [...prevBlocks]
shallowCopy[1].text = "Go Home";
return shallowCopy
});
}

How to initiate state from another state (by copying without referencing)

I have a code as below
useEffect(() => {
{
updateLocalColumns();
}
}, []);
const updateLocalColumns = () => {
let templocalColumns = [...localColumns];
templocalColumns[0]..defaultSortOrder = "descend";
setlocalColumns(templocalColumns);
}
const orderContext = useContext(OrderContext);
const { data, columns } = orderContext;
const [localColumns, setlocalColumns] = useState([...columns]);
return (
<div>
<Table columns={columns} dataSource={data} />
{console.log(columns)}
</div>
)
'columns' is a state that is obtained by using hooks, I am trying to make a copy over local component that is 'localColumns" and thus modify from there without interfering the global 'columns'
I was expecting the original columns to remain as its original state, however when I print out, the original columns is the same as 'localColumns' which is the modified one.
Expected Output
localColumns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
columns: [
{
title: "Username",
dataIndex: "username",
key: "username",
}
]
Current Wrong Output
localColumns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
columns: [
{
title: "Username",
dataIndex: "username",
key: "username",
defaultSortOrder: "descend"
}
]
I am suspecting the problem is when I initate the state, I did a referencing.
The way I did copy was from this link and this link
I would try a simpler code:
//Import this class in the proper place
//import { clone } from 'ramda';
const orderContext = useContext(OrderContext);
const { data, columns } = orderContext;
let localColumns0 = clone(columns) //This is done to make a deep copy of "columns"
localColumns0[id]['defaultSortOrder']="descend"
const [localColumns, setlocalColumns] = useState(localColumns0);
return (
<div>
<Table columns={columns} dataSource={data} />
{console.log(columns)}
</div>
)
If you don't want only the copy of the state, maybe you need to look how to do the deep copy of an array, after you make a deep copy of the estate you can pass the state to your personal component with <PersonalCom {...deepCopyStateObj}/> and the PersonalCom need a constructor where you receive the props, like the following code.
constructor(props) {
super(props);
}
Or something equivalent in JSX syntax.
However make a deep copy of the state and pass the object to component don't look like, because you need to clone the state each time that your app refresh the UI, and react refresh very frequently the UI, for this reason, I suggest cloning the propriety that you need inside the personal component.

React - setting state with imported data keeps getting updated

I am importing an array of data like
import { menuData } from '../../data/menu-data';
data being:
export const menuData = [
{ id: 'all', title: 'Select all', icon: '', selected: false },
{ id: 'overview', title: 'Overview', icon: 'user-check', selected: false },
{
id: 'about',
title: 'About',
icon: 'info-circle',
selected: false,
partSelected: false,
}
];
I then initialise some state in my parent component like:
const [downloadMenu, setMenuState] = useState([...menuData]);
I pass "downloadMenu" state to my child compionent and absorb it as a prop and then create a copy of it which gets mutated i.e.
const updatedMenu = [...downloadMenu];
i then call this function which is in the parent and is passed down as a prop to the child component
onMenuChange(updatedMenu);
const handleMenuChange = (menuState) => {
setMenuState(menuState)
}
This works but when i try and reset the state to the initial menuData it doesnt work. MenuData is getting mutated aswell. Why is this?
I am calling this again - setMenuState([...menuData]);
but menuData is the same as the downloadMenu state.
Using ... only creates a shallow copy - a new list is created, but the items inside it remain the same. The objects in the list aren't copied, but remain as a reference to the original objects in menuData.
The solution is either to create a new object when you change menuData or create a deep copy.
A deep copy can be created using JSON - JSON.parse(JSON.stringify(menuData)) or a library like lodash - _.cloneDeep(menuData).

how to setState an object of an object in react

I'm hoping someone can give me some syntax/explanation help here i'm trying to call setState on an object nested in an object (data) in my state i'm a little stumped?
I'm not sure how to actually push my object onto the specified array in the setState function?
Can someone help me out? Many thanks!
here is the state i'm working with:
state={
height: 50,
parentCount: 1,
data:
{
parentId: 0,
name: 'parent',
children: [{name: 'Child One', distToWater: 0, children: [] }, {name: 'Child Two', distToWater: 0, children: [] }]
},
}
Here's my function where I try to add a child to my children [] array that's nested inside my data object in state:
addChild = () =>{
for (let x in data.children ){
for (child in x){
let closest = 99999
if(child.distToWater < closest){
closest = child.distToWater
var newBest = child
let newChild = {
name: 'child',
distToWater: closest - 1,
children: []
}
}
this.setState({data.children[newBest]: [...newChild] }) //use setState to add a child object
}
}
}
Since it is nested deep inside, something like the following code snippet should do.
const kid = { name: 'John', distToWater: 0, children: [] } // new object to add
const newChildren = [...this.state.data.children, kid]
const newData = { ...this.state.data, children: newChildren }
const newState = { ...this.state, data: newData }
this.setState(newState)
The above code snippet uses the spread operator. In case if you have not seen it before, it is a new JavaScript operator which would come very handy. (MDN link)
I am not sure why you had added the comment //use setState to add a child when the code snippet does not use hooks. I think hooks, or any other state management tool would be beneficial if the state object is so deeply nested.
You can try something like this
this.setState(prevState => ({
...prevState,
data: {
...prevState.data,
children: [...prevState.data.children, newBest]
}
}))

state is not being updated when using React Hooks

I am currently playing around with the new React Hooks feature, and I have run into an issue where the state of a functional component is not being updated even though I believe I am doing everything correctly, another pair of eyes on this test app would be much appreciated.
import React, { useState, useEffect } from 'react';
const TodoList = () => {
let updatedList = useTodoListState({
title: "task3", completed: false
});
const renderList = () => {
return (
<div>
{
updatedList.map((item) => {
<React.Fragment key={item.title}>
<p>{item.title}</p>
</React.Fragment>
})
}
</div>
)
}
return renderList();
}
function useTodoListState(state) {
const [list, updateList] = useState([
{ title: "task1", completed: false },
{ title: "task2", completed: false }
]);
useEffect(() => {
updateList([...list, state]);
})
return list;
}
export default TodoList;
updateList in useTodoListState's useEffect function should be updating the list variable to hold three pieces of data, however, that is not the case.
You have a few problems here:
In renderList you are misusing React.Fragment. It should be used to wrap multiple DOM nodes so that the component only returns a single node. You are wrapping individual paragraph elements each in their own Fragment.
Something needs to be returned on each iteration of a map. This means you need to use the return keyword. (See this question for more about arrow function syntax.)
Your code will update infinitely because useEffect is updating its own hook's state. To remedy this, you need to include an array as a second argument in useEffect that will tell React to only use that effect if the given array changes.
Here's what it should all look like (with some reformatting):
import React, { useState, useEffect } from 'react';
const TodoList = () => {
let updatedList = useTodoListState({
title: "task3", completed: false
});
return (
<React.Fragment> // #1: use React.Fragment to wrap multiple nodes
{
updatedList.map((item) => {
return <p key={item.title}>{item.title}</p> // #2: return keyword inside map with braces
})
}
</React.Fragment>
)
}
function useTodoListState(state) {
const [list, updateList] = useState([
{ title: "task1", completed: false },
{ title: "task2", completed: false }
]);
useEffect(() => {
updateList([...list, state]);
}, [...list]) // #3: include a second argument to limit the effect
return list;
}
export default TodoList;
Edit:
Although I have tested that the above code works, it would ultimately be best to rework the structure to remove updateList from useEffect or implement another variable to control updating.

Resources