So, I define 2 states with the same array, it's listRasioUom and showListRasioUom. but when I setstate to listRasioUom state, the showListRasioUom state also changed with the same value as listRasioUom. any help? this is my code
handleChange = (e) => {
if (["rasio", "uom"].includes(e.target.name)) {
let listRasioUom = this.state.listRasioUom
if(listRasioUom.some(el => el.uom === e.target.value))
{
alert('Duplicate uom')
}
else
listRasioUom[e.target.dataset.id][e.target.name] = e.target.value;
this.setState({ listRasioUom })
}
}
}
showListRasioUom state is used for fetching the data on my datatables, while listRasioUom state is used to modified the state, so i only want to change listrasiouom state, but when i use setstate on listRasioUom state, the showListRasioUom also change.
<MDBDataTable
info={false}
paging={false}
searching={false}
striped
bordered
small
data={{columns: this.state.columns, rows: this.state.showListRasioUom}}
noBottomColumns
/>
Object and arrayss in javascript are assigned by reference and if you mutate the original array the other array which also holds the same reference is also updated
You should update your data in an immutable manner
handleChange = (e) => {
if (["rasio", "uom"].includes(e.target.name)) {
let listRasioUom = this.state.listRasioUom
if(listRasioUom.some(el => el.uom === e.target.value))
{
alert('Duplicate uom')
}
else {
const id = e.target.dataset.id;
const {name,value} = e.target;
this.setState(prev => ({
listRasioUom: prev.listRasioUom.map((item, i) => {
if(i == id) return {...item, [name]: value};
})
return item;
}))
}
}
}
Related
My result is seeing item.acf.count as string and not as number. How can I convert this to number?
Here is the function below.
increaseItem = (id) => {
const { cart } = this.state;
cart.forEach(item => {
if (item.id === id) {
item.acf.count += 1
}
})
this.setState({
cart:cart
})
}
My result is seeing item.acf.count as string and not as number. please
how can I convert is to number
You should ensure that the initial item.acf.count state is a number type so this count: item.acf.count + 1 operation works correctly and returns a number type. So long as your state updaters maintain the state invariant of item.acf.count being a number it should work as expected.
Additionally, the increaseItem handler is mutating the cart state and not creating new array/object references.
increaseItem = (id) => {
const { cart } = this.state; // <-- cart is reference to cart state
cart.forEach(item => {
if (item.id === id) {
item.acf.count += 1; // <-- mutation!
}
});
this.setState({
cart: cart // <-- cart state reference back into state
})
}
You should instead shallow copy the cart and then also shallow copy the cart item (any any other nested properties) you want to update. I also suggest using a functional state update so you are correctly updating from the previous state and not any state value closed over in increaseItem callback scope.
increaseItem = (id) => {
this.setState(prevState => ({
cart: prevState.cart.map(item => item.id === id
? {
...item,
acf: {
...item.acf,
count: item.acf.count + 1
}
}
: item),
}));
}
I have a list of columns, they are rendered as TextFields:
{columns.map((column, index) => (
<TextField
key={index}
margin="dense"
id={column}
label={column}
name={column}
type="text"
onChange={this.handleInputChange}
fullWidth/>
))}
and handleInputChange function is written as below:
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
addData: {
[this.state.fullName]:{
[name]: value
}
}
});
}
The data that I want to receive is:
addData: {
[name-of-column-1]: value,
[name-of-column-2]: value,
[name-of-column-3]: value,
.......
}
but the handleInputChange function overrides after every TextField change, the data that I received:
addData: {
[name-of-column-1 (or 2, 3...)]: value,
}
Is there any way to get the data I need? Thanks everyone!
How do I setState for nested object?
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
addData: {
[this.state.fullName]:{
...this.state.addData[this.state.fullName],
[name]: value,
}
}
});
}
You are assigning whole new object every time value from one TextField changes and not retaining old values from state.
I'm not sure what exactly is this.state.fullName, but in order to have your desired state structure you can implement it like this:
handleInputChange(event) {
const target = event.target;
const value = target.value;
const name = target.name;
this.setState({
addData: {
...this.state.addData,
[name]: value
}
});
}
When destructing this.state.addData (with ...), you are basically assigning all of its properties to the new object and then adding additional [name]: value. Its important to put destruction above new assignment in order to update latest value.
I think you should prepare your data first and set it into your state or try in this way
this.setState({
addData: {
[...this.state.fullName]:{
[name]: value
}
}
});
This's a issue when you setState. When you using
this.setState({
addData: {
[this.state.fullName]:{
[name]: value
}
}
});
that mean you are setting a new state just have one nested object
addData { abc: value }
so the old values are lost. You need add them before you change the new value. Try this
handleInputChange(event) {
const { name, value } = event.target;
const newData = {...this.state.addData};
newData[name] = value;
this.setState({ addData: newData });
}
or
handleInputChange(event) {
const { name, value } = event.target;
this.setState({
addData: {
...this.state.addData,
[name]: value
}
});
}
I have this inside my render:
{this.availProps(this.state.data)}
this.state.data is updated with a fetch when componentOnMount
availProps = data =>{
if (data.length == 0)
return (
<option>"hello"</option>
)
else
return (
<option> "hi" </option>
)
}
It prints out "hi" just fine when the data is done fetching.
However, if I use:
{this.availProps()}
and
availProps = () =>{
if (this.state.data.length == 0)
return (
<option>"hello"</option>
)
else
return (
<option> "hi" </option>
)
}
It will not work. It prints out "hello" instead.
Is this because the page is only rerendered if a variable inside the "render" is changed/updated? ( in this case, this.state.data)
Thank you
edit: here is the componentDidMount
componentDidMount() {
this.getDataFromDb()
}
getDataFromDb = () => {
fetch("http://localhost:3001/api/property")
.then(property => property.json())
.then(res => this.setState({ data: res.data }))
.then(() =>{
for(var i = 0; i < this.state.data.length; i++) {
if (this.state.data[i].status == 0)
this.state.useData.push(this.state.data[i])
}
}).then
( ()=> console.log(this.state.useData))
};
Setting the property directly on this.state does not invoke the render method.
You will have to use this.setState({ useData: useData }) so that react will that something has changed which runs the render method.
And since the state that is being set is based on the previous state, it is better you use state updater pattern and its callback so that the updated state is available when you try to access it.
Do not update state directly
getDataFromDb = () => {
fetch("http://localhost:3001/api/property")
.then(property => property.json())
.then(res => this.setState({
data: res.data
}))
.then(() => {
// Your computations are based on previous state
// use the updater function to have access to the latest
// state as state updates are asynchronous
this.setState((previousState) => {
const {
data
} = previousState;
// accessing this.state here might have stale data
const updatedUseData = data.reduce((acc, obj) => {
if (obj.status === 0) {
acc.push(obj);
}
return acc;
}, []);
// this will invoke the render again as
// a state is updated
return {
useData: updatedUseData
}
}, () => {
// Use the call back which gets invoked once the state
// is updated
console.log(this.state.useData)
})
})
}
A component will rerender by default if a value from props or state is changed, which is being used in the render or in a function that the render is calling.
If you had a class-level variable, such as this.example and were using that in the render, changing that value wouldn't make the component rerender.
You can also override shouldComponentUpdate to prevent the component from re-rendering when certain values of props or state changes, if you wish to do that. You can read more about that here. As it says, it's not recommended to do this, unless you're confident that you're handling it correctly.
EDIT
As the other answer suggests, only values updated using this.setState will cause a re-render. Doing something like:
this.state.useData.push(this.state.data[i])
will not cause it to update. I'd suggest the following change to getDataFromDb to handle this
getDataFromDb = () => {
fetch("http://localhost:3001/api/property")
.then(property => property.json())
.then(res => {
this.setState({ data: res.data, useData: res.data.filter(item => item.status === 0) });
}).then
( ()=> console.log(this.state.useData))
};
Just as an aside, setState is async so console.log on the .then won't necessarily see this change yet.
How can I generalize these two functions to use in identical divs?
showNotifyText = () => {
const { showNotifyText } = this.state;
this.setState({ showNotifyText: !showNotifyText });
};
showRoutineAvailabilityText = () => {
const { showRoutineAvailabilityText } = this.state;
this.setState({ showRoutineAvailabilityText: !showRoutineAvailabilityText });
};
Using this.state in setState is antipattern because setState is asynchronous. If new state derives from previous one, setState function should be used.
It could be either generalized method:
toggleStateProperty = key => {
this.setState(state => ({ [key]: !state[key] }));
};
Or higher-order function:
toggleStatePropertyFactory = key => function () {
this.setState(state => ({ [key]: !state[key] }));
};
...
toggleShowNotifyText = toggleStatePropertyFactory('showNotifyText');
toggleRoutineAvailabilityText = toggleStatePropertyFactory('showRoutineAvailabilityText');
In case the method is supposed to be passed as a callback, the second option is preferable because it already binds the method to specific key:
<div onclick={this.toggleShowNotifyText}/>
vs
<div onclick={() => this.toggleStateProperty('showNotifyText')}/>
To generalize the method you could try adding the state key in the template as data, then read it inside the event handler:
onClick = (event) => {
const key = event.target.dataset.key;
const value = this.state[key];
this.setState({ [key]: !value });
}
render() {
return (
<div>
<div onClick={this.onClick} data-key="showRoutineAvailabilityText">click</div>
</div>
);
}
I have this class:
class Board {
this.state = {
lists : [{
id: 0,
title: 'To Do',
cards : [{id : 0}]
}]
}
And want to use setState on the 'cards' array inside of the 'lists' state array. Previously, I had the cards array in a child component but I have now moved it up to the Board class. This is the function that I had before.
deleteCards(id){
this.setState({
cards: this.state.cards.filter(card => card.id !== id)
});
}
How can I change it so that it works now that cards is inside another array?
I was unable to solve it looking at these posts:
ReactJS - setState of Object key in Array
How to edit an item in a state array?
To do it all within setState (note that the first argument to setState is an updater function where its first argument is a reference to the previous state):
If you can provide the listId from the caller:
deleteCards(listId, cardId) {
this.setState(prevState => ({
lists: prevState.lists.map((list) => {
if (list.id !== listId) {
return list
}
return {
...list,
cards: list.cards.filter(card => card.id !== cardId)
}
})
}))
}
If you can not provide the listId from the caller:
deleteCards(id) {
this.setState(prevState => ({
lists: prevState.lists.map((list) => {
if (list.cards.some(card => card.id === id)) {
return {
...list,
cards: list.cards.filter(card => card.id !== id)
}
}
return list
})
}))
}
You should attempt to use the new rest and spread syntax...
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Spread_syntax
const newListItem = {this.state.lists[0].cards.filter....}
this.setState({lists: [...this.state.lists.cards, newListItem]})
I would have made this a comment but it would be pretty hard to read. This is just an example you need to actually write a filter.