跪拜 Guibai
← Back to the summary

Zustand and localStorage Aren't Redundant — They Solve Two Halves of Auth State

We've already solved:

But React has its own problem:

How does the page know whether the user is currently logged in?

For example, the navigation bar needs to decide whether to show:

Login

or:

Logout

The Pay page also needs to know:

Is the current user logged in?

At this point, relying solely on localStorage isn't the most comfortable solution.


1. Browser Storage and React State Are Not the Same Thing

After a successful login:

localStorage.setItem('token', token);

The browser certainly remembers the Token.

But should the React component re-render?

This is not something localStorage handles for you automatically.

React cares more about:

What is the current state?

So we need a dedicated place to manage:

token
user

That place is the Store.


2. Why Not Just Use Props?

Suppose the App has:

Nav
Login
Home
Pay

After logging in:

Login

gets:

token
user

But:

Nav
Pay
RequireAuth

also need this information.

If using Props:

App
 ↓
Nav
 ↓
...

You might need to pass it down layer by layer.

Login state clearly belongs to:

Data shared across the entire application.

So it's very suitable for a global Store.


3. Creating a Zustand Store

First, create:

src/store/

Then:

src/store/user.js

Because this Store holds:

The current user's identity and login state.

Code:

import { create } from 'zustand';

Then:

export const useAuthStore = create(set => ({
  token: '',
  user: null,

  setAuth: ({ token, user }) => {
    // ...
  },

  logout: () => {
    // ...
  }
}));

4. Why Does create(set => ({})) Look So Strange?

This is also the part of Zustand that most easily confuses beginners.

create(set => ({
  token: '',
  user: null
}))

You can first think of it as:

create() needs you to tell it "what states and state-modifying methods are in this Store".

And:

set

is the method provided by Zustand for:

Modifying the Store's state.

So:

set({
  token,
  user
});

means:

Change the token and user in the Store.


5. What to Save Inside the Store?

Current code:

export const useAuthStore = create(set => ({
  token: localStorage.getItem('token') || '',
  user: initialUser,

There are two core states here:

token
user

token indicates:

Whether there is currently a login credential.

user indicates:

Who is currently logged in.


6. Why Read from localStorage When Initializing the Store?

Because Zustand's state is essentially in memory.

If you refresh the page:

Refresh
 ↓
React restarts
 ↓
The original in-memory state disappears

But:

localStorage

does not disappear on refresh.

Therefore, when creating the Store:

token: localStorage.getItem('token') || ''

allows us to restore the previously saved Token.

The user information is similar.


7. Why Does user Need JSON.parse?

localStorage can only store strings.

When saving an object:

localStorage.setItem(
  'user',
  JSON.stringify(user)
);

When reading:

const savedUser = localStorage.getItem('user');

You get a string.

So you need:

JSON.parse(savedUser)

to turn the string back into a JavaScript object.


8. Modifying the Store After a Successful Login

Login page:

const setAuth = useAuthStore(
  state => state.setAuth
);

Login:

const res = await login(formData);

After success:

setAuth({
  token: res.token,
  user: res.user
});

The Store's:

token
user

are updated.

At the same time, setAuth() also does:

localStorage.setItem('token', token);
localStorage.setItem('user', JSON.stringify(user));

So it accomplishes two things simultaneously:

Update React's current state
+
Persist the login state

9. Why Do We Also Need logout?

Login state isn't just additive; of course, it also needs to be removable.

So:

logout: () => {
  localStorage.removeItem('token');
  localStorage.removeItem('user');

  set({
    token: '',
    user: null
  });
}

Here, it also does two things:

Clear localStorage
+
Clear Zustand

This way, the entire application returns to:

Logged-out state

10. How Can Nav Directly Know If Someone Is Logged In?

Because Nav doesn't need to check localStorage itself.

It directly uses:

const token = useAuthStore(
  state => state.token
);

Has Token:

{token && <button>Logout</button>}

No Token:

{!token && <Link to="/login">Login</Link>}

So you'll find:

Zustand allows different components to share the "current login state".

This is its core value in this project.


11. Now This Project Has Two "Places" That Hold Login State

You can separate them into two roles:

localStorage
Responsible for:
Still being able to find the Token after a page refresh

Zustand
Responsible for:
Sharing login state during React's runtime

So they are not redundant.

Rather:

One is responsible for persistence, the other for runtime state.

This sentence is well worth remembering.