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:
- Expecting a non-awaited asynchronous function to be awaited.
- 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.