I am currently struggling with refactoring my Auth on a React/Strapi Web App. The webapp in this first draft only provides some content, protected by a login.
To realise the login I post credentials to my Strapi backend and want to store the returned jwt in local storage.
I created a useAuth hook, to handle the setting, persisting and provisioning of the auth state for the application. It should check if an auth object is stored locally and if so, return it. Otherwise it should set the auth. This custom hook is being used by a login form component.
My problem now is the following: I try to navigate to the /dashboard
page after successful login using the useNavigate
hook from react-router-dom
.
Given when I comment out the statement navigate("/dashboard")
the Auth is correctly written in local storage and I can access content. The user remains on the login page (form empty) and can navigate to otherwise protected pages.
Given when the statement is present the following happens:
- The page reloads
- The auth is not written to local storage
- The user can therefore not access any protected content
- The form is emptied
I think maybe I am using useNavigate wrong or there is an interaction with my useAuth hook that I am not accounting for.
Some help here would be much appreciated as I have struggled with this for some time now :/
useAuth Hook:
import { useEffect, useState } from "react";
const AUTH_STORAGE_KEY = "my-app-auth";
export const useAuth = () => {
const [auth, setAuth] = useState(() => {
try {
const storedAuth = localStorage.getItem(AUTH_STORAGE_KEY);
return storedAuth ? JSON.parse(storedAuth) : { user: null, accessToken: null };
} catch (error) {
console.error("Error parsing stored auth:", error);
return { user: null, accessToken: null };
}
});
useAuthPersistence(auth);
return { auth, setAuth };
};
export const useAuthPersistence = (auth) => {
useEffect(() => {
try {
if (auth.accessToken) {
localStorage.setItem(AUTH_STORAGE_KEY, JSON.stringify(auth));
} else {
localStorage.removeItem(AUTH_STORAGE_KEY);
}
} catch (error) {
console.error("Error persisting auth:", error);
}
}, [auth, AUTH_STORAGE_KEY]);
};
Relevant part of Login Form Component:
import React, { useRef, useState, useEffect } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '../../hooks/useAuth.js';
export function SignInForm () {
const userRef = useRef();
const { setAuth } = useAuth();
const navigate = useNavigate();
[removed for readibility]
const handleSubmit = async (e) => {
[removed for readibility]
if(response.ok) {
const data = await response.json();
const user = data.user;
const accessToken = data.jwt;
console.log(user)
console.log(accessToken)
// Store the user and access token in the local storage
setAuth({ user, accessToken });
// Navigate to the dashboard
navigate("/dashboard");
} else if (response.status === 400) {
[removed for readibility]
2
Answers
I solved my problem with some little discord sugar:
The issue might be related to the asynchronous nature of the navigate function from react-router-dom. To address this issue, you can move the
navigate("/dashboard")
call inside auseEffect
.