193

I am using the last version react-router module, named react-router-dom, that has become the default when developing web applications with React. I want to know how to make a redirection after a POST request. I have been making this code, but after the request, nothing happens. I review on the web, but all the data is about previous versions of the react router, and no with the last update.

Code:

import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Redirect } from 'react-router'

import SignUpForm from '../../register/components/SignUpForm';
import styles from './PagesStyles.css';
import axios from 'axios';
import Footer from '../../shared/components/Footer';

class SignUpPage extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      errors: {},
      client: {
        userclient: '',
        clientname: '',
        clientbusinessname: '',
        password: '',
        confirmPassword: ''
      }
    };

    this.processForm = this.processForm.bind(this);
    this.changeClient = this.changeClient.bind(this);
  }

  changeClient(event) {
    const field = event.target.name;
    const client = this.state.client;
    client[field] = event.target.value;

    this.setState({
      client
    });
  }

  async processForm(event) {
    event.preventDefault();

    const userclient = this.state.client.userclient;
    const clientname = this.state.client.clientname;
    const clientbusinessname = this.state.client.clientbusinessname;
    const password = this.state.client.password;
    const confirmPassword = this.state.client.confirmPassword;
    const formData = { userclient, clientname, clientbusinessname, password, confirmPassword };

    axios.post('/signup', formData, { headers: {'Accept': 'application/json'} })
      .then((response) => {
        this.setState({
          errors: {}
        });

        <Redirect to="/"/> // Here, nothings happens
      }).catch((error) => {
        const errors = error.response.data.errors ? error.response.data.errors : {};
        errors.summary = error.response.data.message;

        this.setState({
          errors
        });
      });
  }

  render() {
    return (
      <div className={styles.section}>
        <div className={styles.container}>
          <img src={require('./images/lisa_principal_bg.png')} className={styles.fullImageBackground} />
          <SignUpForm 
            onSubmit={this.processForm}
            onChange={this.changeClient}
            errors={this.state.errors}
            client={this.state.client}
          />
          <Footer />
        </div>
      </div>
    );
  }
}

export default SignUpPage;
Rap
  • 6,851
  • 3
  • 50
  • 88
maoooricio
  • 2,249
  • 3
  • 15
  • 19
  • 1
    Your `Redirect` looks like JSX, not JS. – elmeister Apr 05 '17 at 11:50
  • can you provide you entire component code – KornholioBeavis Apr 05 '17 at 11:54
  • Yes, I am using JSX. Well, maybe i need to clarify. The POST request is inside a REACT component that makes the request. – maoooricio Apr 05 '17 at 11:55
  • @KornholioBeavis, sure, now you can see complete. I make the server with expressjs, I don't know if you need this data – maoooricio Apr 05 '17 at 12:00
  • Can you validate that you are getting a callback response from axios.post? Also why are you using async function without await anywhere? – KornholioBeavis Apr 05 '17 at 12:07
  • @KornholioBeavis, you are right, I missed to delete the word "async", because I was using it in order to receive the callback from axios in a variable, but I don't need it anymore. Yes, I am getting the response that I expect, and the response have the status OK (200) – maoooricio Apr 05 '17 at 12:14

17 Answers17

256

You have to use setState to set a property that will render the <Redirect> inside your render() method.

E.g.

class MyComponent extends React.Component {
  state = {
    redirect: false
  }

  handleSubmit () {
    axios.post(/**/)
      .then(() => this.setState({ redirect: true }));
  }

  render () {
    const { redirect } = this.state;

     if (redirect) {
       return <Redirect to='/somewhere'/>;
     }

     return <RenderYourForm/>;
}

You can also see an example in the official documentation: https://reacttraining.com/react-router/web/example/auth-workflow


That said, I would suggest you to put the API call inside a service or something. Then you could just use the history object to route programatically. This is how the integration with redux works.

But I guess you have your reasons to do it this way.

Sebastian Sebald
  • 16,130
  • 5
  • 62
  • 66
  • 1
    @sebastian sebald what do you mean by: `put the API call inside a service or something` ? – andrea-f Apr 05 '17 at 16:40
  • 1
    Having such an (async) API call inside your component will make it harder to test and reuse. It is usually better to create a service and then use it (for example) in `componentDidMount`. Or even better, create a [HOC](https://facebook.github.io/react/docs/higher-order-components.html) that "wraps" your API. – Sebastian Sebald Apr 05 '17 at 17:57
  • 7
    Pay attention that you must include Redirect to use it in begin of file: import { Redirect } from 'react-router-dom' – Alex Sep 19 '17 at 11:39
  • Is it not possible at all to redirect with a function call instead of rendering a redirect-route? – Qwerty Mar 25 '18 at 07:27
  • 3
    Yes, under the hood `Redirect` is calling `history.replace`. If you want access to the `history` obect, use `withRoutet`/`Route`. – Sebastian Sebald Mar 25 '18 at 12:19
  • 3
    `react-router` >=5.1 now includes hooks, so you can just `const history = useHistory(); history.push("/myRoute")` – Chunky Chunk Nov 19 '19 at 19:40
  • 1
    @Alex export 'Redirect' (imported as 'Redirect') was not found in 'react-router-dom' (possible exports: BrowserRouter, HashRouter, Link, MemoryRouter, NavLink, Navigate, Outlet, Route, Router, Routes, UNSAFE_LocationContext, UNSAFE_NavigationContext, UNSAFE_RouteContext, createRoutesFromChildren, createSearchParams, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, unstable_HistoryRouter, useHref, useInRouterContext, useLinkClickHandler, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes, useSearchParams) – Rony Tesler Jan 12 '22 at 19:10
47

Here a small example as response to the title as all mentioned examples are complicated in my opinion as well as the official one.

You should know how to transpile es2015 as well as make your server able to handle the redirect. Here is a snippet for express. More info related to this can be found here.

Make sure to put this below all other routes.

const app = express();
app.use(express.static('distApp'));

/**
 * Enable routing with React.
 */
app.get('*', (req, res) => {
  res.sendFile(path.resolve('distApp', 'index.html'));
});

This is the .jsx file. Notice how the longest path comes first and get's more general. For the most general routes use the exact attribute.

// Relative imports
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter, Route, Switch, Redirect } from 'react-router-dom';

// Absolute imports
import YourReactComp from './YourReactComp.jsx';

const root = document.getElementById('root');

const MainPage= () => (
  <div>Main Page</div>
);

const EditPage= () => (
  <div>Edit Page</div>
);

const NoMatch = () => (
  <p>No Match</p>
);

const RoutedApp = () => (
  <BrowserRouter >
    <Switch>
      <Route path="/items/:id" component={EditPage} />
      <Route exact path="/items" component={MainPage} />          
      <Route path="/yourReactComp" component={YourReactComp} />
      <Route exact path="/" render={() => (<Redirect to="/items" />)} />          
      <Route path="*" component={NoMatch} />          
    </Switch>
  </BrowserRouter>
);

ReactDOM.render(<RoutedApp />, root); 
Matthis Kohli
  • 1,877
  • 1
  • 21
  • 23
  • 1
    this doesn't work all the time. if you have a redirect from `home/hello` > `home/hello/1` but then go to `home/hello` and hit enter it wont redirect the first time. any ideas why?? – The Walrus Jan 05 '18 at 14:10
  • I advise you to use "create-react-app" if possible and follow the documentation from react-router. With "create-react-app" everything works fine for me. I was not able to adapt my own react application to new react-router. – Matthis Kohli Jan 07 '18 at 20:27
42

React Router v5 now allows you to simply redirect using history.push() thanks to the useHistory() hook:

import { useHistory } from "react-router-dom"

function HomeButton() {
  let history = useHistory()

  function handleClick() {
    history.push("/home")
  }

  return (
    <button type="button" onClick={handleClick}>
      Go home
    </button>
  )
}
Thanh-Quy Nguyen
  • 2,995
  • 7
  • 30
  • 46
23

Simply call it inside any function you like.

this.props.history.push('/main');
Masum Billah
  • 2,209
  • 21
  • 20
7

Try something like this.

import React, { PropTypes } from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import { Redirect } from 'react-router'

import SignUpForm from '../../register/components/SignUpForm';
import styles from './PagesStyles.css';
import axios from 'axios';
import Footer from '../../shared/components/Footer';

class SignUpPage extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      errors: {},
      callbackResponse: null,
      client: {
        userclient: '',
        clientname: '',
        clientbusinessname: '',
        password: '',
        confirmPassword: ''
      }
    };

    this.processForm = this.processForm.bind(this);
    this.changeClient = this.changeClient.bind(this);
  }

  changeClient(event) {
    const field = event.target.name;
    const client = this.state.client;
    client[field] = event.target.value;

    this.setState({
      client
    });
  }

  processForm(event) {
    event.preventDefault();

    const userclient = this.state.client.userclient;
    const clientname = this.state.client.clientname;
    const clientbusinessname = this.state.client.clientbusinessname;
    const password = this.state.client.password;
    const confirmPassword = this.state.client.confirmPassword;
    const formData = { userclient, clientname, clientbusinessname, password, confirmPassword };

    axios.post('/signup', formData, { headers: {'Accept': 'application/json'} })
      .then((response) => {
        this.setState({
          callbackResponse: {response.data},
        });
      }).catch((error) => {
        const errors = error.response.data.errors ? error.response.data.errors : {};
        errors.summary = error.response.data.message;

        this.setState({
          errors
        });
      });
  }

const renderMe = ()=>{
return(
this.state.callbackResponse
?  <SignUpForm 
            onSubmit={this.processForm}
            onChange={this.changeClient}
            errors={this.state.errors}
            client={this.state.client}
          />
: <Redirect to="/"/>
)}

  render() {
    return (
      <div className={styles.section}>
        <div className={styles.container}>
          <img src={require('./images/lisa_principal_bg.png')} className={styles.fullImageBackground} />
         {renderMe()}
          <Footer />
        </div>
      </div>
    );
  }
}

export default SignUpPage;
KornholioBeavis
  • 2,402
  • 2
  • 19
  • 26
6

Update for react-router-dom v6, there is a useNavigate hook for condtional redirection and Link component

import { useEffect } from 'react';
import { useNavigate, Link } from 'react-router-dom';

export default function Example(): JSX.Element {
  const navigate = useNavigate();

  useEffect(() => {
      ...
      if(true) { // conditional redirection
       navigate('/not-found', { replace: true });
      }
  }, []);

  return (
    <>
     ...
     <Link to="/home"> Home </Link> // relative link navigation to /home 
     ...
    </>
  );
}

useNavigate
Relative Link Component

Viraj Singh
  • 1,951
  • 1
  • 17
  • 27
  • 1
    For a more declarative approach you can use , personally I prefer the hook approach (in the answer), but felt like mentioning this as well : ) – Uttkarsh Patel Feb 09 '22 at 19:01
4

Alternatively, you can use withRouter. You can get access to the history object's properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will pass updated match, location, and history props to the wrapped component whenever it renders.

import React from "react"
import PropTypes from "prop-types"
import { withRouter } from "react-router"

// A simple component that shows the pathname of the current location
class ShowTheLocation extends React.Component {
  static propTypes = {
    match: PropTypes.object.isRequired,
    location: PropTypes.object.isRequired,
    history: PropTypes.object.isRequired
  }

  render() {
    const { match, location, history } = this.props

    return <div>You are now at {location.pathname}</div>
  }
}
// Create a new component that is "connected" (to borrow redux
// terminology) to the router.
const ShowTheLocationWithRouter = withRouter(ShowTheLocation)

Or just:

import { withRouter } from 'react-router-dom'

const Button = withRouter(({ history }) => (
  <button
    type='button'
    onClick={() => { history.push('/new-location') }}
  >
    Click Me!
  </button>
))
Ömürcan Cengiz
  • 2,085
  • 3
  • 22
  • 28
4

NOTE: Answering just the title of the question

Previous Version

<Redirect from="/old-url" to="/new-url" />

Latest version

<Route path="/old-url" element={<Navigate to="/new-url" />} />
Vijay Vepakomma
  • 733
  • 6
  • 9
3

The problem I run into is I have an existing IIS machine. I then deploy a static React app to it. When you use router, the URL that displays is actually virtual, not real. If you hit F5 it goes to IIS, not index.js, and your return will be 404 file not found. How I resolved it was simple. I have a public folder in my react app. In that public folder I created the same folder name as the virtual routing. In this folder, I have an index.html with the following code:

<script>
  {
    sessionStorage.setItem("redirect", "/ansible/");
    location.href = "/";
  }
</script>

Now what this does is for this session, I'm adding the "routing" path I want it to go. Then inside my App.js I do this (Note ... is other code but too much to put here for a demo):

import React, { Component } from "react";
import { Route, Link } from "react-router-dom";
import { BrowserRouter as Router } from "react-router-dom";
import { Redirect } from 'react-router';
import Ansible from "./Development/Ansible";
import Code from "./Development/Code";
import Wood from "./WoodWorking";
import "./App.css";

class App extends Component {
  render() {
    const redirect = sessionStorage.getItem("redirect");

    if(redirect) {
      sessionStorage.removeItem("redirect");
    }

    return (
      <Router>
        {redirect ?<Redirect to={redirect}/> : ""}
        <div className="App">
        ...
          <Link to="/">
            <li>Home</li>
          </Link>
          <Link to="/dev">
            <li>Development</li>
          </Link>
          <Link to="/wood">
            <li>Wood Working</li>
          </Link>
        ...
          <Route
            path="/"
            exact
            render={(props) => (
              <Home {...props} />
            )}
          />
          <Route
            path="/dev"
            render={(props) => (
              <Code {...props} />
            )}
          />
          <Route
            path="/wood"
            render={(props) => (
              <Wood {...props} />
            )}
          />
          <Route
            path="/ansible/"
            exact
            render={(props) => (
              <Ansible {...props} checked={this.state.checked} />
            )}
          />
          ...
      </Router>
    );
  }
}

export default App;

Actual usage: chizl.com

EDIT: changed from localStorage to sessionStorage. sessionStorage goes away when you close the tab or browser and cannot be read by other tabs in your browser.

Chizl
  • 2,004
  • 17
  • 32
3

In v6 of react-router you can accomplish this using <Navigate/> tag as there is no <Redirect/> Component.

In my case. I was required to maintain the connection to the server between /Home route and /chat route; setting window.location to something would re-render that destroys client-server connection I did this.

 <div className="home-container">
      {redirect && <Navigate to="/chat"/>}
      <div className="home__title">
         ....
      <div className="home__group-list" onClick={handleJoin}>
</div>
const [redirect, doRedirect] = useState(false)

handleJoin changes the state of redirect to true.

Sushil Kumar
  • 131
  • 2
  • 2
2
"react": "^16.3.2",
"react-dom": "^16.3.2",
"react-router-dom": "^4.2.2"

For navigate to another page (About page in my case), I installed prop-types. Then I import it in the corresponding component.And I used this.context.router.history.push('/about').And it gets navigated.

My code is,

import React, { Component } from 'react';
import '../assets/mystyle.css';
import { Redirect } from 'react-router';
import PropTypes from 'prop-types';

export default class Header extends Component {   
    viewAbout() {
       this.context.router.history.push('/about')
    }
    render() {
        return (
            <header className="App-header">
                <div className="myapp_menu">
                    <input type="button" value="Home" />
                    <input type="button" value="Services" />
                    <input type="button" value="Contact" />
                    <input type="button" value="About" onClick={() => { this.viewAbout() }} />
                </div>
            </header>
        )
    }
}
Header.contextTypes = {
    router: PropTypes.object
  };
ravibagul91
  • 20,072
  • 5
  • 36
  • 59
sojan
  • 67
  • 1
  • 10
2

you can write a hoc for this purpose and write a method call redirect, here is the code:

import React, {useState} from 'react';
import {Redirect} from "react-router-dom";

const RedirectHoc = (WrappedComponent) => () => {
    const [routName, setRoutName] = useState("");
    const redirect = (to) => {
        setRoutName(to);
    };


    if (routName) {
        return <Redirect to={"/" + routName}/>
    }
    return (
        <>
            <WrappedComponent redirect={redirect}/>
        </>
    );
};

export default RedirectHoc;
zia
  • 278
  • 1
  • 10
2

Alternatively, you can use React conditional rendering.

import { Redirect } from "react-router";
import React, { Component } from 'react';

class UserSignup extends Component {
  constructor(props) {
    super(props);
    this.state = {
      redirect: false
    }
  }
render() {
 <React.Fragment>
   { this.state.redirect && <Redirect to="/signin" /> }   // you will be redirected to signin route
}
</React.Fragment>
}
Niyongabo Eric
  • 1,333
  • 18
  • 21
  • This worked but it is required to change "react-router" to "react-router-dom", else it will throw Error: Invariant failed: You should not use outside a – Rohit Parte Oct 25 '21 at 14:18
2

Hi if you are using react-router v-6.0.0-beta or V6 in This version Redirect Changes to Navigate like this

import { Navigate } from 'react-router-dom'; // like this CORRECT in v6 import { Redirect } from 'react-router-dom'; // like this CORRECT in v5

import { Redirect } from 'react-router-dom'; // like this WRONG in v6 // This will give you error in V6 of react-router and react-router dom

please make sure use both same version in package.json { "react-router": "^6.0.0-beta.0", //Like this "react-router-dom": "^6.0.0-beta.0", // like this }

this above things only works well in react Router Version 6

Jagtar Singh
  • 171
  • 4
1

The simplest solution to navigate to another component is( Example navigates to mails component by click on icon):

<MailIcon 
  onClick={ () => { this.props.history.push('/mails') } }
/>
piet.t
  • 11,718
  • 21
  • 43
  • 52
Jackkobec
  • 5,889
  • 34
  • 34
0

To navigate to another component you can use this.props.history.push('/main');

import React, { Component, Fragment } from 'react'

class Example extends Component {

  redirect() {
    this.props.history.push('/main')
  }

  render() {
    return (
      <Fragment>
        {this.redirect()}
      </Fragment>
    );
   }
 }

 export default Example
Morris S
  • 2,337
  • 25
  • 30
  • 1
    React throws a warning: `Warning: Cannot update during an existing state transition (such as within `render`). Render methods should be a pure function of props and state.` – Robotronx Jun 25 '19 at 17:25
0

I found that place to put the redirect complent of react-router is in the method render, but if you want to redirect after some validation, by example, the best way to redirect is using the old reliable, window.location.href, i.e.:

evalSuccessResponse(data){
   if(data.code===200){
    window.location.href = urlOneSignHome;
   }else{
     //TODO Something
   }    
}

When you are programming React Native never will need to go outside of the app, and the mechanism to open another app is completely different.