Zustand and localStorage Aren't Redundant — They Solve Two Halves of Auth State
We've already solved:
- How to generate a Token
- How to send a Token
- How to verify a Token
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.