I have a problem that setState being called simultaneously by various children components
Here is some oversimplificated code:
var Content = React.createClass({
updateElements: function(element) {
elements = [].concat(this.state.elements)
elements.push(element)
this.setState({ elements })
}
render: function() {
elements = ["a", "b"];
return (
<div>
<Element updateElements={this.updateElements} data={elements[0]} />
<Element updateElements={this.updateElements} data={elements[1]} />
</div>
);
}
});
var Element = React.createClass({
componentDidMount: function() {
this.props.updateElements(this.props.data)
}
render: function() {
return (
<div>
{this.props.data}
</div>
);
}
});
ReactDOM.render(
<Content />,
document.getElementById('container')
);
Can I somehow wait for the previous state to be set before updating it once again?
According to the setState documentation:
This form of setState() is also asynchronous, and multiple calls
during the same cycle may be batched together
Which means assuming you have 2 child elements that call the following parent method:
doSomething1(amount){
this.setState({
sum1: this.state.sum1 + amount
}, () => {
console.warn(this.state.sum1); // 1
})
}
Both will log 1, because subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once.
Instead you should use the updater function form:
doSomething2(amount) {
this.setState((prevState) => {
return { sum2: prevState.sum2 + amount };
}, () => {
console.warn(this.state.sum2); // 2
});
}
Live example
This can solve your problem if your issue is depending on the previous value of the state.
I am not totally sure but this should work.If you do setState like this
this.setState((prevState, props) => {
return {elements: [...prevState.elements, element]};
});
It won't be synchronous but it should persists the element without overiding it(because of simultaneous update) as it will be taking from prevState.
Related
I have an array that I am rendering on the render() function. Each element in the array is a HTML element that has state variables that I need to display, the HTML are displaying correctly, but the internal state variables do not update even when the rendering is happening
state = {
array: [],
id: 2
}
updateState() {
this.setState({id: 4})
}
componentDidMount(){
array = [<div> {this.state.id} </div>, <div> {this.state.id} </div>]
}
render() {
{this.state.array.map(el => return el)}
//assume something happens here that triggers updateState() multiple times: buttons presses, etc
}
I never see 4, it re renders but keeps the old value 2
You are creating the array in the componentDidMount function which is only being called once when the component first renders.
You should do something like this
//create function
createArray = () => [<div> {this.state.id} </div>, <div> {this.state.id} </div>]
then use it in your code like this
{this.createArray().map(el => el)}
Hope this helps.
You need to save the data and render again:
state = {
id: 2
}
updateState() {
this.setState({id: 4})
}
componentDidMount(){
this.getElements(this.state.id)
}
getElements = (id) => {
return [<div> {id} </div>, <div> {id} </div>]
}
render() {
{this.getElements(this.state.id).map(el => el)}
//assume something happens here that triggers updateState() multiple times: buttons presses, etc
}
I'm new to React and JavaScript.
I have a Menu component which renders an animation onClick and then redirects the app to another route, /coffee.
I would like to pass the value which was clicked (selected) to function this.gotoCoffee and update this.state.select, but I don't know how, since I am mapping all items in this.state.coffees in the same onClick event.
How do I do this and update this.state.select to the clicked value?
My code:
class Menus extends Component{
constructor (props) {
super(props);
this.state = {
coffees:[],
select: '',
isLoading: false,
redirect: false
};
};
gotoCoffee = () => {
this.setState({isLoading:true})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
renderCoffee = () => {
if (this.state.redirect) {
return (<Redirect to={`/coffee/${this.state.select}`} />)
}
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map(c =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee()}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
export default withRouter(Menus);
I have tried passing the value like so:
gotoCoffee = (e) => {
this.setState({isLoading:true,select:e})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
console.log(this.state.select)
}
an like so:
<div onClick={(c) => this.gotoCoffee(c)}
or so:
<div onClick={(event => this.gotoCoffee(event.target.value}
but console.log(this.state.select) shows me 'undefined' for both tries.
It appears that I'm passing the Class with 'c'.
browser shows me precisely that on the uri at redirect:
http://localhost/coffee/[object%20Object]
Now if I pass mapped 'c' to {this.renderCoffee(c)}, which not an onClick event, I manage to pass the array items.
But I need to pass not the object, but the clicked value 'c' to this.gotoCoffee(c), and THEN update this.state.select.
How do I fix this?
You can pass index of element to gotoCoffee with closure in render. Then in gotoCoffee, just access that element as this.state.coffees[index].
gotoCoffee = (index) => {
this.setState({isLoading:true, select: this.state.coffees[index]})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
render(){
const data = this.state.coffees;
return (
<div>
<h1 className="title is-1"><font color="#C86428">Menu</font></h1>
<hr/><br/>
{data.map((c, index) =>
<span key={c}>
<div>
{this.state.isLoading && <Brewing />}
{this.renderCoffee()}
<div onClick={() => this.gotoCoffee(index)}
<strong><font color="#C86428">{c}</font></strong></div>
</div>
</span>)
}
</div>
);
}
}
so based off your code you could do it a couple of ways.
onClick=(event) => this.gotoCoffee(event.target.value)
This looks like the approach you want.
onClick=() => this.gotoCoffee(c)
c would be related to your item in the array.
All the answers look alright and working for you and it's obvious you made a mistake by not passing the correct value in click handler. But since you're new in this era I thought it's better to change your implementation this way:
It's not necessary use constructor at all and you can declare a state property with initial values:
class Menus extends Component{
state= {
/* state properties */
};
}
When you declare functions in render method it always creates a new one each rendering which has some cost and is not optimized. It's better if you use currying:
handleClick = selected => () => { /* handle click */ }
render () {
// ...
coffees.map( coffee =>
// ...
<div onClick={ this.handleClick(coffee) }>
// ...
}
You can redirect with history.replace since you wrapped your component with withRouterand that's helpful here cause you redirecting on click and get rid of renderCoffee method:
handleClick = selected => () =>
this.setState(
{ isLoading: true},
() => setTimeout(
() => {
const { history } = this.props;
this.setState({ isLoading: false });
history.replace(`/${coffee}`);
}
, 5000)
);
Since Redirect replaces route and I think you want normal page change not replacing I suggest using history.push instead.
You've actually almost got it in your question. I'm betting the reason your state is undefined is due to the short lived nature of event. setState is an asynchronous action and does not always occur immediately. By passing the event off directly and allowing the function to proceed as normal, the event is released before state can be set. My advice would be to update your gotoCoffee function to this:
gotoCoffee = (e) => {
const selectedCoffee = e.target.value
this.setState({isLoading:true,select:selectedCoffee},() =>
{console.log(this.state.select})
setTimeout(()=>{
this.setState({isLoading:false,redirect:true})
},5000)
}
Note that I moved your console.log line to a callback function within setState so that it's not triggered until AFTER state has updated. Any time you are using a class component and need to do something immediately after updating state, use the callback function.
I am using React's setState method, and calling another function when the state has been updated.
Is there a preferred approach as to how to call the function that is passed to setState as a callback.
Both of the approaches below work but is there any performance implications of using one over the other?
this.setState(prevState => {
return {
result: '1-0'
}
}, this.clearResult(500))
or
this.setState(prevState => {
return {
result: '1-1',
}
}, () => this.clearResult(500))
My clearPin method looks like the following. All of this code is within a React component.
clearResult(time) {
setTimeout(() => {
this.setState({
result: '0-0'
})
}, time)
}
Both of the approaches below work but is there any performance implications of using one over the other?
There's a correctness implication: The first one is incorrect, the second one is correct. :-)
In your first example, you're calling this.clearResult(500) and then calling setState (with the result of calling this.clearResult(500) — undefined, in your example — as its second argument). this.setState(prevState => { ... }, this.clearResult(500)); is just like foo(bar()) — first it calls bar, then it passes the result of calling it into foo.
In your second example, you're passing a function into setState that it will call when the state is updated.
You want the second form (or one of the various equivalents to it).
this.setState(prevState => {
return {
result: '1-1',
}
}, () => this.clearResult(500));
// or: }, this.clearResult.bind(this, 500));
// But the arrow is clear and idiomatic
Here's proof that your first example is calling clearResult before calling setState, and before your state change callback is called:
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {value: "a"};
}
// Overriding it PURELY to show what's happening
setState(...args) {
console.log("setState called");
return super.setState(...args);
}
componentDidMount() {
this.setState(
() => {
console.log("state change callback");
return {value: "b"};
},
this.clearResult(500)
);
}
clearResult(delay) {
console.log("clearResult called");
setTimeout(() => {
this.setState({value: "c"});
}, delay);
}
render() {
return <div>{this.state.value}</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<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>
whereas with () => this.clearResult(500) instead, clearResult is called after setState (and after the state change):
class Example extends React.Component {
constructor(...args) {
super(...args);
this.state = {value: "a"};
}
// Overriding it PURELY to show what's happening
setState(...args) {
console.log("setState called");
return super.setState(...args);
}
componentDidMount() {
this.setState(
() => {
console.log("state change callback");
return {value: "b"};
},
() => this.clearResult(500)
);
}
clearResult(delay) {
console.log("clearResult called");
setTimeout(() => {
this.setState({value: "c"});
}, delay);
}
render() {
return <div>{this.state.value}</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<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>
Side note 1: If you want, you can be a bit more concise:
this.setState(
() => ({ result: '1-1' }),
() => this.clearResult(500)
);
Side note 2: There's no need to use the function form if the new state you're passing isn't based on current state or props. In your example, it isn't, so yours is one of the places where using the non-callback form is okay:
this.setState(
{ result: '1-1' },
() => this.clearResult(500)
);
That would not be okay if you were using something from this.state or this.props. In that situation, use the callback form and its prevState and props parameters. Always. And there's little harm in always using the callback form, the overhead of a function call is exceptionally trivial on modern JavaScript engines. (It was even trivial on the slowest JS engine of this century: The one in IE6.)
More on that here and here.
tl;dr -- I want to fire a callback whenever the state changes in a component
I have a component that mutates this.props.items and fires this.props.onChange on every mutation. I'm currently doing something like this:
removeItem(ix) {
const items = this.state.items.slice();
items.splice(ix, 1);
this.setState({ items });
this.triggerOnChange();
}
I'm wondering if it's possible to remove the manual call to triggerOnChange and instead do it whenever this.state.items is updated
this.setState takes two parameter, first is object (state) and second is a callback (optional)
this.setState({...}, function(){
console.log('changed')
})
From Spec
The second parameter is an optional callback function that will be
executed once setState is completed and the component is re-rendered
You could override setState, if you want to wrap the state change:
setState(state) {
console.log("New state - before: ", state);
const retVal = super.setState(state);
console.log("New state - after");
return retVal;
}
(That may be overkill, I don't think setState is documented to have a return value; but frustratingly, as far as I can tell, it's not documented not to...)
Example:
class MyThingy extends React.Component {
constructor(props) {
super(props);
this.state = {...props};
}
render() {
return (
<div onClick={() => this.update()}>
<div>{this.state.title}</div>
{this.state.items.map(e => (
<div>{e}</div>
))}
</div>
);
}
update() {
this.setState({
items: ['a', 'b', 'c'],
title: "After"
});
}
setState(state) {
console.log("New state - before: ", state);
const retVal = super.setState(state);
console.log("New state - after");
return retVal;
}
}
ReactDOM.render(
<MyThingy items={[]} title="Before - click me" />,
document.getElementById("react")
);
<div id="react"></div>
<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>
Alternately, you might turn this on its head and look at MobX.
I encountered this problem building a webapp and I replicated it in this jsfiddle. Essentially, I would like an input to call this.setState({message: input_val}) every time I type something into it, then pass it into the parent App class which then re-renders the message onto the Message class. However the output seems to always be one step behind what I actually type. The jsfiddle demo should be self explanatory. I am wondering if I did anything wrong to prompt this.
html
<script src="http://facebook.github.io/react/js/jsfiddle-integration.js"></script>
<div id='app'></div>
js
var App = React.createClass({
getInitialState: function() {
return {message: ''}
},
appHandleSubmit: function(state) {
this.setState({message: state.message});
console.log(this.state.message);
},
render: function() {
return (
<div className='myApp'>
<MyForm onChange={this.appHandleSubmit}/>
<Message message={this.state.message}/>
</div>
);
}
});
var MyForm = React.createClass({
handleSubmit: function() {
this.props.onChange(this.state);
},
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value});
this.handleSubmit();
},
render: function() {
return (
<form className="reactForm" onChange={this.handleChange}>
<input type='text' />
</form>
);
}
});
var Message = React.createClass({
render: function() {
return (
<div className="message">
<p>{this.props.message}</p>
</div>
)
}
});
React.render(
<App />,
document.getElementById('app')
);
A call to setState isn't synchronous. It creates a "pending state transition." (See here for more details). You should explicitly pass the new input value as part of the event being raised (like to handleSubmit in your example).
See this example.
handleSubmit: function(txt) {
this.props.onChange(txt);
},
handleChange: function(e) {
var value = e.target.value;
this.setState({message: value});
this.handleSubmit(value);
},
There is a much simpler way to do this, setState(updater, callback) is an async function and it takes the callback as second argument,
Simply pass the handleSubmit as a callback to setState method, this way after setState is complete only handleSubmit will get executed.
For eg.
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit);
}
Try to change the handleChange() method like above and it will work.
for syntax of using setState check this link
with setState hook
useEffect(() => {
your code...
}, [yourState]);
I was pulling my hair out for like an hour because of this so I decided to share... If your callback is still one step behind and seemingly not working, ensure you don't CALL the function with parenthesis... Just pass it in. Rookie mistake.
RIGHT:
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit);
}
VS
WRONG:
handleChange: function(e) {
console.log(e.target.value);
this.setState({message: e.target.value}, this.handleSubmit());
}
There's no reason for MyForm to be using state here. Also putting the onChange on the form instead of the input you're interested in is odd. Controlled components should be preferred because their behavior is more obvious, and any time App's message state changes (even if you e.g. allow Message to change it later), it'll be correct everywhere.
This also makes your code a bit shorter, and considerably simpler.
var App = React.createClass({
getInitialState: function() {
return {message: ''}
},
appHandleSubmit: function(message) {
this.setState({message: message});
},
render: function() {
return (
<div className='myApp'>
<MyForm onChange={this.appHandleSubmit}
message={this.state.message} />
<Message message={this.state.message}/>
</div>
);
}
});
var MyForm = React.createClass({
handleInputChange: function(e){
this.props.onChange(e.target.value);
},
// now always in sync with the parent's state
render: function() {
return (
<form className="reactForm">
<input type='text' onChange={this.handleInputChange}
value={this.props.message} />
</form>
);
}
});
jsbin
Knowing the problem is with not having asyncronous behaviour of setState I solved my issue with async await
onChangeEmail=async x =>{
await this.setState({email:x})
}
You could refactor your class-based component to a functional component as someone else mentioned. The drawbacks are that this can be quite time-consuming depending on how many code lines you have to refactor and is likely prone to error.
I will use your example of a changeHandler to display how it could be done in a functional component.
const INITIAL_DATA = {
message: ""
}
const [form, setForm] = useState({...INITIAL_DATA})
const changeHandler = (e) = setForm({
...form,
[e.target.name]: e.target.value
})
<InputField name={message} value={form.message} onChange={changeHandler}>
^ The above code will produce that behavior as you explained of onChange being one step behind the setState. As others have said, this is due to the asynchronous nature of the setState hook.
The way to solve this is to use the useEffect hook, which allows you to introduce state dependencies. So when setState is finished going through its update cycle the useEffect will fire. So add the below to the above code and BAM.. it should show the state changes without a step delay.
useEffect(() => {
doSomeValidationHere()
orSendOffTheForm()
}, [form])
*Notice how we add a dependency array after the useEffect(() => {}) hook.
Extra info:
If the dependency array is empty it will only run on component mount and first render
If there is no array, it will run every time the page renders
If there is a state name in the array it will run every time that state is finished setting
I found it very cumbersome for me to define 3 handler functions just to get some value to a component's state, so I decided not to use state at all. I just defined an additional property to the component that stored desired value.
So I ended up with a code that looked something like this:
//...
},
text: '',
handleChange: function(event) {
this.text = event.target.value;
this.forceUpdate();
},
render: function() {
return <div>
<InputComponent onChange={this.handleChange}/>
<DisplayComponent textToDisplay={this.text}/>
</div>
}
//...
or as in my case - just use onKeyUp, instead of down...
There are some solutions mentions above, some of them have some problems, but
ajsaule is the correct ansower. Here is show my Code in TypeScript Example:
// solve the problem: setstate always one step behind
useEffect(() => { isFormValid(fields, setFieldsError) }, [fields])
const handleChange = (field: string ) => (evt: React.ChangeEvent<HTMLInputElement>) => {
setFields({ ...fields, [field]: evt.target.value })
}
const isFormValid = (fields: FieldsState, setFieldsError: React.Dispatch<React.SetStateAction<TempObj>>) => {
const tempObj: TempObj = {}
const { email, password } = fields
if( !isEmail(email) ) tempObj.email = 'Invalid Email Address'
if( password.length < 8 ) tempObj.password = 'password must by atleast 8 character long'
Object.keys(fields).forEach(field => {
if(!fields[field as keyof FieldsState]) tempObj[field] = `'${field}' is emapty`
})
setFieldsError(tempObj)
return Object.values(tempObj).every( item => item == '' )
}