Published: February 17 2023

React Router 6 - Navigate outside React components

Tutorial built with React 18.2 and React Router 6.8.1

This is a quick post to show how to use the React Router 6 navigate() function outside React components.

By default you can only navigate from inside React components or hook functions with the useNavigate() hook. To get around this you can create a history helper object with a navigate property and assign it with the React Router useNavigate() hook from inside a React component, then it will be accessible anywhere in the app by importing the history object and calling history.navigate().

The below code snippets are from a React + Redux tutorial I posted recently, you don't have to know Redux to follow this post, just that a Redux slice is not a React component so I needed this workaround to navigate from inside a slice. The full React + Redux tutorial is available at React 18 + Redux - Basic HTTP Authentication Example & Tutorial.

 

React Router 6 History

Path: /src/_helpers/history.js

The history helper is a plain javascript object that provides access to the React Router navigate() function and location property from anywhere in the React app including outside components. It's required in this example to enable navigation on login and logout from the Redux auth slice. The properties are initialized on app startup in the root App component with the React Router useNavigate() and useLocation() hooks.

// custom history object to allow navigation outside react components
export const history = {
    navigate: null,
    location: null
};
 

React App Component

Path: /src/App.jsx

The App component is the root component of the example app, it contains the outer html, main nav and routes for the application.

The history object is initialized at the top of the function to enable navigation and location access from outside React components.

import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';

import { history } from '_helpers';
import { Nav, PrivateRoute } from '_components';
import { Home } from 'home';
import { Login } from 'login';

export { App };

function App() {
    // init custom history object to allow navigation from 
    // anywhere in the react app (inside or outside components)
    history.navigate = useNavigate();
    history.location = useLocation();

    return (
        <div className="app-container bg-light">
            <Nav />
            <div className="container pt-4 pb-4">
                <Routes>
                    <Route
                        path="/"
                        element={
                            <PrivateRoute>
                                <Home />
                            </PrivateRoute>
                        }
                    />
                    <Route path="/login" element={<Login />} />
                    <Route path="*" element={<Navigate to="/" />} />
                </Routes>
            </div>
        </div>
    );
}
 

Redux Auth Slice

Path: /src/_store/auth.slice.js

The auth slice manages Redux state, actions and reducers for authentication.

Navigation is performed in the login() and logout() functions by calling history.navigate().

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';

import { history, fetchWrapper } from '_helpers';

// create slice

const name = 'auth';
const initialState = createInitialState();
const reducers = createReducers();
const extraActions = createExtraActions();
const extraReducers = createExtraReducers();
const slice = createSlice({ name, initialState, reducers, extraReducers });

// exports

export const authActions = { ...slice.actions, ...extraActions };
export const authReducer = slice.reducer;

// implementation

function createInitialState() {
    return {
        // initialize state from local storage to enable user to stay logged in
        user: JSON.parse(localStorage.getItem('user')),
        error: null
    }
}

function createReducers() {
    return {
        logout
    };

    function logout(state) {
        state.user = null;
        localStorage.removeItem('user');
        history.navigate('/login');
    }
}

function createExtraActions() {
    const baseUrl = `${process.env.REACT_APP_API_URL}/users`;

    return {
        login: login()
    };

    function login() {
        return createAsyncThunk(
            `${name}/login`,
            async ({ username, password }) => await fetchWrapper.post(`${baseUrl}/authenticate`, { username, password })
        );
    }
}

function createExtraReducers() {
    return (builder) => {
        login();

        function login() {
            var { pending, fulfilled, rejected } = extraActions.login;
            builder
                .addCase(pending, (state) => {
                    state.error = null;
                })
                .addCase(fulfilled, (state, action) => {
                    const user = action.payload;

                    // store user details and basic auth data in local storage to keep user logged in between page refreshes
                    localStorage.setItem('user', JSON.stringify(user));
                    state.user = user;

                    // get return url from location state or default to home page
                    const { from } = history.location.state || { from: { pathname: '/' } };
                    history.navigate(from);
                })
                .addCase(rejected, (state, action) => {
                    state.error = action.error;
                });
        }
    };
}

 


Need Some React Help?

Search fiverr for freelance React developers.


Follow me for updates

On Twitter or RSS.


When I'm not coding...

Me and Tina are on a motorcycle adventure around Australia.
Come along for the ride!


Comments


Supported by