0

I have a scenario where an async function is called on button click and the return value is setting the state value. After that, another function is called which needs the previously set value. As it is inside function I am not able to use useEffect. How to achieve this?

const [user, setUser] = React.useState(null);

const handleSignIn = async () => {

    const result = await Google.logInAsync(config);
    const { type, idToken } = result;

    setUser(result?.user);

    if (type === "success") {

        AuthService.googleSignIn(idToken)
          .then((result) => {
              const displayName = `${user?.givenName} ${user?.familyName}`;

              signIn({
                 uid: user.uid,
                 displayName: displayName,
                 photoURL: user.photoUrl,
              });
          })
          .catch((error) => {
      });
  }
};

Here, handleSignIn is called on the button click and user state value is set from the result achieved from the Google.logInAsync. Then AuthService.googleSignIn is called and when success the user object is used there but it not available sometimes.

Hary
  • 5,690
  • 7
  • 42
  • 79
  • Does this answer your question? [useState set method not reflecting change immediately](https://stackoverflow.com/questions/54069253/usestate-set-method-not-reflecting-change-immediately) – cbr Jun 25 '20 at 21:38
  • @cbr, Please spend some time to see the question carefully before opting to close :) The function I've to call in the button click. Are functions inside `useEffect` are accessible in render?? – Hary Jun 25 '20 at 21:40
  • 1
    I linked that post because inside `if (type === "success")`, you access the `user` variable which is going to be null, because `setUser(result?.user)` will not synchronously update the value inside the function's closure. You'd want to do assign `result.user` into a local variable there and use that variable instead in the `.then` handler. What exactly do you need the useEffect for? Do you mean you want to move the functionality from inside `if (type === "success")` into a useEffect hook? – cbr Jun 25 '20 at 21:48
  • @cdr, just the local variable will suffice. Thanks – Hary Jun 25 '20 at 21:57
  • 1
    You can also just use the `result` variable from `Google.logInAsync` if you rename the `result` parameter from `AuthService.googleSignIn` so it doesn't shadow the other `result` which you want to use. – cbr Jun 25 '20 at 21:59

1 Answers1

1

cbr's comment hits the nail on the head. You need to wrap everything following setUser in its own useEffect, which will depend on the user state variable. Like this:

const [result, setResult] = React.useState(null);

const handleSignIn = async () => {

    const result = await Google.logInAsync(config);
    setUser(result);

};

useEffect( () => {

  if (result) {

    const { type, idToken, user } = result;

    if (type === "success") {

        AuthService.googleSignIn(idToken)
          .then((result) => {
              const displayName = `${user?.givenName} ${user?.familyName}`;

              signIn({
                 uid: user.uid,
                 displayName: displayName,
                 photoURL: user.photoUrl,
              });
          })
          .catch((error) => {
          });
    }

  }

}, [result])

What happens here is that your handleSignIn sets the user variable. Your useEffect runs whenever the user variable is updated. If it exists, it will run your AuthService code with the new user value.

Alternatively, you can skip using useEffect altogether by just referencing your result.user directly. Extract it from result along with the type and idToken, and use it directly. You can still save it to state with your setUser function if you need it later:

const [user, setUser] = React.useState(null);

const handleSignIn = async () => {

    const result = await Google.logInAsync(config);
    const { type, idToken, user } = result;

    setUser(user);

    if (type === "success") {

        AuthService.googleSignIn(idToken)
          .then((result) => {
              const displayName = `${user?.givenName} ${user?.familyName}`;

              signIn({
                 uid: user.uid,
                 displayName: displayName,
                 photoURL: user.photoUrl,
              });
          })
          .catch((error) => {
      });
  }
};
Seth Lutske
  • 9,154
  • 5
  • 29
  • 78
  • `const { type, idToken } = result;` is from `result` and not `const { type, idToken } = user;` – Hary Jun 25 '20 at 21:51
  • you are right. i modified things a little bit. now the state variable is the entire `result`, and you can extract the `user` from it. the concept is the same though – Seth Lutske Jun 25 '20 at 21:55
  • @cbr is the real hero here. side question: i see you are using the result?.user syntax. I've not seen that before, except in an article talking about ES20, which I wasn't aware was really being used yet. what is that syntax? – Seth Lutske Jun 25 '20 at 22:00
  • That is for preventing `null` exception. Just checking the property existence and accessing it. – Hary Jun 25 '20 at 22:04