← Back to list

Peristent Authentication in React: How to Build a Seamless Login Experience with Redux Toolkit and…

Introduction

Basseydou OUATTARA in React redux persist · 2025-05-12 21:25 · 627 claps · 3.6 min read paywalled
#react #redux-persist #redux-toolkit #react-redux-toolkit #localstorage
Open on Medium ↗
Wiki topics: RAG · RAG & Retrieval 🌐 · Web Development

Persistent Authentication in React: How to Build a Seamless Login Experience with Redux Toolkit and Redux Persist

Introduction

One of the most frustrating experiences for users is having to log in again every time they refresh a page or reopen your web app. As developers, we want to provide a seamless experience where authentication state is preserved — even after a reload.

That’s where Redux Persist comes in. By combining Redux Toolkit, TypeScript, and Redux Persist, you can build a robust authentication flow in React that not only manages global state cleanly, but also keeps your users logged in across sessions.

In this article, I’ll show you how to structure your Redux code for authentication, how to persist the auth state securely, and how to connect everything to your React components for a smooth, professional user experience.

1. Project Structure for Redux

To keep things maintainable, I use a modular structure:

src/ redux/ features/ auth/ authActions.ts authSlice.ts authTypes.ts authSelectors.ts

  • authActions.ts: Asynchronous actions (login, signup)
  • authSlice.ts: Redux Toolkit slice (state, reducers, extraReducers)
  • authTypes.ts: TypeScript types for state and actions
  • authSelectors.ts: Selectors for accessing state

2. Asynchronous Actions with createAsyncThunk

For login, I use createAsyncThunk from Redux Toolkit. This automatically handles the pending, fulfilled, and rejected states of an async request.

// authActions.ts
export const login = createAsyncThunk(
  'auth/login',
  async (form: LoginForm, { rejectWithValue }) => {
    try {
      const credentials = mapLoginFormToDTO(form);
      const response = await axios.post<ApiResponse<UserDTO>>('/api/bkg/login', credentials);
      // ... handle response and token ...
      return { ...response.data.body, token };
    } catch (error) {
      return rejectWithValue('Login failed');
    }
  }
);

3. The Authentication Slice

The Redux slice manages the global authentication state and reacts to async actions.

// authSlice.ts
const authSlice = createSlice({
  name: 'auth',
  initialState,
  reducers: {
    logout: (state) => {
      state.token = '';
      state.isAuthenticated = false;
      // ... reset user profile ...
    },
    resetErrors: (state) => {
      state.errors = [];
    }
  },
  extraReducers: (builder) => {
    builder
      .addCase(login.pending, (state) => {
        state.loading = true;
        state.errors = [];
      })
      .addCase(login.fulfilled, (state, action) => {
        state.token = action.payload.token;
        state.isAuthenticated = true;
        state.userProfile = action.payload;
        state.loading = false;
      })
      .addCase(login.rejected, (state, action) => {
        state.loading = false;
        state.errors = action.payload as string[];
      });
  }
});

4. Using Redux in a React Component

In the LoginPage component, use useDispatch to trigger actions and useSelector to read global state.

const dispatch = useDispatch();
const isAuthenticated = useSelector(selectIsAuthenticated);
const loading = useSelector(selectAuthLoading);
const errors = useSelector(selectErrors);

const handleSubmit = (e) => {
  e.preventDefault();
  dispatch(login(form));
};

You can then display errors, loading state, and redirect the user as needed.

4. Selectors for State Access

Selectors centralize access to Redux state, making your component code cleaner.

//authSelector.ts
export const selectUserProfile = (state: RootState) => state.auth.userProfile;
export const selectIsAuthenticated = (state: RootState) => state.auth.isAuthenticated;
export const selectAuthLoading = (state: RootState) => state.auth.loading;
export const selectErrors = (state: RootState) => state.auth.errors;

5. Persisting Authentication State with Redux Persist

In many real-world apps, you want your authentication state (like the user token) to survive a page refresh. This is where redux-persist comes in handy.


//store.ts
import storage from "redux-persist/lib/storage";
import { persistReducer, persistStore } from "redux-persist";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import authReducer from "../features/auth/authSlice";

const persistConfig = {
    key: 'root',
    storage,
    whitelist: ['auth'], // Only persist the auth slice
};

const rootReducer = combineReducers({
    auth: authReducer,
    // add other reducers here
});

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
    reducer: persistedReducer,
    middleware: (getDefaultMiddleware) =>
        getDefaultMiddleware({
            serializableCheck: {
                ignoredActions: ['persist/PERSIST'],
            },
        }),
});

export const persistor = persistStore(store);

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

How It Works:

  • When a user logs in, their authentication state (token, user info, etc.) is saved in local storage.
  • On page reload, redux-persist automatically rehydrates the Redux store with the saved state.
  • This means your user stays logged in, even after a refresh!

How to Use It in Your App:

In your main entry file (e.g., index.tsx), wrap your app with the PersistGate component:

//index.tsx
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './redux/store/store';

ReactDOM.render(
  <Provider store={store}>
    <PersistGate loading={null} persistor={persistor}>
      <App />
    </PersistGate>
  </Provider>,
  document.getElementById('root')
);

6. Using Redux in a React Component

In the LoginPage component, use useDispatch to trigger actions and useSelector to read global state.

//LoginPage.tsx
const dispatch = useDispatch();
const isAuthenticated = useSelector(selectIsAuthenticated);
const loading = useSelector(selectAuthLoading);
const errors = useSelector(selectErrors);

const handleSubmit = (e) => {
  e.preventDefault();
  dispatch(login(form));
};

You can then display errors, loading state, and redirect the user as needed.

7. The Redux Authentication Flow

The user submits the login form.

  1. The component dispatches the login async action.
  2. Redux Toolkit handles the API call and the pending/fulfilled/rejected states.
  3. The slice updates the global state (token, user, errors, loading).
  4. Redux Persist saves the state in local storage.
  5. Components react automatically via useSelector.

8. Why This Architecture Works

  • Clear separation of concerns (actions, slice, selectors, types)
  • Centralized error and loading management
  • Type safety with TypeScript
  • Persistence: users stay logged in after a refresh
  • Scalability: easy to add more auth features (forgot password, etc.)

Conclusion

With Redux Toolkit, TypeScript, and Redux Persist, authentication management in React becomes:

  • simpler,
  • safer,
  • persistent,
  • and more maintainable.

Structure your code in modules, use slices and thunks, and centralize error/loading management. This will help you scale your app with confidence!

You can ask questions in the comments! This is my linkedin url : www.linkedin.com/in/bassouat8


메타데이터
post_id
fb0a7dc24efe
slug
peristent-authentication-in-react-how-to-build-a-seamless-login-experience-with-redux-toolkit-and-fb0a7dc24efe
url
https://medium.com/jdk-17-the-key-features/peristent-authentication-in-react-how-to-build-a-seamless-login-experience-with-redux-toolkit-and-fb0a7dc24efe
canonical_url
https://medium.com/jdk-17-the-key-features/peristent-authentication-in-react-how-to-build-a-seamless-login-experience-with-redux-toolkit-and-fb0a7dc24efe
author_url
https://medium.com/@bassouat8
status
ok
fetched_at
2026-07-24 00:52:46