问题
So I had a problem in my React application, I ran into a specific case where I needed to have multiple setState()
calls in one method, and then have code run AFTER the states were set. The code below is a Dialog box used to add an account on the website.
import React from 'react';
import Dialog from 'material-ui/Dialog';
import FlatButton from 'material-ui/FlatButton';
import TextField from 'material-ui/TextField';
/**
* A modal dialog can only be closed by selecting one of the actions.
*/
export default class NewAcctDia extends React.Component {
constructor(props) {
super(props);
this.state = {
open: false,
userError: null,
passError: null,
passConfirmError: null,
}
this.handleOpen = this.handleOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleOpen() {
this.setState({open: true});
}
handleClose() {
this.setState({open: false});
}
handleSubmit() {
if(!this.refs.user.getValue())
this.setState({userError: "This field is required"});
else
this.setState({userError: null});
if(!this.refs.pass.getValue())
this.setState({passError: "This field is required"});
else
this.setState({passError: null});
if(this.refs.pass.getValue() == this.refs.passConfirm.getValue()) {
this.setState({passError: null});
} else {
this.setState({passConfirmError: "Passwords do not match"});
}
if(!this.state.userError && !this.state.passError && !this.state.passConfirmError)
alert('worked');
}
render() {
const actions = [
<FlatButton
label="Cancel"
primary={true}
onTouchTap={this.handleClose}
/>,
<FlatButton
label="Submit"
primary={true}
disabled={false}
onTouchTap={this.handleSubmit}
/>,
];
return (
<Dialog
title="Create an Account"
actions={actions}
modal={true}
open={this.state.open}
contentStyle={{width: 350}}
>
<TextField
ref='user'
floatingLabelText="Username"
errorText={this.state.userError}
/><br />
<TextField
ref='pass'
floatingLabelText="Password"
type="password"
errorText={this.state.passError}
/><br />
<TextField
ref='passConfirm'
floatingLabelText="Confirm Password"
type="password"
errorText={this.state.passConfirmError}
/><br />
</Dialog>
);
}
}
The problem is in the handleSubmit()
method, I needed to check to see that the user had entered something into the username and password fields, and that the passwords and confirm password fields matched. If they didn't, I would add error text to the fields that needed alteration via state. I then tried to look at the state to see if there were any errors.
Unfortunately, as I quickly figured out, the setState()
function is asynchronous, meaning the state would not get changed before my final check. I googled and searched all over for a way to wait for state to change before executing code but came up empty. I've solved this problem now and figured I would put it up on Stack so others might benefit from the method I came up with. I would also like to know any pros and cons of what I am doing, or any suggestions that might work better.
When I googled around I came across a method to send a callback to setState()
as shown: setState(data, callback)
. I did not think this would work for me at first, as I had multiple setState()
calls. However, I realized I could convert the handleSubmit()
method to a single setState()
call with ternaries. Like so:
handleSubmit() {
this.setState({
userError: (
this.refs.user.getValue() ? null : "This field is required"
),
passError: (
this.refs.pass.getValue() ? null : "This field is required"
),
passConfirmError: (
(this.refs.pass.getValue() == this.refs.passConfirm.getValue()) ?
null : "Passwords do not match"
)
}, () => {
if(!this.state.userError && !this.state.passError && !this.state.passConfirmError)
alert('worked');
})
}
The anonymous callback function will be executed after the state is changed, allowing my check to work.
The only problems I foresee with this method are nested ternaries, as that may become extremely messy if they are required. Is there any adverse effects this might have on my program? Or better ways I could have solved this?
I hope I've helped some people with my solution. :D
回答1:
React documentation encourages the use of componentDidUpdate
instead of the callback parameter of setState
.
Yet, if you think the code seems a bit messy, try out local consts and make a single call to setState
:
handleSubmit() {
const {user, pass, passConfirm} = this.refs;
const userError = user.getValue() ? null : "This field is required";
const passError = pass.getValue() ? null : "This field is required";
const passConfirmError = !passError && pass.getValue() === passConfirm.getValue()
? null
: "Passwords do not match";
this.setState({userError, passError, passConfirmError});
if(!userError && !emptyPassError && !passConfirmError)
alert('worked');
}
Finally, documentation also recommends the use of callback refs over strings refs:
Using the ref callback just to set a property on the class is a common pattern for accessing DOM elements. If you are currently using this.refs.myRefName to access refs, we recommend using this pattern instead.
来源:https://stackoverflow.com/questions/41007302/multiple-setstate-calls-in-a-react-method-how-to-make-it-work-synchronously