Home > Software design >  React useEffect wait for Async Function
React useEffect wait for Async Function

Time:08-22

Cheers,

I have the bellow React code, where i need to send an HTTP Request to check if my actual user have permission or not to access my page. For that, im using useEffect hook to check his permission every page entry.

But my actual code does not wait for authorize() conclusion. Leading for /Unauthorized page every request. What i am doing wrong?

import React, { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";

export default function MypPage() {
  const navigate = useNavigate();
  const [authorized, setAuthorized] = useState(false);

  const authorize = useCallback(async () => {
    // it will return true/false depending user authorization
    const response = await security.authorize("AAA", "BBB");

    setAuthorized(response);
  });

  useEffect(() => {
    authorize();

    if (authorized) return;
    else return navigate("/Unauthorized");
  }, [authorize]);

  return <div>MypPage</div>;
}

Thanks.

CodePudding user response:

You are experiencing two issues:

  1. Expecting a non-awaited asynchronous function to be awaited.
  2. Expecting setState function to be synchronous.

Issue 1

authorize is asynchronous and is called within the useEffect but is not awaited. This means that the following code will be executed immediately before authorize has been completed.

Solution

A useEffect cannot be passed an asynchronous function, but a new asynchronous function can be created and then called from within the useEffect. This would allow you to create a proper asynchronous flow that works as you are expecting.

Example:

useEffect(() => {
    // create new async function to be run.
    const myNewAsyncMethod = async () => {
        // async request.
        const response = await request();

        // logic after async request.
        if (response) {
            return;
        }
    }

    // trigger new async function.
    myNewAsyncMethod(); 
}, [])

Issue 2

setState functions are not synchronous, they do not update the state value immediately. They ensure that on the next render of the component that the value will be updated. This means that accessing authorized directly after calling setAuthorized will not result in the value you are expecting.

Solution

Just use the value you passed to the setState function.

Example:

// get new value (i.e. async request, some calculation)
const newValue = true;

// set value in state.
setAuthorized(newValue);

// do further logic on new value before next render.
if (newValue) {
    // do something
}

Conclusion

Altogether you should end up with something similar too:

import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";

export default function MypPage() {
  const navigate = useNavigate();
  const [authorized, setAuthorized] = useState(false);

  useEffect(() => {
    // don't need to memoize the authorize function using `useCallback`
    // if you are only going to use it in a `useEffect`
    const authorize = async () => {
        // run async request.
        const response = await security.authorize("AAA", "BBB");

        // setAuthorized will not update authorized immediately.
        // setAuthorized will ensure that on the next render the new 
        // value is available.
        setAuthorized(response);

        // access value from response of async request as authorized 
        // will not be updated yet.
        if (!response) {
            navigate("/Unauthorized");
        }
    };

    // call the async function.
    authorize();
  }, [navigate, setAuthorized]);

  return <div>MyPage</div>;
}

CodePudding user response:

useEffect hooks do not wait for async code. Since you want to check every time you access this page, you really don't need to set a state for this.

import React, { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";

export default function MypPage() {
  const navigate = useNavigate();

  const authorize = useCallback(async () => {
    // it will return true/false depending user authorization
    const isAuthorized = await security.authorize("AAA", "BBB");
    // If the user is authorized this function will finish
    // If not, the user will be redirected.
    if(isAuthorized) return;
    navigate("/Unauthorized");
  });

  // With will only be called when the component is mounted
  useEffect(() => {
    authorize();
  }, []);

  return <div>MypPage</div>;
}

CodePudding user response:

Whenever I needed to call async-wait function in useEffect, I always did like following. I hope this would be helpful for you.

import React, { useState, useCallback } from "react";
import { useNavigate } from "react-router-dom";
import { security } from "../../services/security";

export default function MypPage() {
  const navigate = useNavigate();

  useEffect(() => {
    (async () => {
      const authorized= await security.authorize("AAA", "BBB");
      if (authorized) return;
      else return navigate("/Unauthorized");
    })()
  }, []);

  return <div>MypPage</div>;
}

CodePudding user response:

Additionally to the other answers this functioality should be hanfled on the back end. You can modify js code on the fly from the browser, the authorization functionality is better handled with file permissions on the server side.

  • Related