Replacing value in nested state with react functional components - reactjs

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.

Related

State not updating in react with a nested object (hooks)

I've been playing with react-beautiful-dnd and hooks (very new to react) - and for some reason my state doesn't update on drag. (Edit: I know the logic only works for 'same category' drag - this isn't updating the UI either for me)
Data (simplified)
const skills = {
skills: {
skill1: {
id: "skill1",
name: "Communication"
},
skill2: {
id: "skill2",
name: "Empathy"
},
skill3: {
id: "skill3",
name: "Initiative"
}
},
categories: {
cat1: {
id: "cat1",
name: "Core",
skillIds: ["skill1", "skill2", "skill3", "skill4"]
},
cat2: {
id: "cat2",
name: "Craft",
skillIds: ["skill5", "skill6", "skill7", "skill8"]
},
cat3: {
id: "cat3",
name: "Leadership",
skillIds: ["skill9", "skill10"]
}
},
categoryOrder: ["cat1", "cat2", "cat3"]
};
Function to update the skillIds array in the correct category
const reorder = (list, startIndex, endIndex) => {
const result = Array.from(list);
const [removed] = result.splice(startIndex, 1);
result.splice(endIndex, 0, removed);
return result;
};
const onDragEnd = (result) => {
const { source, destination } = result;
// dropped outside the list
if (!destination) {
return;
}
// Handle moving within one category
if (source.droppableId === destination.droppableId) {
const catSkills = data.categories[source.droppableId].skillIds;
const items = reorder(catSkills, source.index, destination.index);
const newData = {
...data,
categories: {
...data.categories,
[source.droppableId]: {
...data.categories[source.droppableId],
skillIds: items
}
}
};
setData(newData);
}
};
I've created a simplified codesandbox to test - https://codesandbox.io/s/hooks-problem-il5m4
Any help appreciated!
I can see the state is getting updated
if (source.droppableId === destination.droppableId) { setData(data) }
this "if" clause means it will only update the state if the drag is happen on the same lane .i think you have tried to drag it to another lane . hope this is what you meant
Edit: I have understood your problem . The issue is you are not using updated data you are looping the static skill.I hope this solve your problem
{data.categoryOrder.map((catId) => {
const category = data.categories[catId]; //change skills to data
const catSkills = category.skillIds.map(
(skillId) => skills.skills[skillId]
);

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)

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

Update item in state onClick ReactJS

So, I have class Comopnent :
state = {
tokens: [
{
name: "first",
value: 3
},
{
name: "second",
value: 2
},
{
name: "third",
value: 4
}
]
}
handleClick = (name, id) => {
const newState = this.state.tokens.map((token => {
console.log(token.name)
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div value={t.name} onClick={() => this.handleClick(t.name, t.value)}>
<img src=""/>
</div>
)
})
What i need to do - after click - to subtract 1 from value clicked token.
So - ex. after click on "First" token i want his value equal 2.
So far I've done just as much as the above.
I do not know how to go about it, i am new in ReactJS, so thanks for help in advance!
You'll have to find in your state in tokens array the object which has the same name as the argument passed in the onclick handler. Then you will have to change it's value - decrement it (value--) but you have to be aware that you can't mutate the state.
handleClick = name => () => {
const { tokens } = this.state;
const clickedToken = tokens.find(token => token.name === name);
clickedToken.value--;
const clickedTokenIndex = tokens.indexOf(clickedToken);
const newTokens = [
...tokens.slice(0, clickedTokenIndex),
clickedToken,
...tokens.slice(clickedTokenIndex + 1)
];
this.setState({ tokens: newTokens });
};
Codesandbox link: https://codesandbox.io/s/92yz34x97w
First, some things are wrong with your code.
1- You have an array of tokens, then you're mapping the list, but you don't have a key to index, this will cause weird behaviors, I improve your tokens list with keys now.
2.- You can handle the click and change the state of the tokens list, this will trigger a reload of the component.
state = {
tokens: [
{
name: "first",
value: 3,
id: 1
},
{
name: "second",
value: 2,
id: 2
},
{
name: "third",
value: 4,
id: 3
}
]
}
handleClick = (name, id) => {
const { tokens} = this.state;
const newState = tokens.map((token => {
if(token.id === id) {
token.value--;
}
return token;
}))
}
render() {
const token = this.state.tokens;
const tokenList = token.map(t => {
return (
<div key={t.key} value={t.name} onClick={() => this.handleClick(t.name, t.value, t.key)}>
<img src=""/>
</div>
)
})

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