4

I'm trying to change the page in a Redux Thunk action creator to take the user back "home" after they submit a form

The url changes when the action creator is invoked, but the page doesn't change

I can't use BrowserRouter beacuse I'm creating my own instance of history, but whenever I use Router I get the error

TypeError: Cannot read properties of undefined (reading 'pathname')

I tried referencing this which is exactly the same problem I'm having, but it too is unresolved. The only answer given just throws the same error but replaces pathname with createHref

Needless to say I'm extremely grateful to anyone who can get me unstuck

Here's the code:

history.js

import { createBrowserHistory } from 'history';
export default createBrowserHistory();

App.js

import React from 'react'
import { Router, Routes, Route } from 'react-router-dom'
import history from '../history'

import StreamCreate from './streams/StreamCreate'
import StreamDelete from './streams/StreamDelete'
import StreamEdit from './streams/StreamEdit'
import StreamList from './streams/StreamList'
import StreamShow from './streams/StreamShow'

import Header from './Header';

const App = () => {
    return (
        <div className="ui container">
            <Router history={history}>
                <Header />
                <Routes>
                    <Route path="/" exact element={ <StreamList /> } />
                    <Route path="/streams/new" exact element={ <StreamCreate /> } />
                    <Route path="/streams/edit" exact element={ <StreamEdit /> } />
                    <Route path="/streams/delete" exact element={ <StreamDelete /> } />
                    <Route path="/streams/show" exact element={ <StreamShow /> } />
                </Routes>
            </Router>
        </div>
    )
}

export default App

actions

import history from '../history';

export const createStream = (formValues) => async (dispatch, getState) => {
    const { userId } = getState().authReducer // userId from auth
    const { data } = await streams.post('/streams', { ...formValues, userId })

    dispatch({ type: CREATE_STREAM, payload: data })
    history.push("/")
}

5 Answers5

1

You can separate this history responsibility from push to component that dispatch this action. And after dispatch from your component, you can use the hook useNavigate() to navigate to /.

History is not working in react-router v6.

1

If you happend to migrate to react router 6 then you might need to check their migration guide

React Router v6 introduces a new navigation API that is synonymous with and provides better compatibility with suspense-enabled apps.

You can use useNavigate() hook and then have access to navigate

 let navigate = useNavigate();
Damian Busz
  • 1,693
  • 1
  • 10
  • 22
1

react router v6 doesn't support exact and history anymore.

// old - v5 <Route exact path="/" component={Home} />

// new - v6 <Route path="/" element={<Home />} />

const navigate = useNavigate();
Abbas Hussain
  • 1,277
  • 6
  • 14
1

Turns out it is still possible to maintain a global history instance so that abstracting programmatic navigation to your action creators is still possible

Happy to see you, unstable_HistoryRouter(:

import { createBrowserHistory } from 'history';
import { unstable_HistoryRouter as HistoryRouter } from 'react-router-dom';

let history = createBrowserHistory();

function App() {
  return (
    <HistoryRouter history={history}>
      // The rest of your app
    </HistoryRouter>
  );
}

history.push("/foo");
0

The error you see is because you haven't passed the correct props to the low-level Router component. There is no history prop for the react-router-dom@6 Router component.

Router

declare function Router(
  props: RouterProps
): React.ReactElement | null;

interface RouterProps {
  basename?: string;
  children?: React.ReactNode;
  location: Partial<Location> | string;
  navigationType?: NavigationType;
  navigator: Navigator;
  static?: boolean;
}

Take a cue from the way the high-level routers instantiate the history object and internalize it.

Examine the BrowserRouter implementation for example:

export function BrowserRouter({
  basename,
  children,
  window
}: BrowserRouterProps) {
  let historyRef = React.useRef<BrowserHistory>();
  if (historyRef.current == null) {
    historyRef.current = createBrowserHistory({ window });
  }

  let history = historyRef.current;
  let [state, setState] = React.useState({
    action: history.action,
    location: history.location
  });

  React.useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      basename={basename}
      children={children}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
}

Create a CustomRouter that consumes a custom history object and manages the state:

const CustomRouter = ({ history, ...props }) => {
  const [state, setState] = useState({
    action: history.action,
    location: history.location
  });

  useLayoutEffect(() => history.listen(setState), [history]);

  return (
    <Router
      {...props}
      location={state.location}
      navigationType={state.action}
      navigator={history}
    />
  );
};

This effectively proxies the custom history object into the Router and manages the navigation state.

import React from 'react';
import { Routes, Route } from 'react-router-dom';
import Router from '../CustomRouter';
import history from '../history';

import StreamCreate from './streams/StreamCreate'
import StreamDelete from './streams/StreamDelete'
import StreamEdit from './streams/StreamEdit'
import StreamList from './streams/StreamList'
import StreamShow from './streams/StreamShow'

import Header from './Header';

const App = () => {
  return (
    <div className="ui container">
      <Router history={history}>
        <Header />
        <Routes>
          <Route path="/" element={<StreamList />} />
          <Route path="/streams/new" element={<StreamCreate />} />
          <Route path="/streams/edit" element={<StreamEdit />} />
          <Route path="/streams/delete" element={<StreamDelete />} />
          <Route path="/streams/show" element={<StreamShow />} />
        </Routes>
      </Router>
    </div>
  );
}

export default App;

You should also ensure you've history@5 installed for compatibility with react-router-dom@6.

Drew Reese
  • 165,259
  • 14
  • 153
  • 181