7

On a button click, I am getting the URL changed by doing history.push()

import createHistory from 'history/createBrowserHistory'  
const history = createHistory()  
.  
. some code  
.  
history.push(`/share/${objectId}`)

and hoping the component mentioned in the Route for that URL would render but that is not happening. Though, on refreshing that component is getting rendered as expected. But I don't get that why it is not rendering as soon as the URL is changing. I've tried wrapping the component inside withRouter.

import React, {Component} from 'react'  
import {BrowserRouter, Router, Route, Switch, withRouter} from 'react-router-dom'  
import createHistory from 'history/createBrowserHistory'  

import Home from './pages/home'  
import ViewFile from './pages/view-file'  

const history = createHistory()

class App extends Component {
    constructor(props) {
      super(props)
    }

      render() {
      return (
          <BrowserRouter>
              <Switch>
                  <Route exact path={'/'} component={Home}/>
                  <Route path={'/share/:id'} component={withRouter(ViewFile)}/>
              </Switch>
          </BrowserRouter>
      )
  }
}

export default App 

as well as passing history in Router which I think same as using BrowserRouter.

import React, {Component} from 'react'  
import {BrowserRouter, Router, Route, Switch, withRouter} from 'react-router-dom'  
import createHistory from 'history/createBrowserHistory'  

import Home from './pages/home'  
import ViewFile from './pages/view-file'  

const history = createHistory()

class App extends Component {
    constructor(props) {
      super(props)
    }

      render() {
      return (
          <Router history={history}>
              <Switch>
                  <Route exact path={'/'} component={Home}/>
                  <Route path={'/share/:id'} component={ViewFile}/>
              </Switch>
          </Router>
      )
  }
}

export default App 

but not getting any luck with this. Can anyone explain why is this happening?
P.S I went through the answers here but they didn't help

Neeraj Sewani
  • 2,662
  • 4
  • 26
  • 41

7 Answers7

9

You're creating new history each time you invoke createHistory(). If you're using react-router-dom you can simply use the withRouter HOC that'll supply the history object to the component via a prop. You'll then utilize history.push('/'), or if it's in a class component, this.props.history.push('/') and so on.

Working example: https://codesandbox.io/s/p694ln9j0

routes (define history within routes)

import React from "react";
import { Router, Route, Switch } from "react-router-dom";
import { createBrowserHistory } from "history";
import Header from "../components/Header";
import Home from "../components/Home";
import About from "../components/About";
import Contact from "../components/Contact";
import ScrollIntoView from "../components/ScrollIntoView";

const history = createBrowserHistory();

export default () => (
  <Router history={history}>
    <div>
      <ScrollIntoView>
        <Header />
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Switch>
        <Header />
      </ScrollIntoView>
    </div>
  </Router>
);

components/Header.js (we want to access history, however, sometimes we have to use withRouter because components like Header don't reside within a <Route "/example" component={Header}/> HOC, so it's unaware of our routing)

import React from "react";
import { withRouter } from "react-router-dom";

const Header = ({ history }) => (
  <header>
    <nav style={{ textAlign: "center" }}>
      <ul style={{ listStyleType: "none" }}>
        <li style={{ display: "inline", marginRight: 20 }}>
          <button onClick={() => history.push("/")}>Home</button>
        </li>
        <li style={{ display: "inline", marginRight: 20 }}>
          <button onClick={() => history.push("/about")}>About</button>
        </li>
        <li style={{ display: "inline", marginRight: 20 }}>
          <button onClick={() => history.push("/contact")}>Contact</button>
        </li>
      </ul>
    </nav>
  </header>
);

export default withRouter(Header);

components/Home.js (a component like Home is aware of routing and has the history object already passed in via the <Route path="/" component={Home} /> HOC, so withRouter isn't required)

import React from "react";

const Home = ({ history }) => (
  <div className="container">
    <button
      className="uk-button uk-button-danger"
      onClick={() => history.push("/notfound")}
    >
      Not Found
    </button>
    <p>
      ...
    </p>
  </div>
);

export default Home;

Same concept as above, however, if you don't want to use withRouter, then you can simply create a history instance that'll be shared across your components that need it. You'll import this history instance and navigate with history.push('/'); and so on.

Working example: https://codesandbox.io/s/5ymj657k1k

history (define history in its own file)

import { createBrowserHistory } from "history";

const history = createBrowserHistory();

export default history;

routes (import history into routes)

import React from "react";
import { Router, Route, Switch } from "react-router-dom";
import Header from "../components/Header";
import Home from "../components/Home";
import About from "../components/About";
import Contact from "../components/Contact";
import ScrollIntoView from "../components/ScrollIntoView";
import history from "../history";

export default () => (
  <Router history={history}>
    <div>
      <ScrollIntoView>
        <Header />
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
        </Switch>
        <Header />
      </ScrollIntoView>
    </div>
  </Router>
);

components/Header.js (import history into Header)

import React from "react";
import history from "../history";

const Header = () => (
  <header>
    <nav style={{ textAlign: "center" }}>
      <ul style={{ listStyleType: "none" }}>
        <li style={{ display: "inline", marginRight: 20 }}>
          <button onClick={() => history.push("/")}>Home</button>
        </li>
        <li style={{ display: "inline", marginRight: 20 }}>
          <button onClick={() => history.push("/about")}>About</button>
        </li>
        <li style={{ display: "inline", marginRight: 20 }}>
          <button onClick={() => history.push("/contact")}>Contact</button>
        </li>
      </ul>
    </nav>
  </header>
);

export default Header;

components/Home.js (is still aware of routing and has the history object already passed in via the <Route path="/" component={Home} /> HOC, so importing history isn't required, but you can still import it if you wanted to -- just don't deconstruct history in the Home's function parameters)

import React from "react";

const Home = ({ history }) => (
  <div className="container">
    <button
      className="uk-button uk-button-danger"
      onClick={() => history.push("/notfound")}
    >
      Not Found
    </button>
    <p>
      ...
    </p>
  </div>
);

export default Home;

But you might be thinking, what about BrowserRouter? Well, BrowserRouter has it's own internal history object. We can, once again, use withRouter to access its history. In this case, we don't even need to createHistory() at all!

Working example: https://codesandbox.io/s/ko8pkzvx0o

routes

import React from "react";
import { BrowserRouter, Route, Switch } from "react-router-dom";
import Header from "../components/Header";
import Home from "../components/Home";
import About from "../components/About";
import Contact from "../components/Contact";
import Notfound from "../components/Notfound";
import ScrollIntoView from "../components/ScrollIntoView";

export default () => (
  <BrowserRouter>
    <div>
      <ScrollIntoView>
        <Header />
        <Switch>
          <Route exact path="/" component={Home} />
          <Route path="/about" component={About} />
          <Route path="/contact" component={Contact} />
          <Route component={Notfound} />
        </Switch>
        <Header />
      </ScrollIntoView>
    </div>
  </BrowserRouter>
);
Matt Carlotta
  • 13,627
  • 3
  • 24
  • 39
  • Thanks very much. I wasted hours yesterday in reading the docs of react router but didn't understand much. I think you should have written the docs. – Neeraj Sewani Mar 13 '19 at 10:28
  • It's not working, i have no idea why, i'm using `Router` not `BrowserRouter` but all my components are not rendering, the URL change but i always get the `404` fallback route component. – Hassan Azzam Jul 07 '20 at 20:11
  • 1
    @HassanAzzam Please create a [MCVE](https://stackoverflow.com/help/minimal-reproducible-example) and then [ask a new question](https://stackoverflow.com/questions/ask) with your example code. – Matt Carlotta Jul 07 '20 at 20:47
  • It's the exact same code above, and it's not working i even tried `history.listen` and it returns `undefined` when i change routes. – Hassan Azzam Jul 07 '20 at 20:49
  • 1
    @HassanAzzam It looks they may have dropped support for using your own history object in v5. When this was written, v4.x.x was the latest, and as such, it works as [expected](https://codesandbox.io/s/cranky-spence-lhczz). I'd encourage you to move away from this approach as there are other better alternatives (like using `BrowserRouter` with either the `useHistory` hook or accessing `history` with the `withRouter` higher order component if you're not using hooks) – Matt Carlotta Jul 07 '20 at 21:28
  • Or... if you're using `redux`, then use [connected-react-router](https://github.com/supasate/connected-react-router), which allows you to `push` or `replace` items in history from anywhere. – Matt Carlotta Jul 07 '20 at 21:33
  • Ya i know, but the problem is i wanted to use it outside a component, and `useHistory` only works inside a component. But anyway thanks for the clarification you saved me a lot of time. – Hassan Azzam Jul 07 '20 at 21:42
  • check version of react , react-router, react-router-dom and history package version that is also impotent – AmitNayek Feb 04 '21 at 18:02
8

use import { Router } instead of import { BrowserRouter as Router } . Because BrowserRouter ignores the history prop.

Abdullah Toufiq
  • 154
  • 1
  • 5
6

This may help someone. Make sure the history package version are compatible with the react-router-dom.

This combination didn't work for me:

history: 5.0.0 with react-router-dom: 5.2.0

This combo did:

history: 4.10.1 with react-router-dom 5.2.0

A H
  • 1,171
  • 10
  • 23
3

Few points:

1) Use import { Router} from 'react-router-dom' rather than import { BrowserRouter as Router} from 'react-router-dom'

2) Create variable history in a different file namely (can be changed) ../src/services/history.js and the content should be :

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

You would have to install the react-history package by npm install --save react-history

3) Import history into main file and pass that inside the Router component.

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


class App extends React.Component {

    render() {
        return (
            <Router history={history}>
                <Switch>
                    <Route exact path='/' component={MainPage}></Route>
                    <Route exact path='/vendor' component={VendorPage}></Route>
                    <Route exact path='/client' component={ClientPage}></Route>
                </Switch>
            </Router>
        );
    }
}

export default App;

4) Now you can use history anywhere in the components by

import React from 'react'
import history from '../services/history';

export default class VendorPage extends React.Component {

    loginUser = () => {
        if (<SOME_CONDITION>)
            history.push('/client')
    }

    render() {
        return (
            <h1>Welcome to Vendor Page....</h1>
        )
    }
}

Hope it Helps..

:)

RSRC
  • 91
  • 9
2

check version of react , react-router, react-router-dom and history package version that is also impotent – my config package.json

"history": "^4.10.1",
"react": "^17.0.1",
"react-dom": "^17.0.1",
"react-router": "5.1.2",
"react-router-dom": "5.1.2"

check this link :- https://stackblitz.com/edit/react-34ohqv?embed=1&file=package.json https://stackblitz.com/edit/react-router-example-mahi-hzitgg?embed=1&file=index.js

AmitNayek
  • 88
  • 1
  • 8
1

Downgrading history package to history: 4.10.1 with react-router-dom 5.2.0 works for me

0

For people using hooks, using useLocation() hook instead of useHistory() worked.

  const { pathname } = useLocation();

Component re-renders each time pathname changes.

Quentin C
  • 1,001
  • 8
  • 20