computed style in setState of react doesn't work - javascript

I have nested form objects but I try to setState using one handler, somehow doesn't work
constructor() {
super()
this.state = {
form: {
name: '',
location: ''
}
}
}
handleFormInput = (event) => {
this.setState({
[this.state.form[event.target.name]]: event.target.value
})
setTimeout(() => {
console.log(this.state.form)
},50)
}
event.target.name can be name and location.

You cannot directly access and modify a dynamic state within the setState function, you would rather get a copy of the state object and modify it. Also as you may already know that setState is async and hence you have a setTimeout function which is not necessary since setState provides you a callback function which is executed when state has changed.
handleFormInput = (event) => {
var form = {...this.state.form}
form[event.target.name] = event.target.value;
this.setState({
form
}, () => {this.state.form})
}

I think this code is not working properly:
this.setState({
[this.state.form[event.target.name]]: event.target.value
})
alternative is :
handleFormInput = (event) => {
//New code
let obj = {}
obj[this.state.form[event.target.name]] = event.target.value
this.setState(obj,() => { console.log(this.state.form) })
}
to view updated state, use callback function param of this.setState

setState is asynchronous, your state will properly update somehow after the call. Then render will be called again. Try checking your new state in render or in componentWillUpdate : https://facebook.github.io/react/docs/react-component.html#componentwillupdate
You should never look for a change in your state right after changing your state

Related

React cannot read properties of undefined (reading 'state')

Updated code
Initialized constructor and placed filter and loadOptions in class render method.
Still showing error saying that this.state.cars.filter is not a function.
import React, { Component } from 'react';
import AsyncSelect from 'react-select/async';
export default class Search extends Component {
constructor(props) {
super(props);
this.state = {
inputValue: null,
cars: []
};
}
componentDidMount() {
fetch(url)
.then(res => {
this.setState(prevState => ({...prevState, cars: res}))
})
}
handleInputChange = (newValue) => {
const inputValue = newValue.replace(/\W/g, '');
this.setState({ inputValue });
return inputValue;
};
render() {
const filterCars = (inputValue) => {
return this.state.cars.filter((i) =>
i.label.toLowerCase().includes(inputValue.toLowerCase())
);
};
const loadOptions = (
inputValue,
callback) => {
setTimeout(() => {
callback(filterCars(inputValue));
}, 1000);
};
return (
<div>
<AsyncSelect
cacheOptions
loadOptions={loadOptions}
defaultOptions
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
Example code of json file that Im fetching data from
[{"make":"KIA","link":"\/images\/image.jpg"},{"make":"BMW","link":"\/images\/image.jpg"}]
There are a couple of issues here. First is that you should declare state inside of a constructor, so instead of declaring state like that, do the following:
constructor(props) {
super(props)
this.state = {inputValue: '', cars: []}
}
Then you need to deal with your state mutations. Whenever you call setState, you are essentially giving it a new object which it sets the state to. You do not reassign the properties, you return a new object.
To resolve this reassignment issue, ES6 has introduced the spread operator which was not specifically designed to deal with mutations but it helps a lot.
Essentially, whenever you want to create a copy of an object, you don't do
let a = b
instead you do
let a = {...b}
With this change, changes on A will not reflect on B. So you make a copy instead of a duplicate.
How you can use this to avoid state mutation?
Whenever you call setState, make sure to first spread the rest of the state accross the new one before making any property changes:
setState(prevState => ({...prevState, cars: res}))
This way, you do not essentially remove the inputValue from your state object which can cause undefined issues.
Back to the main issue, your function filterCars is located outside of your class and in there you are trying to access this.state. Move the filterCars function inside of the class and your error will be resolved but if you don't resolve the mutation issues, it will not work as expected.

React - having trouble setting state of a collection through a text box

I have my initialized state like this:
this.state = {
notearr: {
noteid: this.props.id,
notevalue: null,
},
dataLoaded: false,
};
And then a function to handle the textbox onChange event:
<TextField
name="notevalue"
value={this.state.notevalue}
onChange={handleChange}
/>
Here is my present onChange handler:
const handleChange = (event) => {
event.preventDefault();
this.setState({[event.target.name]: event.target.value});
console.log(this.state.notearr.notevalue);
};
In the handleChange function, I would like to target the notearr collection, but am unsure how to target that specific collection. It currently writes to the root of the state, rather within the desired collection.
You would have to override notearr with a new object, that you clone from the previous state:
const { name, value } = event.target;
this.setState(prev => ({ notearr: { ...prev.notearr, [name]: value }}));
You might be tempted to do this instead:
this.setState({ notearr: { ...this.state.notearr, [event.target.name]: event.target.value }});
... however with that version, if two updates happen at the same time they will override each other, as setState is asynchronous. Also as events get recycled you need to copy the values over before the callback.
Also note that logging this.state after calling setState will not show you the updated state as setState is asynchronous. If you are seeing the updated state there, you're doing something wrong (you're mutating state).

post setState evaluation of state

In a react component, I update state in various ways, but I would like to do an evaluation (call a function) after the state was updated.
When I do the following, secondUpdate() does not access to the updated state, so it is one cycle late:
firstUpdate = e => {
this.setState({ email: e.state.value });
// ... some validation
secondUpdate();
}
secondUpdate() {
const allValid= this.state.aIsValid & this.state.bIsValid & this.state.cIsValid;
this.setState({ allIsValid: allValid });
}
How should I bind secondUpdate() to any or some state update?
You can use setState callback :
this.setState({ email: e.state.value } , () => {
// Will get called once the state is updated
// ... some valdidations
secondUpdate();
});
allIsValid shall not be a state at all. By having states that depend on each other, you risk that states get out of sync, and your logic breaks. Instead, as allIsValid is derived from other states, it can just be calculated based on the state inside render:
render() {
const { email } = this.state;
const allIsValid = email.length > 5 && /*...*/;
// ...
}
You can use componentDidUpdate().
componentDidUpdate(prevProps, prevState){
// code from secondUpdate()
}
Here is official doc
You might like to go through this:
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
this.setState({
// change the state
}, () => {
//Perform something after the state is updated.
})

React fetch data getting lost

I have a component in which I fetch data based on an item ID that was clicked earlier. The fetch is successful and console.log shows the correct data, but the data gets lost with this.setState. I have componentDidUpdate and componentDidMount in the same component, not sure if this is okay or maybe these two are messing eachother up?
Here is the code:
const teamAPI = 'http://localhost:8080/api/teams/'
const playerAPI = 'http://localhost:8080/api/playersByTeam/'
const matchAPI = 'http://localhost:8080/api/match/'
class View extends Component {
constructor() {
super();
this.state = {
data: [],
playersData: [],
update: [],
team1: [],
team2: [],
matchData: [],
testTeam: [],
};
}
componentDidUpdate(prevProps) {
if (prevProps.matchId !== this.props.matchId) {
fetch(matchAPI + this.props.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse);
this.setState({
matchData:matchfindresponse,
testTeam:matchfindresponse.team1.name,
})
})
}
}
componentDidMount() {
fetch(teamAPI)
.then((Response) => Response.json())
.then((findresponse) => {
console.log(findresponse)
this.setState({
data:findresponse,
team1:findresponse[0].name,
team2:findresponse[1].name,
})
})
fetch(playerAPI + 82)
.then(playerResponse => playerResponse.json())
.then(players => {
console.log(players)
this.setState({
playersData:players
})
})
}
The first render also gives this warning:
Warning: Can only update a mounted or mounting component. This usually means you called setState, replaceState, or forceUpdate on an unmounted component. This is a no-op.
Please check the code for the View component.
Everything from ComponentDidMount works fine in render but {this.state.matchData} and {this.state.testTeam} from componentDidUpdate are empty.
Could the problem be that ComponentDidMount re-renders the component which causes the data from ComponentDidUpdate to be lost and if so, how could I fix this?
Tried ComponentWillReceiveProps like this but still no luck
componentWillReceiveProps(newProps) {
if (newProps.matchId !== this.props.matchId) {
fetch(matchAPI + newProps.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse.team1.name);
console.log(this.props.matchId + ' ' + newProps.matchId);
this.setState({
matchData:matchfindresponse.team1.name,
})
})
}
}
On your componentDidMount you should be using Promise.all. This isn't really your problem, but it does make more sense.
componentDidMount() {
const promises = [
fetch(teamAPI).then(resp => resp.json()),
fetch(playerAPI + 82).then(resp => resp.json())
];
Promise.all(promises).then(([teamData, playerData]) => {
// you can use this.setState once here
});
}
Looks like your componentDidUpdate should be a getDerivedStateFromProps in combination with componentDidUpdate (this is new to react 16.3 so if you are using an older version use the depreciated componentWillReceiveProps). Please see https://github.com/reactjs/rfcs/issues/26. Notice too that now componentDidUpdate receives a third parameter from getDerivedStateFromProps. Please see https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html for more details.
EDIT: Just to add more details.
Your state object should just include other key like matchIdChanged.
Then
// in your state in your constructor add matchId and matchIdChanged then
static getDerivedStateFromProps(nextProps, prevState) {
if (nextProps.matchId !== prevState.matchId) {
return { matchIdChanged: true, matchId: nextProps.matchId }
}
return null;
}
componentDidUpdate() {
if (this.state.matchIdChanged) {
fetch(matchAPI + this.props.matchId)
.then((matchResponse) => matchResponse.json())
.then((matchfindresponse) => {
console.log(matchfindresponse);
this.setState({
matchData:matchfindresponse,
testTeam:matchfindresponse.team1.name,
matchIdChanged: false // add this
})
})
}
}
instead of using componentDidUpdate() lifecycle hook of react try using getDerivedStateFromProps() lifecycle function if you are using react 16.3, else try using componentWillReceiveProps() for below versions. In my opinion try to avoid the use of componentDidUpdate().
Plus error you are getting is because, setState() function is called, when your component somehow gets unmounted, there can be multiple reasons for this, most prominent being -
check the render function of this component, are you sending null or something, based on certain condition?
check the parent code of component, and see when is the component getting unmounted.
Or you can share these code, so that we might help you with this.
Plus try to debug using ComponentWillUnmount(), put console.log() in it and test it for more clarity.
Hope this helps, thanks

console is not showing the updated state value after setState [duplicate]

Ok, i'll try and make this quick because it SHOULD be an easy fix...
I've read a bunch of similar questions, and the answer seems to be quite obvious. Nothing I would ever have to look up in the first place! But... I am having an error that I cannot fathom how to fix or why its happening.
As follows:
class NightlifeTypes extends Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
this.handleOnChange = this.handleOnChange.bind(this);
}
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: event.target.checked});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
More code surrounds this but this is where my problem lies. Should work, right?
I've also tried this:
handleOnChange = (event) => {
if(event.target.className == "barClubLounge") {
this.setState({barClubLounge: !this.state.barClubLounge});
console.log(event.target.checked)
console.log(this.state.barClubLounge)
}
So I have those two console.log()'s, both should be the same. I'm literally setting the state to be the same as the event.target.checked in the line above it!
But it always returns the opposite of what it should.
Same goes for when I use !this.state.barClubLounge; If it starts false, on my first click it remains false, even though whether the checkbox is checked or not is based off of the state!!
It's a crazy paradox and I have no idea whats going on, please help!
Reason is setState is asynchronous, you can't expect the updated state value just after the setState, if you want to check the value use a callback method. Pass a method as callback that will be get executed after the setState complete its task.
Why setState is asynchronous ?
This is because setState alters the state and causes re rendering. This can be an expensive operation and making it synchronous might leave the browser unresponsive.
Thus the setState calls are asynchronous as well as batched for better UI experience and performance.
From Doc:
setState() does not immediately mutate this.state but creates a
pending state transition. Accessing this.state after calling this
method can potentially return the existing value. There is no
guarantee of synchronous operation of calls to setState and calls may
be batched for performance gains.
Using callback method with setState:
To check the updated state value just after the setState, use a callback method like this:
setState({ key: value }, () => {
console.log('updated state value', this.state.key)
})
Check this:
class NightlifeTypes extends React.Component {
constructor(props) {
super(props);
this.state = {
barClubLounge: false,
seeTheTown: true,
eventsEntertainment: true,
familyFriendlyOnly: false
}
}
handleOnChange = (event) => { // Arrow function binds `this`
let value = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: value}, () => { //here
console.log(value);
console.log(this.state.barClubLounge);
//both will print same value
});
}
}
render() {
return (
<input className="barClubLounge" type='checkbox' onChange={this.handleOnChange} checked={this.state.barClubLounge}/>
)
}
}
ReactDOM.render(<NightlifeTypes/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
Since setState is a async function. That means after calling setState state variable does not immediately change. So if you want to perform other actions immediately after changing the state you should use callback method of setstate inside your setState update function.
handleOnChange = (event) => {
let inputState = event.target.checked;
if(event.target.className == "barClubLounge") {
this.setState({ barClubLounge: inputState}, () => { //here
console.log(this.state.barClubLounge);
//here you can call other functions which use this state
variable //
});
}
}
This is by-design due to performance considerations. setState in React is a function guaranteed to re-render Component, which is a costly CPU process. As such, its designers wanted to optimize by gathering multiple rendering actions into one, hence setState is asynchronous.

Categories