Using history.push() in React Router v6 instead of useNavigate()

Loading

Migrating from history.push() to useNavigate() in React Router v6

React Router v6 introduced significant changes, replacing the history API with the useNavigate() hook. Here’s how to properly handle navigation in v6.

The Problem: history.push() in v6

// ❌ Old approach (React Router v5)
import { useHistory } from 'react-router-dom';

function MyComponent() {
  const history = useHistory();

  const handleClick = () => {
    history.push('/new-route'); // Doesn't work in v6
  };
}

Why This Changed

  1. Simplified API: useNavigate() consolidates navigation methods
  2. Hook-based: Aligns with React’s hooks ecosystem
  3. Type Safety: Better TypeScript support
  4. Relative Navigation: Improved handling of relative paths

Correct v6 Implementation

1. Basic Navigation

import { useNavigate } from 'react-router-dom';

function MyComponent() {
  const navigate = useNavigate();

  const handleClick = () => {
    navigate('/new-route'); // ✅ The new way
  };

  return <button onClick={handleClick}>Go</button>;
}

2. Equivalent Methods

v5 (history)v6 (useNavigate())
push()navigate()
replace()navigate(path, { replace: true })
goBack()navigate(-1)
goForward()navigate(1)
go(n)navigate(n)

3. State Passing

// Navigating with state
navigate('/user', {
  state: { userId: 123 },
  replace: true // Optional replace flag
});

Special Cases

1. Outside Components

For navigation outside React components (e.g., in Redux actions):

// Create a history instance (history v5 package)
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();

// Router setup
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';
import { history } from './history';

<HistoryRouter history={history}>
  <App />
</HistoryRouter>

// Usage anywhere
history.push('/somewhere');

2. Class Components

For class components, either:

Option 1: Wrap with a function component

class MyComponent extends React.Component {
  render() {
    return <Wrapper {...this.props} />;
  }
}

function Wrapper(props) {
  const navigate = useNavigate();
  return <MyComponent {...props} navigate={navigate} />;
}

Option 2: Use the navigate prop from React Router v6

<Route path="*" element={<MyComponent />} />
// MyComponent will receive navigate prop

Migration Tips

  1. Update Imports:
  • Replace useHistory with useNavigate
  • Replace history.push with navigate
  1. Search and Replace:
  • history.push(navigate(
  • history.replace(navigate(..., { replace: true })
  1. Relative Paths:
   // v5: history.push(`${match.url}/new`)
   // v6: navigate('new') // Simpler relative navigation

Common Pitfalls

  1. Forgetting to Remove history Dependency
  2. Not Updating Tests:
   // Old test mock
   jest.mock('react-router-dom', () => ({
     ...jest.requireActual('react-router-dom'),
     useHistory: () => ({ push: jest.fn() }),
   }));

   // New test mock
   jest.mock('react-router-dom', () => ({
     ...jest.requireActual('react-router-dom'),
     useNavigate: () => jest.fn(),
   }));
  1. Missing Replace Flag:
   // Old: history.replace('/login')
   // New: navigate('/login', { replace: true })

Best Practices

  1. Use useNavigate for all new code
  2. Centralize Navigation Logic in custom hooks
  3. Type the Hook in TypeScript:
   const navigate = useNavigate();
   navigate('/path' as const); // For literal type safety
  1. Handle Errors:
   try {
     navigate('/protected');
   } catch (error) {
     console.error('Navigation failed:', error);
   }

Remember: While you can still use the history package directly in v6, useNavigate() is the recommended approach for most use cases as it’s better integrated with React Router v6’s features.

Leave a Reply

Your email address will not be published. Required fields are marked *