I'm trying to adapt an example seen on an udemy course from a class-based stateful component to a function based component using the useState hook with React 16.7.0-alpha.2
While setter functions for primitive datatypes work fine (for example setUsername), calling a setter for an array variable has no effect/result. At least it doesn't reset the state variable back to an empty array.
On the other hand, setting a new copy of the array from state by using the concat method works as expected.
I'm still new to React hooks and wonder what I've missed?
import React, {useState} from 'react';
import { Grid, Form, Segment, Button, Header, Message, Icon } from 'semantic-ui-react';
import { Link } from 'react-router-dom';
import { registerUser } from './authFunctions';
import { isRegisterFormEmpty } from './validatorFunctions';
const Register = () => {
//defining state properties and setters:
const [username, setUsername] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [passwordConfirmation, setPasswordConfirmation] = useState('');
const [registerErrors, setRegisterErrors] = useState([]);
//defining handlers:
const onUsernameChange = e => setUsername(e.target.value);
const onEmailChange = e => setEmail(e.target.value);
const onPasswordChange = e => setPassword(e.target.value);
const onPasswordConfirmationChange = e => setPasswordConfirmation(e.target.value);
const onFormSubmit = e => {
e.preventDefault(); //prevent a page reload
//set registerErrors to empty array in case that the user clicks on submit again
setRegisterErrors([]); // DOES NOT WORK
setUsername('JDoe'); //works as expected
if( isRegisterFormEmpty(username, email, password, passwordConfirmation) ) {
let error = {message: 'Please fill in all fields'};
setRegisterErrors( registerErrors.concat(error) ); //THIS WORKS FINE, THOUGH...
} else {
//registerUser(username, email, password, passwordConfirmation);
}//if
}//onFormSubmit
const showErrors = () => registerErrors.map( (error, idx) => <p key={idx}>{error.message}</p> );
return (
<Grid textAlign='center' verticalAlign='middle' className='app'>
<Grid.Column style={{ maxWidth: 450 }}>
<Header as='h2' icon color='teal' textAlign='center'>
<Icon name='puzzle piece' color='teal' />
Register to DevChat
</Header>
<Form size='large' onSubmit={onFormSubmit}>
<Segment stacked>
<Form.Input
fluid
type='text'
icon='user'
iconPosition='left'
placeholder='Username'
onChange={onUsernameChange}
value={username}
/>
<Form.Input
fluid
type='email'
icon='mail'
iconPosition='left'
placeholder='Email'
onChange={onEmailChange}
value={email}
/>
<Form.Input
fluid
type='password'
icon='lock'
iconPosition='left'
placeholder='Password'
onChange={onPasswordChange}
value={password}
/>
<Form.Input
fluid
type='password'
icon='lock'
iconPosition='left'
placeholder='Password Confirmation'
onChange={onPasswordConfirmationChange}
value={passwordConfirmation}
/>
<Button
color='teal'
fluid
size='large'
content='Submit'
/>
</Segment>
</Form>
{
registerErrors.length > 0 &&
<Message error>
<h3>Please note</h3>
{showErrors()}
</Message>
}
<Message>
Already a user? <Link to='/login'>Login</Link>
</Message>
</Grid.Column>
</Grid>
)
}
export default Register;
Going back to the useState hook, it returns its variables in an array because it is likely that we're going to use that hook more than once per component. We need to be able to instantiate that hook several times but each instance has a different name. Example: const [val, setVal] = useState(0);
The answer: They're just queues setState , and React. useState create queues for React core to update the state object of a React component. So the process to update React state is asynchronous for performance reasons. That's why changes don't feel immediate.
Creating an Array state with useState() We destructure the return value of the useState() hook to get a variable that contains the state array and a method for updating the state. You can't update the array directly without using the method returned from useState() . In our case, it's setMyArray() .
What does useState return? It returns a pair of values: the current state and a function that updates it. This is why we write const [count, setCount] = useState() .
This is common useState
pitfall.
setRegisterErrors([])
works, there's no chance for it to not work because it's called. It triggers synchronous component update. Since onFormSubmit
doesn't exit after that, setRegisterErrors(registerErrors.concat(error))
is called after that, where registerErrors
is previous state that was defined outside onFormSubmit
.
onFormSubmit
results in 2 register state updates, where second update (concatenated original array) overrides first update (empty array).
A way to fix this is same as with setState
, to use state updater function that provides current state to be updated:
setRegisterErrors(registerErrors => [...registerErrors, error]);
Alternatively, register state updates can be merged:
e.preventDefault();
const registerErrors = [];
setUsername('JDoe');
if( isRegisterFormEmpty(username, email, password, passwordConfirmation) ) {
registerErrors.push({message: 'Please fill in all fields'});
} else {
...
}
setRegisterErrors(registerErrors);
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With