0

I want to change the state of setArray but I cant change it. The console.log shows empty array. The other states are working but not this I've used useEffect too but nothing is happening. Kindly give me a solution.

import React from 'react';

import { Link} from 'react-router-dom'

import {useEffect} from 'react';

const Register = () =>{

   const classes = useStyle();
   const [name, setName] = React.useState('');
   const [password, setPassword] = React.useState('');
   const [array, setArray] = React.useState([])



   const submit = (event) =>{
       const obj = {}
       event.preventDefault();
       if(array.length === 0){
          localStorage.setItem('name', name);
          localStorage.setItem('password',password);
          localStorage.setItem('array',JSON.stringify(array))
          obj.id = Math.random();
          obj.name = localStorage.getItem('name');
          obj.password = localStorage.getItem('password');
          setArray(array => [...array,obj])
          console.log(array);
          return alert('You are registered'); 
       }
    }

 return(
    <div>
        <div className = {classes.formWrapper}>
            <Paper elevation={3} className = {classes.paper} >
            <Typography variant="h5" style = {{ textAlign : 'center'}}>Register</Typography>
            <form noValidate autoComplete="off">
                <TextField id="username" className={classes.textfield} style={{width : '95%'}} value = {name} name = "username"  label="Username" onChange = {e=>setName(e.target.value)} />
                <br />
                <TextField id="password" className={classes.textfield} style={{width : '95%'}} value = {password} name = "password"  label="Password" onChange = {e=>setPassword(e.target.value)} />
                <br />
                <br />
                <Link to='/'>SignIn</Link>
                <Button variant="contained" color="secondary" style = {{width : '100%'}} onClick = {submit} >Register </Button>
            </form>
            </Paper>
        </div>
    </div>

  )
}

export default Register;
TGnat
  • 3,903
  • 8
  • 35
  • 46
  • 1
    You are trying to access the state through console.log( ), just after calling setState( ) . The setState is asynchronus in nature and thus it would give you previous or initialState ,if you to try to print it in console. – Piyush Mahapatra Jun 29 '21 at 12:27

2 Answers2

2

To clarify, the reason why console.log(array); logs the old array is not because setArray (or rather, it's implementation) is or is not asynchronous. It is because, simply put, it's impossible. And even if it were fully synchronous, it would still be so.


The reason:

  1. Javascript is a pass-by-value (or rather call-by-sharing), or more importantly not a pass-by-reference language. This means, that there is absolutely no way to implement setArray in such a way that it could change what is assigned to array. A dead giveaway that you shouldn't even assume this was possible, is that array is a const.

  2. Except from throwing an exception, it is impossible to prevent the execution of the remaining code after setArray(array => [...array,obj]) from inside setArray.

Also note, that this is not a react thing. It's simply a part of the language as is. So there's also no way to fix this (not that I'm saying a fix is needed).


So what is really happening? Quit simple:

const submit = (event) => { ... } forms a closure over the constant array. And thus console.log(array); must refer to that value. That setArray(array => [...array,obj]) stages a state update for the next render, and whether that process is synchronous or asynchronous, is completely irrelevant here.


So how to refer to a changed state variable?

  1. use useEffect appropriately.

  2. use the value you would want to assign as the new state where you calcualte it. E.g.:

    setArray(array => {
        const next = [...array, obj];
    
        console.log(next); // new value
    
        return next;
    });
    
    console.log(array); // *still* the old value, for the above mentioned reasons
    
Yoshi
  • 54,081
  • 14
  • 89
  • 103
0

setState is asynchronous. It means you can’t call it on one line and assume the state has changed on the next.

From React Docs.

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.

Piyush Mahapatra
  • 213
  • 2
  • 13
  • 1
    *Awaiting* a function that has `void` as it's return type makes absolutely no sense. Also it's irrelevant that the state update might or might not be asynchronous. The described problem is simply a case of a *stale closure*. – Yoshi Jun 29 '21 at 13:19
  • Can you elaborate this with an answer? By looking at the description of the question,it is clear that OP is concerned about the inability to log the state,just after mutating it with setState( ). In React docs it is clearly mentioned why it happens and there is a github thread too. https://github.com/facebook/react/issues/11527 Additionally,I tried to give a small code example to achieve what he want to. Thanks. – Piyush Mahapatra Jun 30 '21 at 05:21
  • I've posted my answer. I hope it helps. – Yoshi Jun 30 '21 at 06:54
  • If you are adamant to log the state after the setState,then try this(just to experiment,dont do in prod app): ```js async logAftersetState(){ await setState(array => [...array,obj]); console.log(array); } // Call logAfterSetState() inside your submit function ``` – Piyush Mahapatra Jun 30 '21 at 07:21