Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React useState Hook setter has no result with array variable

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;
like image 781
Peter Szrama Avatar asked Jan 12 '19 20:01

Peter Szrama


People also ask

Does useState return array?

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);

Why useState is not updating immediately?

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.

Can we use array in useState?

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() .

Does useState return anything?

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() .


1 Answers

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);
like image 141
Estus Flask Avatar answered Oct 13 '22 14:10

Estus Flask