Blog>
Snippets

Authentication Flow with useReducer

Implement an authentication flow, managing the state of user login, logout and token refresh logic with useReducer.
const initialState = {
  isAuthenticated: false,
  user: null,
  token: null,
  isLoading: false
};

const authReducer = (state, action) => {
  switch (action.type) {
    case 'REQUEST_LOGIN':
      return {
        ...state,
        isLoading: true
      };
    case 'LOGIN_SUCCESS':
      return {
        ...state,
        user: action.payload.user,
        token: action.payload.token,
        isAuthenticated: true,
        isLoading: false
      };
    case 'LOGOUT':
      return {
        ...state,
        user: null,
        token: null,
        isAuthenticated: false,
        isLoading: false
      };
    case 'LOGIN_ERROR':
      return {
        ...state,
        isLoading: false
      };
    case 'TOKEN_REFRESH':
      return {
        ...state,
        token: action.payload.token
      };
    default:
      return state;
  }
};
Defines initial state and a reducer to manage authentication states through different actions.
const login = async (dispatch, credentials) => {
  dispatch({ type: 'REQUEST_LOGIN' });
  try {
    // Replace with your actual login API call
    const response = await fakeAuthApi(credentials);
    if (response.token) {
      dispatch({ type: 'LOGIN_SUCCESS', payload: response });
      // Save token to local storage or cookie
    } else {
      dispatch({ type: 'LOGIN_ERROR' });
    }
  } catch (error) {
    dispatch({ type: 'LOGIN_ERROR' });
  }
};
This function handles the logic for initiating a login request, processing a successful authentication or handling errors.
const logout = (dispatch) => {
  dispatch({ type: 'LOGOUT' });
  // Perform additional cleanup if needed
};
A function to handle user logout, dispatching an action to update the app state.
const refreshToken = async (dispatch, currentToken) => {
  try {
    // Replace with your actual token refresh API call
    const newToken = await fakeRefreshTokenApi(currentToken);
    dispatch({ type: 'TOKEN_REFRESH', payload: { token: newToken } });
  } catch (error) {
    dispatch({ type: 'LOGIN_ERROR' });
  }
};
Function for refreshing the authentication token when it expires or is about to expire.
const App = () => {
  const [state, dispatch] = React.useReducer(authReducer, initialState);

  // Authentication context value
  const authContextValue = useMemo(() => ({
    state,
    dispatch,
    login: (credentials) => login(dispatch, credentials),
    logout: () => logout(dispatch),
    refreshToken: (currentToken) => refreshToken(dispatch, currentToken)
  }), [state]);

  // Example usage
  // const { state, login, logout } = React.useContext(AuthContext);
  // if (state.isAuthenticated) { /* Show user dashboard */ }
  // else { /* Show login form */ }

  return (
    // Your app JSX goes here
  );
};
The main App component where we make use of useReducer hook alongside context to manage authentication states and provide helper functions.
// Fake API functions for demonstration purposes
async function fakeAuthApi(credentials) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve({ user: 'John Doe', token: 'fake-jwt-token' })
    }, 1000);
  });
}

async function fakeRefreshTokenApi(currentToken) {
  return new Promise((resolve) => {
    setTimeout(() => {
      resolve('fake-new-jwt-token');
    }, 1000);
  });
}
Fake API functions to simulate network requests for login and token refresh.