EzJS
EzJS

Reputation: 229

How do I keep state persistant using local storage, react hooks, and Context Provider

I'm implementing authentication into my app, the issue I am having is on refresh the state is not persistent so I get redirected back to the login screen. How can I use React Hooks to check if there is an authToken in local storage to keep me logged in on refresh.

This is My Protected Route That checks for auth tokens

<Route
    {...rest}
    render={
        props=>
            authTokens ? (
                <Component {...props}/>
            ) : (
                <Redirect
                    to ={
                        {
                            pathname: '/login',
                            state: {
                                from: props.location
                            }
                        }
                    }
                />

This is my Login page Function

 function Login(props) {
const [isLoggedIn, setLoggedIn] = useState(false);
const [isError, setIsError] = useState(false);
const [firstName, setUserName] = useState("");
const [password, setPassword] = useState("");
const { setAuthTokens } = useAuth();

function postLogin() {
    axios.post("http://10.6.254.22:5000/userinformation/login", {
      firstName,
      password
    }).then(result => {
      if (result.status === 200) {
        console.log('You have been logged in as user: ' +result.data[0].firstName)
        setAuthTokens(result.data);
        setLoggedIn(true);
      } else {
        setIsError(true);
      }
    }).catch(e => {
      setIsError(true);
    });
  }


  if (isLoggedIn) {
    return <Redirect to="/" />;
  }

Below is my Hook that contains the state

    const [authTokens, setAuthTokens] = useState();

  const setTokens = (data) => {
    localStorage.setItem("authTokens", JSON.stringify(data));
    setAuthTokens(data);
  }

  useEffect(()=> {
    let jsonToken = localStorage.getItem('authTokens', JSON.stringify(authTokens))
    console.log('JSON TOKEN  -----' + jsonToken)
    setAuthTokens(jsonToken)
  })

How can i set the state authTokens with the local storage variable on load so that when I refresh the state isnt empty so i dont get logged out on page reload. Also to mention The context provider is set up as so:

<AuthContext.Provider value={{ authTokens, setAuthTokens: setTokens}}>
   <ProtectedRoute path="/" exact component={ProfileBox}/>
</AuthContext.Provider>

Upvotes: 5

Views: 5760

Answers (2)

Ricardo Gonzalez
Ricardo Gonzalez

Reputation: 1879

You can create a hook that checks that the localstorage contains the token, if it doesn't exist the user isn't authenticated, so you can redirect him to logIn, else the user can access the ProtectedRoutes.

Maybe you can reuse your hook inside the ProtectedRoute component and handle it just there.

ProtectedRoute

Lets say that your component Login is the page that you want to redirect if the user isn't auth. And lets say that you are using react-router or something to handle the routes and redirections.

const ProtectedRoute = ({component, ...options}) => {
   const [isAuth ] = useAuth();
   let conditionalComponent = Login;
   if( isAuth ){
     conditionalComponent = component;
   }
   return <Route {...options} component={conditionalComponent} />
}

useAuth


const useAuth = () => {
 let [isAuth, handleAuthorized] = useState(false);

 useEffect( () => {
   if( checkLocalStorageToken() ){
    handleAuthorized( true );
   }  
   else {
    handleAuthorized(false)
   }
  return [ isAuth ]
 }, [ checkLocalStorageToken() ]);

}

I've been used this pattern and works good, i hope that i can share the main idea so you can implement it in your case.

Upvotes: 5

victor.ja
victor.ja

Reputation: 888

The problem is that authTokens is undefined when your component mounts, so the logic of your Protected route is evaluating to the Redirect portion of your code, causing the redirection to /login.

React will remove componentWillMount hook, so, the solution I found to this specific problem, is to check also for the token in the localStorage, instead of just checking for the state authTokens.

This way, your code will be able to evaluate that you have the token (even before the first component update).

The code of your Protected Route may look like this:

<Route
    {...rest}
    render={
        props=>
            (authTokens || localStorage.getItem("authTokens")) ? (
                <Component {...props}/>
            ) : (
                <Redirect
                    to ={
                        {
                            pathname: '/login',
                            state: {
                                from: props.location
                            }
                        }
                    }
                />

Other possible solutions are:

  • Using Context
  • Using Redux

But I think that if you just want to protect private routes, this solution is sufficient

Upvotes: 3

Related Questions