Search code examples
javascriptreactjsreact-hookstry-catch

Why useState set function is not working when useState initial value is set to false?


I have a component in my react app called PrivateRoutes. It's just a simple component which I used in my routing structure in react-router to limit unauthorized access to my app.

In the PrivateRoutes component I use an async function to check if user JWT token is valid and then set the state of authentication to true and redirect him to home page using setState hook (because I couldn't make my validator function return anything somehow -_-).

const [auth, setAuth] = useState(true)

The above code works just fine with my app but if i use the code below:

const [auth, setAuth] = useState(false)

which makes sense to do it this way and then set different states on different scenarios But it won't work.

I also did const [auth, setAuth] = useState([]) and surprisingly it works as well as const [auth, setAuth] = useState(true)

complete code is shown below:

function PrivateRoutes() {
const [auth, setAuth] = useState(false);

    hasJWT(jwt)
    async function hasJWT(jwt) {

        try {
            const publicKey = await jose.importSPKI(spki, alg)
            await jose.jwtVerify(jwt, publicKey, {
              issuer: 'backup dashboard',
              audience: 'user',
            })
            setAuth(true)

            }
            catch(e) {
                if (e.code === 'ERR_JWT_EXPIRED') {
                    localStorage.removeItem('token')
                    console.error(e.code, e.message)
                    setAuth(false)
            }
                else {
                    localStorage.removeItem('token')
                    console.error(e.code, e.message)
                    setAuth(false)
                }
            }
        }
    return auth ? <Outlet/> : <Navigate to="/login"/>
    }
export default PrivateRoutes

It won't redirect me to Outlet component because in the first place when i do console.log(auth) it shows that the auth value has never changed and it's still false, even though i set it to true in try block.

The weird part is that the set function of useState only works if the initial value is set to true, and if i set the initial value to false it just stops working.

If I want to inform you about my problem in one line I can say: useState set function only sets the true to false but not vice versa.

I'm trying to understand the reason of this problem although my code works fine now :). any suggestion is welcome I appreciate that.

I tried using .then instead of async/await and it still didn't update the state and wouldn't work.

I tried const [auth, setAuth] = useState([]) and it worked but I don't understand why :0.

I also tried useEffect hook and it didn't work either (didn't update the state and remained on initial state).

I even tried to define useState array like let [auth, setAuth] = useState(false) to make it globally available if that's the case, but it won't work.


Solution

  • When you do a function that's async and the result will effect the render, these want to be placed inside a useEffect..

    Also until your effect is run, auth is neither true or false, so what you can do is just return, so on the first render nothing is done, you could alternatively return loading.. or similar if this can take some time.

    Something like ->

    function PrivateRoutes() {
      const [auth, setAuth] = useState();  //leave blank, unknown state.
    
      useEffect(() => {
        async function hasJWT(jwt) {
          try {
            const publicKey = await jose.importSPKI(spki, alg)
            await jose.jwtVerify(jwt, publicKey, {
              issuer: 'backup dashboard',
              audience: 'user',
            })
            setAuth(true)
          } catch (e) {
            if (e.code === 'ERR_JWT_EXPIRED') {
              localStorage.removeItem('token')
              console.error(e.code, e.message)
              setAuth(false)
            }
            else {
              localStorage.removeItem('token')
              console.error(e.code, e.message)
              setAuth(false)
            }
          }
        }
        hasJWT(jwt);
      }, [jwt]);  
    
      if (auth === undefined) return "Loading..";
    
      return auth ? <Outlet /> : <Navigate to="/login" />
    }
    
    export default PrivateRoutes