15

I have a React.js project with a simple sign in function. After the user is authorized, I call history.push method which changes the link in the address bar but does not render the new component. (I use BrowserRouter)

My index.js component:

ReactDOM.render(
  <Provider store={createStore(mainReducer, applyMiddleware(thunk))}>
    <BrowserRouter>
      <Main />
    </BrowserRouter>
  </Provider>,
  document.getElementById('root')
);

My Main.js component:

const Main = (props) => {
  return (
    <Switch>
      <Route exact path="/" component={Signin} />
      <Route exact path="/servers" component={Servers} />
    </Switch>
)}

export default withRouter(Main);

My Action Creator:

export const authorization = (username, password) => (dispatch) =>
  new Promise ((resolve, reject) => {
    fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        username: username,
        password: password,
      })
    }).then( response => {
      if (response.ok) {
          response.json().then( result => {
            console.log("API reached.");
            dispatch(logUserIn(result.token));
            resolve(result);
        })
      } else {
        let error = new Error(response.statusText)
        error.response = response
        dispatch(showError(error.response.statusText), () => {throw error})
        reject(error);
      }
    });
  });

My Signin.js component:

 handleSubmit(event) {

    event.preventDefault();

    this.setState({ isLoading: true })

    const { username, password } = this.state;
    this.props.onLoginRequest(username, password, this.props.history).then(result => {
      console.log("Success. Token: "+result.token); //I do get "success" in console
      this.props.history.push('/servers') //Changes address, does not render /servers component
    });

  }

const mapActionsToProps = {
  onLoginRequest: authorization
}

The weirdest thing is that if I change my handleSubmit() method to this - everything works perfectly:

  handleSubmit(event) {

    event.preventDefault();

    this.setState({ isLoading: true })

    const { username, password } = this.state;
    this.props.onLoginRequest(username, password, this.props.history).then(result => {
      console.log("Success. Token: "+result.token);
      //this.props.history.push('/servers')
    });
    this.props.history.push('/servers')
  }

The same issue comes if I try to push history from the componentWillReceiveProps(newProps) method - it changes address but does not render new component. Could someone please explain why this happens and how to fix it?

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
zilijonas
  • 3,285
  • 3
  • 26
  • 49
  • Is your Signin component connected to Redux via connect()? – devserkan Jun 19 '18 at 10:44
  • Silly question, I looked again and you are using action creators there so must be connected. Then, next question is do you use withRouter also or not? – devserkan Jun 19 '18 at 10:47
  • @devserkan I currently use it on main.js (as you can see in the export). I've also tried it on Signin.js and Servers.js components - no luck. – zilijonas Jun 19 '18 at 10:50
  • Let's try using custom history and Router. I don't know it solves the issue for you but I am going to provide an answer. – devserkan Jun 19 '18 at 10:52
  • did this problem solved ..? i'm having same problem not working with any of the approach – AmitNayek Feb 04 '21 at 15:47

6 Answers6

13

If anyone is interested - this was happening because the app was rendering before the history was pushed. When I put the history push into my action but just before the result is converted into JSON, it started working since now it pushes history and only then renders the App.

export const authorization = (username, password, history) => (dispatch) =>
  new Promise ((resolve, reject) => {
    fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        username: username,
        password: password,
      })
    }).then( response => {
      if (response.ok) {

          //################################
          //This is where I put it

          history.push("/servers");

          //################################

          response.json().then( result => {
            dispatch(logUserIn(result.token));
            resolve(result);
        })
      } else {
        let error = new Error(response.statusText)
        error.response = response
        dispatch(showError(error.response.statusText), () => {throw error})
        reject(error);
      }
    });
  });
zilijonas
  • 3,285
  • 3
  • 26
  • 49
9

You need to apply withRouter to use this.props.history.push('/page') in every component that use "push"

import { withRouter } from 'react-router-dom';
.....
export default
        withRouter(MoneyExchange);

this is important when using push.

darkscripter
  • 425
  • 5
  • 9
  • if you do have another higher order component like i did (aws withAuthenticator) you can just wrap one around another and it will work just fine also. Something like this: export default withRouter(withAuthenticator(ListNotesPage)); – Jacck Mark Dec 04 '21 at 16:49
5

First, create a history object used the history package:

// src/history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();

Then wrap it in in Main Router Component.

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

    ReactDOM.render(
        <Provider store={store}>
          <Router history={history}>
            <Fragment>
              <Header />
              <Switch>
                <SecureRoute exact path="/" component={HomePage} />
                <Route exact path={LOGIN_PAGE} component={LoginPage} />
                <Route exact path={ERROR_PAGE} component={ErrorPage} />
              </Switch>
              <Footer />
            </Fragment>
      </Router>
    </Provider>)         

Here, After dispatching the request, redirecting to home page.

    function requestItemProcess(value) {
        return (dispatch) => {
            dispatch(request(value));
            history.push('/');
        };

    }   

should be helpful :)

Ajay Kumar
  • 4,864
  • 1
  • 41
  • 44
2

Try to use custom history and Router instead of BrowserRouter. After installing history:

yarn add history

Create a custom browser history:

import { createBrowserHistory } from "history";

export default createBrowserHistory();

Use Router instead of BrowserRouter in your setup:

import history from "your_history_file";

ReactDOM.render(
  <Provider store={createStore(mainReducer, applyMiddleware(thunk))}>
    <Router history={history}>
      <Main />
    </Router>
  </Provider>,
  document.getElementById('root')
);

or if you don't want to use a custom history file and import from there you can crate it directly in your index.js:

import { createBrowserHistory } from "history";

const history = createBrowserHistory();

ReactDOM.render(
  <Provider store={createStore(mainReducer, applyMiddleware(thunk))}>
    <Router history={history}>
      <Main />
    </Router>
  </Provider>,
  document.getElementById('root')
);
devserkan
  • 16,870
  • 4
  • 31
  • 47
  • Unfortunately, same issue with this code as well. :( – zilijonas Jun 19 '18 at 11:03
  • Weird, I solved a similar problem with this way. React+Redux and history.push. But, your problem could be different since outside of then block it is working for you. I hope you find a solution. One last thing, if possible drop withRouter from your Main app then try again with my suggestion. – devserkan Jun 19 '18 at 11:10
  • Still does not work. I believe this issue is more related to the asynchronicity of my request. – zilijonas Jun 19 '18 at 11:22
1

I using react-electron-boilerplate and i experienced a issue using MemoryRouter

using history.push('/someUrl') doesn't work...

Just use exact property in component, and initialEntries to set your default route.

    <Router initialEntries={['/']}>
      <Switch>
        <Route exact path="/" component={LoginScreen} />
        <Route exact path="/session" component={SessionScreen} />
      </Switch>
    </Router>
Knautiluz
  • 354
  • 3
  • 14
0

Not Working on this one ->

handleSubmit(event) {

    event.preventDefault();

    this.setState({ isLoading: true })

    const { username, password } = this.state;
    this.props.onLoginRequest(username, password, this.props.history).then(result => {
      console.log("Success. Token: "+result.token); //I do get "success" in console
      this.props.history.push('/servers') //Changes address, does not render /servers component
    });

  }

const mapActionsToProps = {
  onLoginRequest: authorization
}

Because in this handleSubmit method you are calling this.props.history.push() inside a promise so the this is pointing to the Promise's instance not your current class instance.

Try this One ->

 handleSubmit(event) {

    event.preventDefault();
    const { history: { push } } = this.props;
    this.setState({ isLoading: true })

    const { username, password } = this.state;
    this.props.onLoginRequest(username, password, this.props.history).then(result => {
      console.log("Success. Token: "+result.token); //I do get "success" in console
      push('/servers') //Changes address, does not render /servers component
    });
  }

const mapActionsToProps = {
  onLoginRequest: authorization
}

Now In this Statement ->

 handleSubmit(event) {

    event.preventDefault();

    this.setState({ isLoading: true })

    const { username, password } = this.state;
    this.props.onLoginRequest(username, password, this.props.history).then(result => {
      console.log("Success. Token: "+result.token);
      //this.props.history.push('/servers')
    });
    this.props.history.push('/servers')
  }

You are correctly calling this.props.history.push() since it is out of the promise and referring to the Current Class instance.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Harish Soni
  • 1,796
  • 12
  • 27
  • It then says: "push is not defined". I've also tried like this: this.props.onLoginRequest(username, password).then(result => { console.log("Success. Token: "+result.token); this.props.history.push('/servers') }); - didn't work either. – zilijonas Jun 19 '18 at 10:32
  • Did you added this line - > `const { history: { push } } = this.props;` – Harish Soni Jun 19 '18 at 10:33
  • Ugh, sorry, missed that part. Added. Now push works, but the same issue persists - address changed, success logged, servers component still does not render. And if this issue is related to promise - why does history.push not work with componentWillReceiveProps where I receive prop called loggedIn and then the push() is activated, but the exact same issue persists - new component not rendered. – zilijonas Jun 19 '18 at 10:35
  • This is a bad example to follow. It removes `this.props.history.push('/servers')` from asynchronous execution in the promise. That line will not fire regardless of whether `onLoginRequest` successfully completes. Similarly, it may execute before that promise above even resolves assuming it is making a network call. – Tim Dec 13 '19 at 05:20