0

I was facing almost the same problem as in this question

The code is a bit too much so I have made a stripped down version of the problem. (please forgive me if I made a mistake in doing so)

Basically, I have a main component and a sub component

main component

import React {useState} from 'react'
import SubComponent from './subcomponent';

const Main = (props) => {
  const [state, setState] = useState(null);
  const updateStateFunction = (data) => {
    const newState = data
    console.log(state)
    setState(newState)
    console.log(state)
  }

  return (
    <div>
      <SubComponent
        updateStateFunction = {updateStateFunction}
      />
    </div>
  )
}


export default Main;

sub component

import React {useState} from 'react'
const SubComponent = ({updateStateFunction}) => {
  return (
    <div>
      <button
        onClick={() => updateStateFunction("Something new")}
      >       
      </button>
    </div>
  )
}
export default SubComponent;

both the console logs give null.

My attempts at a solution:

  1. Since most stack overflow answers suggested that stateupdates using hooks is asynchronous I tried using setTimeout

  2. I thought we could then use async-await but that was a wrong approach

  3. I tried updating the state inside useEffect but the point is that nothing is being re redered. This is because the variable that is being updated is never a part of an output but rather sort a helper varibale for further operations. The way I did this was using the solution in the above refereced question:

const Main = (props) => {

  /*Debugging*/
  let newState = null
  useEffect(()=>{
    console.log("useEffect called")
    setState(newState)
  }, [newState])
  /*Debugging*/

  const [state, setState] = useState(null);
  const updateStateFunction = (data) => {
    newState = data
    console.log(state)
    setState(newState)
    console.log(state)
  }

  return (
    <div>
      <SubComponent
        updateStateFunction = {updateStateFunction}
      />
    </div>
  )
}

I though since the useEffect hook is not even being executed hence I did not try the other two methods in the solution

Am I referencing the wrong type of problem or is this a common behaviour in react?

Happy to provide any more information if needed


Edit:
  1. I have added console.log() because I have operations followed by the state change that uses the value of the state variable.

  2. Using React dev tools I see that the state is updating and that too almost instantly. The problem is that the button press leads to a dialogue pop-up in the real code whose component uses the state for other logic, hence I get an error that that state is still null

nsrCodes
  • 625
  • 10
  • 25
  • setState() execution is asynchronous in react, no gurantee it will update just at next line, use carefully – Ashish Kamble Oct 13 '20 at 04:49
  • 2
    Does this answer your question? [useState set method not reflecting change immediately](https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately) @AshishKamble It is actually quite guaranteed ***not*** to ever be updated by any line within any function called within the same render cycle as the enqueued state update. – Drew Reese Oct 13 '20 at 04:52
  • @DrewReese I have specifically mentioned how this question does not answer my question. I tried all the solutions mentioned there and it still did not give the results I wanted. I thought mine is a different problem overall (considereing that there was an accepted answer in that question) so I posted a new question. – nsrCodes Oct 13 '20 at 04:53
  • 1
    It is the same issue. Your state is null, the first log, you enqueue a state update, and then log current state again, still null. It answers your question because react state updates are asynchronous and update *between* render cycles, so you can ***never*** log the state you just enqueued. This question is asked almost daily. Just queue the state update and log the new state in an useEffect with dependency on that state. – Drew Reese Oct 13 '20 at 04:55
  • @DrewReese ohh. So in short there is no alternative to this? if so I will archive this question – nsrCodes Oct 13 '20 at 05:00
  • 1
    Right, `useState` update function doesn't return a Promise so you cant await it, and `setTimeout` callbacks will simply enclose the current state. – Drew Reese Oct 13 '20 at 05:02
  • 1
    @AshishKamble it's _absolutely_ guaranteed that the next line will not reflect the updated state. `state` is a `const` reference from the closure, regardless of whether the `setState()` execution was synchronous or not. – Patrick Roberts Oct 13 '20 at 05:09

3 Answers3

1

I am not sure how let newState = null has anything to do with any of the answers in the quoted question, so to be clear, this is how one would directly apply the accepted answer:

const Main = (props) => {
  const [state, setState] = useState(null);
  const updateStateFunction = (data) => { setState(data) }

  useEffect(() => { console.log(state) }, [state])

  return <SubComponent updateStateFunction = {updateStateFunction} />
}

However, there is no point of changing a state if it's not used for anything rendered on the screen - if Reacts does not detect any change in the return value, it might not commit any change to the DOM as a part of the optimizations, so it would probably not run any useEffects in that case.

I would recommend using React Dev Tools for checking the current state of a Component.

Also, console.log is basically the only side effect which does not need to be inside useEffect. If directly in the render function, it would be executed whenever the render function is called and would not depend on React committing changes to DOM...

Note that the first advice in my answer to the quoted question does not wrap console.log into useEffect - and to be clear again, this is how one would directly apply that advice:

const Main = (props) => {
  const [state, setState] = useState(null);
  const updateStateFunction = (data) => { setState(data) }

  console.log(state)

  return <SubComponent updateStateFunction = {updateStateFunction} />
}
Aprillion
  • 21,510
  • 5
  • 55
  • 89
  • So if `useEffect` is not a solution then should I declare some sort of global variable? or use context? or something else? (P.S. I just checked using React dev tools that the state is updating and that too almost instantly. The problem is that the button press leads to a dialogue pop-up whose component uses the state for other logic, hence I get an error that that state is still null) – nsrCodes Oct 13 '20 at 09:40
  • @Geek you will have to create https://stackoverflow.com/help/minimal-reproducible-example for the ACTUAL problem you have - e.g. create a codesandbox where button press leads to a dialogue pop-up whose content used the state for other logic and it's null there... but in general ` – Aprillion Oct 13 '20 at 09:50
0

The setting of the state is asynchronous in react. An asynchronous function will be executed in parallel (well, kind of) as other instructions. Rather than console.logging after setting state, you could console.log state before the return function to know the new output.

There is nothing wrong with your first implementation. No need to use useEffect here

import React {useState} from 'react'
import SubComponent from './subcomponent';

const Main = (props) => {
  const [state, setState] = useState(null);
  const updateStateFunction = (data) => {
    setState(data)
  }
  
  // try console logging here
  console.log(state)
  return (
    <div>
      <SubComponent
        updateStateFunction = {updateStateFunction}
      />
    </div>
  )
}


export default Main;

Think of it like this, whenever the state is set, your function which contains the state gets refreshed and re-run. You could console.log anywhere in the function to get the new state.

Prateek Thapa
  • 4,829
  • 1
  • 9
  • 23
0

Use the react devtools to debug, even if the console.log() display null you see the state change in the devtools. The reason is that the state update is asynchronous.

If you still want to debug your react app using console.log() then call it just before the return statement or even in the jsx directly using curly braces.

  • The reason for console logging it there is because I have other operations followed by that state change that depend on state of that variable. – nsrCodes Oct 13 '20 at 05:04
  • You can use `useEffect` for this, its execute right after the state change. –  Oct 13 '20 at 05:42