Recursive function in Reactjs Hooks? - reactjs

I want to update the state using react Hooks useState(); ?
Here is an example :
I have global state on top of the app:
const [familyTree, setFamilyTree] = useState([
{
fam_id: 1,
name: "No name",
attributes: {
"Key1": "*",
"Key2": "*",
},
children: [
{
fam_id: 2,
name: "No Name2",
attributes: {
"Key1": "*",
"Key2": "*",
},
},
],
},
]);
I have a current object to update the global state:
let res = {
fam_id: 2,
name: "No Name2",
attributes: {
"Key1": "Update this",
"Key2": "*",
},
},
Recursive function in this case helps me to update global state with matched ID, but I have problem now,
const matchAndUpdate = (updater, target) => {
if (updater.fam_id === target.fam_id) {
target.name = updater.name;
target.attributes = updater.attributes;
}
if ("children" in target && Array.isArray(target.children)) {
target.children.forEach((child) => {
matchAndUpdate(updater, child);
});
}
};
familyTree.forEach((g) => {
matchAndUpdate(res, g);
setFamilyTree({ ...g }); // here is my try, this works on start, but on secound update i got error about forEach is not a function...
});
I don't know where to update state on correct way?
Thanks, o/

Because you update state inside of forEach().
Maybe you should use .map and update state then at the end of check array.
This is the solution:
const matchAndUpdate = (updater, children) => {
return children.map(_child => {
if (updater.fam_id === _child.fam_id) {
return {
...updater,
children: _child.children && Array.isArray(_child.children) ? matchAndUpdate(updater, _child.children) : null
};
} else {
return {..._child,children: _child.children && Array.isArray(_child.children) ? matchAndUpdate(updater,_child.children) : null};
}
});
};
This will return and array of children, so you will begin from the initial array:
const finalFamily = matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
finalFamily will be the final updated array.
You can update the state like this:
// Option 1:
setFamilyTree(matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
// Option 2:
const newFamilyTree = matchAndUpdate({ fam_id: 1, name: "Name" }, familyTree);
setFamilyTree(newFamily);
--- NEXT QUESTION-- -
I understand that you want to create a method to push new children to child specified by id.
I developed a method that maintains attributes and old children:
const addChildrenToChild = (parent,numChildren) => {
const arrayChildren = [];
for (let i = 0; i < numChildren; i++) {
arrayChildren.push({
fam_id: Math.floor(Math.random() * 100),
name: "No name",
attributes: {
key1:"",
key2:""
},
});
}
return {...parent,children:parent.children && Array.isArray(parent.children) ? parent.children.concat(arrayChildren) : arrayChildren }
}
And upgrade matchAndUpdate to maintains old children
const matchAndUpdate = (updater, children) => {
return children.map(_child => {
if (updater.fam_id === _child.fam_id) {
return {
...updater,
children: updater.children
//Filter updater children
.filter(_childFiltered =>
_child.children && Array.isArray(_child.children) ?
//check if exists new child in old children
_child.children.some(
_childToCheck => _childToCheck.fam_id !== _childFiltered.fam_id
) : true
)
//concat old children and check to update
.concat(
_child.children && Array.isArray(_child.children)
? matchAndUpdate(updater, _child.children)
: []
)
};
} else {
return {
..._child,
children:
_child.children && Array.isArray(_child.children)
? matchAndUpdate(updater, _child.children)
: []
};
}
});
};
And now. You can use the other method at the same time to add new children:
// Now we are going to add new children to the first element in familyTree array, and maintains old children if it has.
const newFamilyTree = matchAndUpdate(
addChildrenToChild(familyTree[0], 10),
familyTree
);
setFamilyTree(newFamilyTree);

Related

Replacing value in nested state with react functional components

I am trying to use update state in a react function component but it is not working. I tried following a tutorial on pluralsite and apply it to my own project. Ideally this code should be finding the product based on the ID number and replacing the total with a new value.
Unfortunately I am getting an error saying that 'productData.find' is not a function and I'm not sure where the code being used for that is. Are there any suggestions on how to solve this issue?
This is what the data looks like. In this example I am saving the first element of the array.
export let data = [
{
name: "Name",
description:
"",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
...
];
function CompareCard({}) {
const index = 0;
const [productData, setProductData] = useState(data[index]);
function setTotalUpdate(id) {
const productPrevious = productData.find(function (rec) {
return rec.id === id;
});
const productUpdated = {
...productPrevious,
total: 1,
};
const productNew = productData.map(function (rec) {
return rec.id === id ? productUpdated : rec;
});
setProductData(productNew);
}
setTotalUpdate(1)
}
It's because productData is not an array so .find would not work. You want iterate over the products property in your data, so do productData.products.find(...)
When you do
const [productData, setProductData] = useState(data[index])
you don't pass an Array on your state but an Object (the first element of your data so an Object) and Object don't have find method.
Try
const [productData, setProductData] = useState([data[index]])
with [] on our useState to put your Object on array
///////////////////////////////// Edit /////////////
Ok, I try your code, and I propose you this.
import React, { useState } from "react";
const data = [
{
name: "Name",
description: "",
products: [
{
id: 1,
name: "Name 1",
material: 1.05,
time: 25,
total: 0,
},
{
id: 2,
name: "Name 2",
material: 3,
time: 252,
total: 0,
},
],
},
];
const CompareCard = () => {
// setState with the subArray products from data[0], I use '...' (spread operator) inside a Array or an Object to make a shalow copy
const [productsData, setProductsData] = useState([...data[0].products]);
const setTotalUpdate = (id) => {
// find the product to change inside products collection, that's ok
const productPrevious = productsData.find((rec) => {
return rec.id === id;
});
// create a new product to change one property's value, you can do something like 'productPrevious.total = 1', same same
const productUpdated = {
...productPrevious,
total: 1,
};
// create a new products collection to update state
const productNew = productsData.map((rec) => {
return rec.id === id ? productUpdated : rec;
});
setProductsData([...productNew]);
};
const setTotalUpdateSecond = (id) => {
// create a newState
const newState = productsData.map((product) => {
// condition if id === productId and do something
if (id === product.id) {
product.total = 1;
}
// both case, I return the product (change or not)
return product;
});
setProductsData([...newState]);
};
return (
<>
<button onClick={() => setTotalUpdate(1)}>Test old method on product 1</button>
<button onClick={() => setTotalUpdateSecond(2)}>Test second method on product 2</button>
{productsData.map((product) => {
return (
<>
<p>Product Id : {product.id} / Product Total : {product.total}</p>
</>
);
})}
</>
);
};
export default CompareCard;
Can you copy / past this, try and say me if it's what you want, if yes, I explain you where the confusion was. If not, explain me, what's the problem here and I modificate.

REACT UPDATE A DATA

I have a problem trying to update an Array of Objects that lives in a Themecontext, my problem is with mutation, I'm using Update from Immutability helpers. the thing is that when I update my array in my specific element, This appears at the end of my object.
This is my code:
function changeValueOfReference(id, ref, newValue) {
const namevalue = ref === 'colors.primary' ? newValue : '#';
console.warn(id);
const data = editor;
const commentIndex = data.findIndex(function(c) {
return c.id === id;
});
const updatedComment = update(data[commentIndex], {styles: { value: {$set: namevalue} } })
var newData = update(data, {
$splice: [[commentIndex, 1, updatedComment]]
});
setEditor(newData);
this is my result:
NOTE: before I tried to implement the following code, but this mutates the final array and break down my test:
setEditor( prevState => (
prevState.map( propStyle => propStyle.styles.map( eachItem => eachItem.ref === ref ? {...eachItem, value: namevalue}: eachItem ))
))
Well, I finally understood the issue:
1 - commentIndex always referenced to 0
The solution that worked fine for me:
1 - Find the index for the Parent
2 - Find the index for the child
3 - Add an array []
styles : { value: {$set: namevalue} } => styles :[ { value: [{$set: namevalue}] } ]
Any other approach is Wellcome
Complete Code :
function changeValueOfReference(id, referenceName, newValue) {
const data = [...editor];
const elemIndex = data.findIndex((res) => res.id === id);
const indexItems = data
.filter((res) => res.id === id)
.map((re) => re.styles.findIndex((fil) => fil.ref === referenceName));
const updateItem = update(data[elemIndex], {
styles: {
[indexItems]: {
value: { $set: namevalue },
variableref: { $set: [''] },
},
},
});
const newData = update(data, {
$splice: [[elemIndex, 1, updateItem]],
});
setEditor(newData);
}

How to update nested state properties in react using immutable way

I am trying to update children records based on api response in reducer. My current redux state is containing data in following format :
const container = {
data: [
{
id: "5e58d7bc1bbc71000118c0dc",
viewName: "Default",
children: [
{
id: "5e58d7bc1bbc71000118c0dd",
description: "child1",
quantity: 10
},
{
id: "5e58d7bc1bbc71000118c0ff",
description: "child2",
quantity: 20
},
{
id: "5e58d7bc1bbc71000118c0gg",
description: "child3",
quantity: 30
}
]
}
]
}
I am getting child1 data from api in following format :
{
id:"5e58d7bc1bbc71000118c0dd",
description:"child1",
quantity:20 // Quantity is updated
}
How can i update quantity for child1 in correct way ?
I am using immutable package.
https://www.npmjs.com/package/immutable
I honestly see no reason to use immutable.js, if you don't understand spread syntax or find it too verbose then you can use this helper.
const REMOVE = () => REMOVE;
const set = (object, path, callback) => {
const setKey = (current, key, value) => {
if (Array.isArray(current)) {
return value === REMOVE
? current.filter((_, i) => key !== i)
: current.map((c, i) => (i === key ? value : c));
}
return value === REMOVE
? Object.entries(current).reduce((result, [k, v]) => {
if (k !== key) {
result[k] = v;
}
return result;
}, {})
: { ...current, [key]: value };
};
const recur = (current, path) => {
if (path.length === 1) {
return setKey(
current,
path[0],
callback(current[path[0]])
);
}
return setKey(
current,
path[0],
recur(current[path[0]], path.slice(1))
);
};
return recur(object, path, callback);
};
const data = {
name: [{ hello: 'world', stay: true }, 4],
list: [1, 2, 3],
};
console.log(
'setting nested value',
set(data, ['name', 0, 'hello'], () => 'hello world')
.name[0].hello
);
console.log(
'doubling nested value',
set(data, ['name', 1], x => x * 2).name[1]
);
console.log(
'removing nested value',
set(data, ['name', 0, 'hello'], REMOVE).name[0]
);
console.log(
'adding to an array',
set(data, ['list'], v => [...v, 4]).list
);
console.log(
'mapping an array',
set(data, ['list'], v => v.map(v => v * 8)).list
);
console.log(
'data is not mutated',
JSON.stringify(data, undefined, 2)
);
You didn't post any code of how you save that data in state, did you use the immutable.js classes for it? If you did then say goodbye to redux dev tools and logging state to the console. Best to just leave it as data objects (serializable with JSON.stringify)

Find through array in array field

How can I find something through some arrays that also contain an array?
To be more precisely:
And I want to return from the coaches array, the id(within the coaches) that matches the username. What I've tried:
if (!(args['coach'].value === '') && (args['coach'].value !== null)) {
coachId = this.items.find(x => x.username === args.coach.value).id;
}
Basically this.items is what I've console.log before. Now it gives me undefined.
Has someone a fix for this? Thank you very much.
[
{
"id":584,
"name":"Name",
"coaches":[
{
"id":8587,
"username":"test"
},
{
"id":8589,
"username":"test1"
}
]
},
{
"id":587,
"name":"O1",
"coaches":[
]
}
]
And let s say I want to return the id 8587 when searching for the name test.
Combine map and find:
const array = [
[{a1: 1},
{a2: 2}
],
[{a1: 1},
{a2: 2},
{a3: 3}]
];
const element = array.map(innerArray => {
const found = innerArray.find(el => el.a1 === 1);
if (found) return found.a1;
return null;
}).find(el => el !== null);
console.log(element) // 1
For finding multiple matches do as follows:
const data = [{
"id": 584,
"name": "Name",
"coaches": [{
"id": 8587,
"username": "test"
},
{
"id": 8589,
"username": "test1"
}
]
},
{
"id": 587,
"name": "O1",
"coaches": [
]
}
];
const usernameToSearch = 'test1';
const foundCoachIds = data
.reduce((acc, curr) => {
// Destructure the coaches property first
const {
coaches,
...rest
} = curr;
// Check if any username matches the coach
const foundMatches = coaches.filter(x => x.username === usernameToSearch);
// If there is any found match push into accumulator
if (foundMatches.length) {
for (const foundMatch of foundMatches) {
if (acc.indexOf(foundMatch.id) === -1) {
acc.push(foundMatch.id);
}
}
}
return acc;
}, []);
console.log(foundCoachIds);
let y = this.items.filter(o => o.coaches.some(e => e.username === 'test'))[0]
.coaches.filter(e=>e.username === 'test')[0].id;
console.log(y);
const data = [
{
"id":584,
"name":"Name",
"coaches":[
{
"id":8587,
"username":"test"
},
{
"id":8589,
"username":"test1"
}
]
},
{
"id":587,
"name":"O1",
"coaches":[
]
}
];
For outer id
data.map(it => {return !!it.coaches.find(it2 => it2.username == "test") ? it.id : null}).filter(it=>!!it)
evaluates to [584]
For inner
(coaches) id:
data.map(it => it.coaches.find(it2 => it2.username == "test")).filter(it => !!it).map(it=> it.id)
returns [8587]
Just need to take the first item from these to get your answer.

Why i cannot update value of specific index in an array in react js via set State?

I have an array like below
[
1:false,
9:false,
15:false,
19:false,
20:true,
21:true
]
on click i have to change the value of specific index in an array.
To update value code is below.
OpenDropDown(num){
var tempToggle;
if ( this.state.isOpen[num] === false) {
tempToggle = true;
} else {
tempToggle = false;
}
const isOpenTemp = {...this.state.isOpen};
isOpenTemp[num] = tempToggle;
this.setState({isOpen:isOpenTemp}, function(){
console.log(this.state.isOpen);
});
}
but when i console an array it still shows old value, i have tried many cases but unable to debug.
This is working solution,
import React, { Component } from "react";
class Stack extends Component {
state = {
arr: [
{ id: "1", value: false },
{ id: "2", value: false },
{ id: "9", value: false },
{ id: "20", value: true },
{ id: "21", value: true }
]
};
OpenDropDown = event => {
let num = event.target.value;
const isOpenTemp = [...this.state.arr];
isOpenTemp.map(item => {
if (item.id === num) item.value = !item.value;
});
console.log(isOpenTemp);
this.setState({ arr: isOpenTemp });
};
render() {
let arr = this.state.arr;
return (
<React.Fragment>
<select onChange={this.OpenDropDown}>
{arr.map(item => (
<option value={item.id}>{item.id}</option>
))}
</select>
</React.Fragment>
);
}
}
export default Stack;
i hope it helps!
The problem is your array has several empty value. And functions like map, forEach will not loop through these items, then the index will not right.
You should format the isOpen before setState. Remove the empty value
const formattedIsOpen = this.state.isOpen.filter(e => e)
this.setState({isOpen: formattedIsOpen})
Or use Spread_syntax if you want to render all the empty item
[...this.state.isOpen].map(e => <div>{Your code here}</div>)

Resources