1

I have the following layout:

<button onClick={joinSession}></button>

my Function

 const joinSession = () => {
        //set the value this will take some time
        setState({...state, session : someLibrary.initSession()})
        
        const session = state.session;
    
        //it's undefined so the code never gets here...
        session.on('event' , () => {
            console.log('hello');
        })
    
    
    }


const [state, setState] = useState({
    session: undefined,
})

The problem is that the function I call finishes running before the session state is set. I need some way to wait for the state to be set before running the rest of the code. Something like a .then would be nice. In class components you can use a second argument and pass it to setState, but i'm using functional components.

I tried using a promise :

new Promise(resolve => {
return setState({ ...state, session: OV.initSession() });
}).then(res => {
const session = ovState.session;

This didn't help..

Kevin.a
  • 4,094
  • 8
  • 46
  • 82
  • `setState` doesn't return a promise, but still happens asynchronously. In your case, you could do: `const session = someLibrary.initSession()` and used it both to update the state and to setup the event handling. – Emile Bergeron May 20 '21 at 22:06
  • 2
    you could use an useEffect that runs when that state has been updated, just add your object as a dependency – Breezer May 20 '21 at 22:07
  • Breezer is right, and it would be even better since `useEffect` has the possibility of unregistering the event whenever the session changes or the component unmounts. – Emile Bergeron May 20 '21 at 22:08
  • I have a session.on event listener, and i think that in the useEffect the function will only be run once and then it wouldnt listen to the incoming events or am i mistaken? @Breezer – Kevin.a May 20 '21 at 22:09
  • I also need to call the function on button click, will that work if the function is in an useEffect? – Kevin.a May 20 '21 at 22:10
  • `useEffect` can be configured to [run whenever its dependencies update](https://reactjs.org/docs/hooks-reference.html#conditionally-firing-an-effect). – Emile Bergeron May 20 '21 at 22:11

2 Answers2

3

I remember when I first wanted to update one of my programs to use latest react hooks, I was confused like you because I use setstate function callback a lot:

with using useEffect you can achieve the same behavior:

in your code example:

const joinSession = async () => {
    // first make sure to get session asyncrounously
    const newSession = await someLibrary.initSession();
    // now set it as new state
    setState({...state, session : newSession})
}

useEffect(()=>{
    // here we are sure that session has changed or component just loaded
    if (state.session) {
        // assuming state.session is a falsy value in initial state
        // now we are sure that session is updated here
        state.session.on('event' , () => {
            console.log('hello');
        })
    }
}, [state.session])
Mosijava
  • 3,941
  • 3
  • 27
  • 39
  • 1
    if `someLibrary.initSession()` is not asynchronous then you can skip async await section of my code and assign it to session property like you just did it before. – Mosijava May 20 '21 at 23:09
1

I don't know why you need to retrieve the updated state value there, since you can continue to use the local variable.

const joinSession = async () => {
  //set the value this will take some time
  let session = await someLibrary.initSession()
  setState((state)=>({...state, session}))
  
  session.on('event' , () => {
      console.log('hello');
  })
}

But if you still want to get the state there, you can use a custom hook from my lib (Demo):

import React, { useState } from "react";
import { useAsyncWatcher } from "use-async-effect2";

const someLibrary = {
  async initSession() {
    await new Promise((resolve) => setTimeout(resolve, 1000));
    return Math.random();
  }
};

function TestComponent(props) {
  const [state, setState] = useState({ test: 456 });
  const stateWatcher = useAsyncWatcher(state);

  const joinSession = async () => {
    let sessionValue = await someLibrary.initSession();
    setState(state=> ({ ...state, session: sessionValue }));
    const currentState = await stateWatcher(); // get the updated state value
    const session = currentState.session;

    console.log("session", session);
  };

  return (
    <div className="component">
      <div className="caption">Test</div>
      <button onClick={() => joinSession()} className="btn btn-info">
        Call async fn
      </button>
    </div>
  );
}

export default TestComponent;
Dmitriy Mozgovoy
  • 1,419
  • 2
  • 8
  • 7
  • That's a lot of extra steps just to get back `sessionValue` which is already available... – Emile Bergeron May 21 '21 at 00:43
  • @EmileBergeron In fact, this is just a few lines, but as already said, in this case, it is not necessary. Maybe this is general pseudocode that describes the issue and the author wants to do something more specific with the state, who knows. – Dmitriy Mozgovoy May 21 '21 at 11:34