In the current version of React Router (v3) I can accept a server response and use browserHistory.push to go to the appropriate response page. However, this isn't available in v4, and I'm not sure what the appropriate way to handle this is.
In this example, using Redux, components/app-product-form.js calls this.props.addProduct(props) when a user submits the form. When the server returns a success, the user is taken to the Cart page.
// actions/index.js
export function addProduct(props) {
return dispatch =>
axios.post(`${ROOT_URL}/cart`, props, config)
.then(response => {
dispatch({ type: types.AUTH_USER });
localStorage.setItem('token', response.data.token);
browserHistory.push('/cart'); // no longer in React Router V4
});
}
How can I make a redirect to the Cart page from function for React Router v4?
You can use the history methods outside of your components. Try by the following way.
First, create a history object used the history package:
// src/history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Then wrap it in <Router> (please note, you should use import { Router } instead of import { BrowserRouter as Router }):
// src/index.jsx
// ...
import { Router, Route, Link } from 'react-router-dom';
import history from './history';
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/login">Login</Link></li>
</ul>
<Route exact path="/" component={HomePage} />
<Route path="/login" component={LoginPage} />
</div>
</Router>
</Provider>,
document.getElementById('root'),
);
Change your current location from any place, for example:
// src/actions/userActionCreators.js
// ...
import history from '../history';
export function login(credentials) {
return function (dispatch) {
return loginRemotely(credentials)
.then((response) => {
// ...
history.push('/');
});
};
}
UPD: You can also see a slightly different example in React Router FAQ.
React Router v4 is fundamentally different from v3 (and earlier) and you cannot do browserHistory.push() like you used to.
This discussion seems related if you want more info:
Creating a new browserHistory won't work because <BrowserRouter> creates its own history instance, and listens for changes on that. So a different instance will change the url but not update the <BrowserRouter>.
browserHistory is not exposed by react-router in v4, only in v2.
Instead you have a few options to do this:
Use the withRouter high-order component
Instead you should use the withRouter high order component, and wrap that to the component that will push to history. For example:
import React from "react";
import { withRouter } from "react-router-dom";
class MyComponent extends React.Component {
...
myFunction() {
this.props.history.push("/some/Path");
}
...
}
export default withRouter(MyComponent);
Check out the official documentation for more info:
You can get access to the history object’s properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will re-render its component every time the route changes with the same props as <Route> render props: { match, location, history }.
Use the context API
Using the context might be one of the easiest solutions, but being an experimental API it is unstable and unsupported. Use it only when everything else fails. Here's an example:
import React from "react";
import PropTypes from "prop-types";
class MyComponent extends React.Component {
static contextTypes = {
router: PropTypes.object
}
constructor(props, context) {
super(props, context);
}
...
myFunction() {
this.context.router.history.push("/some/Path");
}
...
}
Have a look at the official documentation on context:
If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.
If you insist on using context despite these warnings, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.
Now with react-router v5 you can use the useHistory hook like this:
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>
);
}
read more at: https://reacttraining.com/react-router/web/api/Hooks/usehistory
Simplest way in React Router 4 is to use
this.props.history.push('/new/url');
But to use this method, your existing component should have access to history object. We can get access by
If your component is linked to Route directly, then your component already has access to history object.
eg:
<Route path="/profile" component={ViewProfile}/>
Here ViewProfile has access to history.
If not connected to Route directly.
eg:
<Route path="/users" render={() => <ViewUsers/>}
Then we have to use withRouter, a heigher order fuction to warp the existing component.
Inside ViewUsers component
import { withRouter } from 'react-router-dom';
export default withRouter(ViewUsers);
That's it now, your ViewUsers component has access to history object.
UPDATE
2- in this scenario, pass all route props to your component, and then we can access this.props.history from the component even without a HOC
eg:
<Route path="/users" render={props => <ViewUsers {...props} />}
This is how I did it:
import React, {Component} from 'react';
export default class Link extends Component {
constructor(props) {
super(props);
this.onLogout = this.onLogout.bind(this);
}
onLogout() {
this.props.history.push('/');
}
render() {
return (
<div>
<h1>Your Links</h1>
<button onClick={this.onLogout}>Logout</button>
</div>
);
}
}
Use this.props.history.push('/cart'); to redirect to cart page it will be saved in history object.
Enjoy, Michael.
According to React Router v4 documentation - Redux Deep Integration session
Deep integration is needed to:
"be able to navigate by dispatching actions"
However, they recommend this approach as an alternative to the "deep integration":
"Rather than dispatching actions to navigate you can pass the history object provided to route components to your actions and navigate with it there."
So you can wrap your component with the withRouter high order component:
export default withRouter(connect(null, { actionCreatorName })(ReactComponent));
which will pass the history API to props. So you can call the action creator passing the history as a param. For example, inside your ReactComponent:
onClick={() => {
this.props.actionCreatorName(
this.props.history,
otherParams
);
}}
Then, inside your actions/index.js:
export function actionCreatorName(history, param) {
return dispatch => {
dispatch({
type: SOME_ACTION,
payload: param.data
});
history.push("/path");
};
}
Nasty question, took me quite a lot of time, but eventually, I solved it this way:
Wrap your container with withRouter and pass history to your action in mapDispatchToProps function. In action use history.push('/url') to navigate.
Action:
export function saveData(history, data) {
fetch.post('/save', data)
.then((response) => {
...
history.push('/url');
})
};
Container:
import { withRouter } from 'react-router-dom';
...
const mapDispatchToProps = (dispatch, ownProps) => {
return {
save: (data) => dispatch(saveData(ownProps.history, data))}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Container));
This is valid for React Router v4.x.
I offer one more solution in case it is worthful for someone else.
I have a history.js file where I have the following:
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.pushLater = (...args) => setImmediate(() => history.push(...args))
export default history
Next, on my Root where I define my router I use the following:
import history from '../history'
import { Provider } from 'react-redux'
import { Router, Route, Switch } from 'react-router-dom'
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<Router history={history}>
<Switch>
...
</Switch>
</Router>
</Provider>
)
}
}
Finally, on my actions.js I import History and make use of pushLater
import history from './history'
export const login = createAction(
...
history.pushLater({ pathname: PATH_REDIRECT_LOGIN })
...)
This way, I can push to new actions after API calls.
Hope it helps!
this.context.history.push will not work.
I managed to get push working like this:
static contextTypes = {
router: PropTypes.object
}
handleSubmit(e) {
e.preventDefault();
if (this.props.auth.success) {
this.context.router.history.push("/some/Path")
}
}
Be careful that don't use react-router#5.2.0 or react-router-dom#5.2.0 with history#5.0.0. URL will update after history.push or any other push to history instructions but navigation is not working with react-router. use npm install history#4.10.1 to change the history version. see React router not working after upgrading to v 5.
I think this problem is happening when push to history happened. for example using <NavLink to="/apps"> facing a problem in NavLink.js that consume <RouterContext.Consumer>. context.location is changing to an object with action and location properties when the push to history occurs. So currentLocation.pathname is null to match the path.
In this case you're passing props to your thunk. So you can simply call
props.history.push('/cart')
If this isn't the case you can still pass history from your component
export function addProduct(data, history) {
return dispatch => {
axios.post('/url', data).then((response) => {
dispatch({ type: types.AUTH_USER })
history.push('/cart')
})
}
}
I struggled with the same topic.
I'm using react-router-dom 5, Redux 4 and BrowserRouter.
I prefer function based components and hooks.
You define your component like this
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
const Component = () => {
...
const history = useHistory();
dispatch(myActionCreator(otherValues, history));
};
And your action creator is following
const myActionCreator = (otherValues, history) => async (dispatch) => {
...
history.push("/path");
}
You can of course have simpler action creator if async is not needed
Here's my hack (this is my root-level file, with a little redux mixed in there - though I'm not using react-router-redux):
const store = configureStore()
const customHistory = createBrowserHistory({
basename: config.urlBasename || ''
})
ReactDOM.render(
<Provider store={store}>
<Router history={customHistory}>
<Route component={({history}) => {
window.appHistory = history
return (
<App />
)
}}/>
</Router>
</Provider>,
document.getElementById('root')
)
I can then use window.appHistory.push() anywhere I want (for example, in my redux store functions/thunks/sagas, etc) I had hoped I could just use window.customHistory.push() but for some reason react-router never seemed to update even though the url changed. But this way I have the EXACT instance react-router uses. I don't love putting stuff in the global scope, and this is one of the few things I'd do that with. But it's better than any other alternative I've seen IMO.
If you are using Redux, then I would recommend using npm package react-router-redux. It allows you to dispatch Redux store navigation actions.
You have to create store as described in their Readme file.
The easiest use case:
import { push } from 'react-router-redux'
this.props.dispatch(push('/second page'));
Second use case with Container/Component:
Container:
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import Form from '../components/Form';
const mapDispatchToProps = dispatch => ({
changeUrl: url => dispatch(push(url)),
});
export default connect(null, mapDispatchToProps)(Form);
Component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Form extends Component {
handleClick = () => {
this.props.changeUrl('/secondPage');
};
render() {
return (
<div>
<button onClick={this.handleClick}/>
</div>Readme file
);
}
}
I was able to accomplish this by using bind(). I wanted to click a button in index.jsx, post some data to the server, evaluate the response, and redirect to success.jsx. Here's how I worked that out...
index.jsx:
import React, { Component } from "react"
import { postData } from "../../scripts/request"
class Main extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.postData = postData.bind(this)
}
handleClick() {
const data = {
"first_name": "Test",
"last_name": "Guy",
"email": "test#test.com"
}
this.postData("person", data)
}
render() {
return (
<div className="Main">
<button onClick={this.handleClick}>Test Post</button>
</div>
)
}
}
export default Main
request.js:
import { post } from "./fetch"
export const postData = function(url, data) {
// post is a fetch() in another script...
post(url, data)
.then((result) => {
if (result.status === "ok") {
this.props.history.push("/success")
}
})
}
success.jsx:
import React from "react"
const Success = () => {
return (
<div className="Success">
Hey cool, got it.
</div>
)
}
export default Success
So by binding this to postData in index.jsx, I was able to access this.props.history in request.js... then I can reuse this function in different components, just have to make sure I remember to include this.postData = postData.bind(this) in the constructor().
so the way I do it is:
- instead of redirecting using history.push, I just use Redirect component from react-router-dom
When using this component you can just pass push=true, and it will take care of the rest
import * as React from 'react';
import { Redirect } from 'react-router-dom';
class Example extends React.Component {
componentDidMount() {
this.setState({
redirectTo: '/test/path'
});
}
render() {
const { redirectTo } = this.state;
return <Redirect to={{pathname: redirectTo}} push={true}/>
}
}
Use Callback. It worked for me!
export function addProduct(props, callback) {
return dispatch =>
axios.post(`${ROOT_URL}/cart`, props, config)
.then(response => {
dispatch({ type: types.AUTH_USER });
localStorage.setItem('token', response.data.token);
callback();
});
}
In component, you just have to add the callback
this.props.addProduct(props, () => this.props.history.push('/cart'))
React router V4 now allows the history prop to be used as below:
this.props.history.push("/dummy",value)
The value then can be accessed wherever the location prop is available as
state:{value} not component state.
As we have a history already included in react router 5, we can access the same with reference
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
function App() {
const routerRef = React.useRef();
const onProductNav = () => {
const history = routerRef.current.history;
history.push("product");
}
return (
<BrowserRouter ref={routerRef}>
<Switch>
<Route path="/product">
<ProductComponent />
</Route>
<Route path="/">
<HomeComponent />
</Route>
</Switch>
</BrowserRouter>
)
}
step one wrap your app in Router
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));
Now my entire App will have access to BrowserRouter. Step two I import Route and then pass down those props. Probably in one of your main files.
import { Route } from "react-router-dom";
//lots of code here
//somewhere in my render function
<Route
exact
path="/" //put what your file path is here
render={props => (
<div>
<NameOfComponent
{...props} //this will pass down your match, history, location objects
/>
</div>
)}
/>
Now if I run console.log(this.props) in my component js file that I should get something that looks like this
{match: {…}, location: {…}, history: {…}, //other stuff }
Step 2 I can access the history object to change my location
//lots of code here relating to my whatever request I just ran delete, put so on
this.props.history.push("/") // then put in whatever url you want to go to
Also I'm just a coding bootcamp student, so I'm no expert, but I know you can also you use
window.location = "/" //wherever you want to go
Correct me if I'm wrong, but when I tested that out it reloaded the entire page which I thought defeated the entire point of using React.
Create a custom Router with its own browserHistory:
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
const ExtBrowserRouter = ({children}) => (
<Router history={history} >
{ children }
</Router>
);
export default ExtBrowserRouter
Next, on your Root where you define your Router, use the following:
import React from 'react';
import { /*BrowserRouter,*/ Route, Switch, Redirect } from 'react-router-dom';
//Use 'ExtBrowserRouter' instead of 'BrowserRouter'
import ExtBrowserRouter from './ExtBrowserRouter';
...
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<ExtBrowserRouter>
<Switch>
...
<Route path="/login" component={Login} />
...
</Switch>
</ExtBrowserRouter>
</Provider>
)
}
}
Finally, import history where you need it and use it:
import { history } from '../routers/ExtBrowserRouter';
...
export function logout(){
clearTokens();
history.push('/login'); //WORKS AS EXPECTED!
return Promise.reject('Refresh token has expired');
}
you can use it like this as i do it for login and manny different things
class Login extends Component {
constructor(props){
super(props);
this.login=this.login.bind(this)
}
login(){
this.props.history.push('/dashboard');
}
render() {
return (
<div>
<button onClick={this.login}>login</login>
</div>
)
/*Step 1*/
myFunction(){ this.props.history.push("/home"); }
/**/
<button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go
Home</button>
If you want to use history while passing a function as a value to a Component's prop, with react-router 4 you can simply destructure the history prop in the render attribute of the <Route/> Component and then use history.push()
<Route path='/create' render={({history}) => (
<YourComponent
YourProp={() => {
this.YourClassMethod()
history.push('/')
}}>
</YourComponent>
)} />
Note: For this to work you should wrap React Router's BrowserRouter Component around your root component (eg. which might be in index.js)
Related
I'm trying to load a details view based on a react-router-dom route that should grab the URL parameter (id) and use that to further populate the component.
My route looks like /task/:id and my component loads fine, until I try to grab the :id from the URL like so:
import React from "react";
import { useParams } from "react-router-dom";
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = useParams();
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default TaskDetail;
This triggers the following error and I'm unsure where to correctly implement useParams().
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
The docs only show examples based on functional components, not class based.
Version <= 5:
You can use withRouter to accomplish this. Simply wrap your exported classed component inside of withRouter and then you can use this.props.match.params.id to get the parameters instead of using useParams(). You can also get any location, match, or history info by using withRouter. They are all passed in under this.props
Using your example it would look like this:
import React from "react";
import { withRouter } from "react-router";
class TaskDetail extends React.Component {
componentDidMount() {
const id = this.props.match.params.id;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withRouter(TaskDetail);
Simple as that!
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return props => <Component {...props} params={useParams()} />;
}
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = this.props.params;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withParams(TaskDetail);
Since hooks wont work with class based components you can wrap it in a function and pass the properties along:
class TaskDetail extends React.Component {
componentDidMount() {
const { id } = this.props.params;
// ...
}
}
export default (props) => (
<TaskDetail
{...props}
params={useParams()}
/>
);
But, like #michael-mayo said, I expect this is what withRouter is already performing.
Params get passed down through props on the match object.
props.match.params.yourParams
source: https://redux.js.org/advanced/usage-with-react-router
Here is an example from the docs destructing the props in the arguments.
const App = ({ match: { params } }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
<Footer />
</div>
)
}
You can not call a hook such as "useParams()" from a React.Component.
Easiest way if you want to use hooks and have an existing react.component is to create a function then call the React.Component from that function and pass the parameter.
import React from 'react';
import useParams from "react-router-dom";
import TaskDetail from './TaskDetail';
function GetId() {
const { id } = useParams();
console.log(id);
return (
<div>
<TaskDetail taskId={id} />
</div>
);
}
export default GetId;
Your switch route will still be something like
<Switch>
<Route path="/task/:id" component={GetId} />
</Switch>
then you will be able to get the id from from the props in your react component
this.props.taskId
In react-router-dom-v6 you can easily use useParams() in functional components but when it gets to the class component you have to create HOC (higher-order component) because hooks don't support class components:
import { useNavigate, useParams } from "react-router-dom";
export const withRouter = (WrappedComponent) => (props) => {
const params = useParams();
const navigate = useNavigate();
return <WrappedComponent {...props} params={params} navigate={navigate} />;
};
Then export your component from your HOC and give your component as a parameter. like below:
export default withRouter(YourComponentName);
After that you can easily access the url id with this.props.params.id and you can navigate to other components with this.props.navigate("/YourPath")
React Route v5
Query params can be read and processed as JSON using withRouter and queryString as follow:
import React from "react";
import { withRouter } from "react-router";
import queryString from 'query-string';
class MyComponent extends React.Component {
componentDidMount() {
const params = queryString.parse(this.props.location.search);
console.log('Do something with it', params);
}
render() {
return <div>Hi!</div>;
}
}
export default withRouter(MyComponent);
SmujMaiku is rigth!!! His answer works perfectly. This is how work today with react-router v6
enter code here
import React ,{Component} from 'react'
import { useParams } from "react-router-dom";
import PokeDescription from '../components/PokeDescription'
class PokeInfoConteiner extends Component{
render(){
let urlPokemon= "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/"
const {idPokemon} = this.props.params
console.log(idPokemon)
return(
<div>
<PokeDescription pokeImage={`${urlPokemon}${idPokemon}.png?raw=true`}/>
<p>{}</p>
</div>
)
}
}
export default (props) => (
<PokeInfoConteiner
{...props}
params={useParams()}
/>)
in React Router V6 :
import React, {Component} from 'react';
import {useParams} from 'react-router-dom';
/* This is a higher order component that
* inject a special prop to our component.
*/
function withRouter(Component) {
function ComponentWithRouter(props) {
let params = useParams()
return <Component {...props} params={params} />
}
return ComponentWithRouter
}
class TaskDetail extends React.Component {
state={
id : ""
}
componentDidMount() {
this.setState({
id : this.props.params.id
})
}
static getDerivedStateFromProps(nextProps) {
return {
id : nextProps.params.id
}
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
const HOCTaskDetail = withRouter(TaskDetail);
export default HOCTaskDetail;
React Route v6
My friends, I tried to use in class but I failed to find any doc about it. So after many hours of searching and trying hard this is (in function). Now (i.e when I'm writing this post) there is only limited resource about v6. But there are many for <v6.
Here I'm using useState,useEffect,useParams,axios.
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
const Post = () => {
let { post_id } = useParams();
const [posts, setPosts] = useState({ post: null, countSecrets: 0, ui: '' });
useEffect(() => {
if (posts.countSecrets === 0) {
const doAxe = (a) => {
axios.get('https://jsonplaceholder.typicode.com/posts/' + post_id)
.then((res) => {
setPosts(s => ({ ...s, value: res.data }));
doUI(res.data)
// console.log(res.data)
});
}
setPosts(s => ({ ...s, countSecrets: s.countSecrets + 1 }));
doAxe()
}
}, [posts, post_id]);
let doUI = (x) => {
// console.log('x' + x.title)
const finalPost = (x !== null) ? (
<div className="post">
<h4 className="center">{x.title}</h4>
<p>{x.body}</p>
</div>
) : (
<div className="center">Loading posts...</div>
);
setPosts(s => ({ ...s, ui: finalPost }));
}
return (
<div className="container">
{posts.ui}
</div>
);
}
export default Post;
NOTE:
I faced useEffect looping. I prevented it with a key.
HOPE: This may help someone!
Reference:
using useParams
state inside function
preventing loop from useEffect
In react-router-dom v6, there is no hook such as withRouter therefore my advice to you is to convert your class-based component to a functional component to use useParams hook in your component otherwise you can create a higher-order component to pass your class-based component.
as you know the useParams() is a hook for react-router-dom.
you can not use this inside the componentDidMount() or useEffect() because both of them are method that called during the Mounting phase of the React Life-cycle i.e after the component is rendered.
you have a solution:
create or define another function outside the componentDidMount() to define useParams then call it inside the componentDidMount.
know every thing will be ok.
This is my working example. :)
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return (props) => <Component {...props} params={useParams()} />;
}
class ProductDetails extends Component {
handleSave = () => {
// Navigate to /products
};
render() {
return (
<div>
<h1>Product Details - {this.props.params.id}</h1>
<button onClick={this.handleSave}>Save</button>
</div>
);
}
}
export default withParams(ProductDetails);
Hooks only work on functional components,
you have to make that ocmponent a functional component
Fixed by creating a wrapping function
I needed to pass params to my SaxjaxApp.js from index.js using react-router-dom v6.
In v6 Switch has been changed to Routes
I got the useParams working with a class component by following Mohamed MAZEK's idea in post 20 using a wrapping function.
I needed to access the sessionId part of the url when it was available.
ie in localhost:3000/shared/123XYZId
I needed the 123XYZId part.
make note of this line : <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> in the index.js below.
:sessionId denotes that useParams has a property called sessionId, that can be accessed by:
const {sessionId} = useParams() from a functional component.
In my index.js file I did this:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./styles/style.scss";
import SaxjaxAppWrapper from "SaxjaxAppWrapper";
import SaxjaxApp from "./SaxjaxApp";
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
//INFO: to learn about react-roue-dom v6 https://reactrouter.com/en/v6.3.0/upgrading/v5
root.render(
// <React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} />
<Route path="/" element={<SaxjaxApp />} />
</Routes>
</BrowserRouter>
// </React.StrictMode>
);
This line <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> calls my wrapping function, whereas the default path / just calls the class component.
I had to create a separate file to hold the wrapping function I don't know why:
import React from "react";
import { useParams } from "react-router-dom";
import SaxjaxApp from "SaxjaxApp";
function SaxjaxAppWrapper() {
//I use the params here and store them to pass as props
let { sessionId } = useParams();
return (
//I pass the sessionId from the url params as a prop to my SaxjaxApp class component here
<SaxjaxApp sessionId={sessionId} />
);
}
export default SaxjaxAppWrapper;
My class component:
import React, { Component } from "react";
import "./styles/style.scss";
class SaxjaxApp extends Component {
state = {
octave: 4,
};
constructor(props) {
super(props);
//... initialise stuff
}
//... a lot of methods
render() {
//Access the param here
const { sessionId } = this.props;
<>
<div>
keybordId={sessionId ? sessionId : "no id was passed"}
</div>
</>
);
}
}
export default SaxjaxApp;
I'm trying to load a details view based on a react-router-dom route that should grab the URL parameter (id) and use that to further populate the component.
My route looks like /task/:id and my component loads fine, until I try to grab the :id from the URL like so:
import React from "react";
import { useParams } from "react-router-dom";
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = useParams();
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default TaskDetail;
This triggers the following error and I'm unsure where to correctly implement useParams().
Error: Invalid hook call. Hooks can only be called inside of the body of a function component.
The docs only show examples based on functional components, not class based.
Version <= 5:
You can use withRouter to accomplish this. Simply wrap your exported classed component inside of withRouter and then you can use this.props.match.params.id to get the parameters instead of using useParams(). You can also get any location, match, or history info by using withRouter. They are all passed in under this.props
Using your example it would look like this:
import React from "react";
import { withRouter } from "react-router";
class TaskDetail extends React.Component {
componentDidMount() {
const id = this.props.match.params.id;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withRouter(TaskDetail);
Simple as that!
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return props => <Component {...props} params={useParams()} />;
}
class TaskDetail extends React.Component {
componentDidMount() {
let { id } = this.props.params;
this.fetchData(id);
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
export default withParams(TaskDetail);
Since hooks wont work with class based components you can wrap it in a function and pass the properties along:
class TaskDetail extends React.Component {
componentDidMount() {
const { id } = this.props.params;
// ...
}
}
export default (props) => (
<TaskDetail
{...props}
params={useParams()}
/>
);
But, like #michael-mayo said, I expect this is what withRouter is already performing.
Params get passed down through props on the match object.
props.match.params.yourParams
source: https://redux.js.org/advanced/usage-with-react-router
Here is an example from the docs destructing the props in the arguments.
const App = ({ match: { params } }) => {
return (
<div>
<AddTodo />
<VisibleTodoList filter={params.filter || 'SHOW_ALL'} />
<Footer />
</div>
)
}
You can not call a hook such as "useParams()" from a React.Component.
Easiest way if you want to use hooks and have an existing react.component is to create a function then call the React.Component from that function and pass the parameter.
import React from 'react';
import useParams from "react-router-dom";
import TaskDetail from './TaskDetail';
function GetId() {
const { id } = useParams();
console.log(id);
return (
<div>
<TaskDetail taskId={id} />
</div>
);
}
export default GetId;
Your switch route will still be something like
<Switch>
<Route path="/task/:id" component={GetId} />
</Switch>
then you will be able to get the id from from the props in your react component
this.props.taskId
In react-router-dom-v6 you can easily use useParams() in functional components but when it gets to the class component you have to create HOC (higher-order component) because hooks don't support class components:
import { useNavigate, useParams } from "react-router-dom";
export const withRouter = (WrappedComponent) => (props) => {
const params = useParams();
const navigate = useNavigate();
return <WrappedComponent {...props} params={params} navigate={navigate} />;
};
Then export your component from your HOC and give your component as a parameter. like below:
export default withRouter(YourComponentName);
After that you can easily access the url id with this.props.params.id and you can navigate to other components with this.props.navigate("/YourPath")
React Route v5
Query params can be read and processed as JSON using withRouter and queryString as follow:
import React from "react";
import { withRouter } from "react-router";
import queryString from 'query-string';
class MyComponent extends React.Component {
componentDidMount() {
const params = queryString.parse(this.props.location.search);
console.log('Do something with it', params);
}
render() {
return <div>Hi!</div>;
}
}
export default withRouter(MyComponent);
SmujMaiku is rigth!!! His answer works perfectly. This is how work today with react-router v6
enter code here
import React ,{Component} from 'react'
import { useParams } from "react-router-dom";
import PokeDescription from '../components/PokeDescription'
class PokeInfoConteiner extends Component{
render(){
let urlPokemon= "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/"
const {idPokemon} = this.props.params
console.log(idPokemon)
return(
<div>
<PokeDescription pokeImage={`${urlPokemon}${idPokemon}.png?raw=true`}/>
<p>{}</p>
</div>
)
}
}
export default (props) => (
<PokeInfoConteiner
{...props}
params={useParams()}
/>)
in React Router V6 :
import React, {Component} from 'react';
import {useParams} from 'react-router-dom';
/* This is a higher order component that
* inject a special prop to our component.
*/
function withRouter(Component) {
function ComponentWithRouter(props) {
let params = useParams()
return <Component {...props} params={params} />
}
return ComponentWithRouter
}
class TaskDetail extends React.Component {
state={
id : ""
}
componentDidMount() {
this.setState({
id : this.props.params.id
})
}
static getDerivedStateFromProps(nextProps) {
return {
id : nextProps.params.id
}
}
fetchData = id => {
// ...
};
render() {
return <div>Yo</div>;
}
}
const HOCTaskDetail = withRouter(TaskDetail);
export default HOCTaskDetail;
React Route v6
My friends, I tried to use in class but I failed to find any doc about it. So after many hours of searching and trying hard this is (in function). Now (i.e when I'm writing this post) there is only limited resource about v6. But there are many for <v6.
Here I'm using useState,useEffect,useParams,axios.
import React, { useState, useEffect } from 'react';
import { useParams } from 'react-router-dom';
import axios from 'axios';
const Post = () => {
let { post_id } = useParams();
const [posts, setPosts] = useState({ post: null, countSecrets: 0, ui: '' });
useEffect(() => {
if (posts.countSecrets === 0) {
const doAxe = (a) => {
axios.get('https://jsonplaceholder.typicode.com/posts/' + post_id)
.then((res) => {
setPosts(s => ({ ...s, value: res.data }));
doUI(res.data)
// console.log(res.data)
});
}
setPosts(s => ({ ...s, countSecrets: s.countSecrets + 1 }));
doAxe()
}
}, [posts, post_id]);
let doUI = (x) => {
// console.log('x' + x.title)
const finalPost = (x !== null) ? (
<div className="post">
<h4 className="center">{x.title}</h4>
<p>{x.body}</p>
</div>
) : (
<div className="center">Loading posts...</div>
);
setPosts(s => ({ ...s, ui: finalPost }));
}
return (
<div className="container">
{posts.ui}
</div>
);
}
export default Post;
NOTE:
I faced useEffect looping. I prevented it with a key.
HOPE: This may help someone!
Reference:
using useParams
state inside function
preventing loop from useEffect
In react-router-dom v6, there is no hook such as withRouter therefore my advice to you is to convert your class-based component to a functional component to use useParams hook in your component otherwise you can create a higher-order component to pass your class-based component.
as you know the useParams() is a hook for react-router-dom.
you can not use this inside the componentDidMount() or useEffect() because both of them are method that called during the Mounting phase of the React Life-cycle i.e after the component is rendered.
you have a solution:
create or define another function outside the componentDidMount() to define useParams then call it inside the componentDidMount.
know every thing will be ok.
This is my working example. :)
import React, { Component } from "react";
import { useParams } from "react-router-dom";
function withParams(Component) {
return (props) => <Component {...props} params={useParams()} />;
}
class ProductDetails extends Component {
handleSave = () => {
// Navigate to /products
};
render() {
return (
<div>
<h1>Product Details - {this.props.params.id}</h1>
<button onClick={this.handleSave}>Save</button>
</div>
);
}
}
export default withParams(ProductDetails);
Hooks only work on functional components,
you have to make that ocmponent a functional component
Fixed by creating a wrapping function
I needed to pass params to my SaxjaxApp.js from index.js using react-router-dom v6.
In v6 Switch has been changed to Routes
I got the useParams working with a class component by following Mohamed MAZEK's idea in post 20 using a wrapping function.
I needed to access the sessionId part of the url when it was available.
ie in localhost:3000/shared/123XYZId
I needed the 123XYZId part.
make note of this line : <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> in the index.js below.
:sessionId denotes that useParams has a property called sessionId, that can be accessed by:
const {sessionId} = useParams() from a functional component.
In my index.js file I did this:
import React from "react";
import ReactDOM from "react-dom/client";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import "./styles/style.scss";
import SaxjaxAppWrapper from "SaxjaxAppWrapper";
import SaxjaxApp from "./SaxjaxApp";
const container = document.getElementById("root");
const root = ReactDOM.createRoot(container);
//INFO: to learn about react-roue-dom v6 https://reactrouter.com/en/v6.3.0/upgrading/v5
root.render(
// <React.StrictMode>
<BrowserRouter>
<Routes>
<Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} />
<Route path="/" element={<SaxjaxApp />} />
</Routes>
</BrowserRouter>
// </React.StrictMode>
);
This line <Route path="/shared/:sessionId" element={<SaxjaxAppWrapper />} /> calls my wrapping function, whereas the default path / just calls the class component.
I had to create a separate file to hold the wrapping function I don't know why:
import React from "react";
import { useParams } from "react-router-dom";
import SaxjaxApp from "SaxjaxApp";
function SaxjaxAppWrapper() {
//I use the params here and store them to pass as props
let { sessionId } = useParams();
return (
//I pass the sessionId from the url params as a prop to my SaxjaxApp class component here
<SaxjaxApp sessionId={sessionId} />
);
}
export default SaxjaxAppWrapper;
My class component:
import React, { Component } from "react";
import "./styles/style.scss";
class SaxjaxApp extends Component {
state = {
octave: 4,
};
constructor(props) {
super(props);
//... initialise stuff
}
//... a lot of methods
render() {
//Access the param here
const { sessionId } = this.props;
<>
<div>
keybordId={sessionId ? sessionId : "no id was passed"}
</div>
</>
);
}
}
export default SaxjaxApp;
In the current version of React Router (v3) I can accept a server response and use browserHistory.push to go to the appropriate response page. However, this isn't available in v4, and I'm not sure what the appropriate way to handle this is.
In this example, using Redux, components/app-product-form.js calls this.props.addProduct(props) when a user submits the form. When the server returns a success, the user is taken to the Cart page.
// actions/index.js
export function addProduct(props) {
return dispatch =>
axios.post(`${ROOT_URL}/cart`, props, config)
.then(response => {
dispatch({ type: types.AUTH_USER });
localStorage.setItem('token', response.data.token);
browserHistory.push('/cart'); // no longer in React Router V4
});
}
How can I make a redirect to the Cart page from function for React Router v4?
You can use the history methods outside of your components. Try by the following way.
First, create a history object used the history package:
// src/history.js
import { createBrowserHistory } from 'history';
export default createBrowserHistory();
Then wrap it in <Router> (please note, you should use import { Router } instead of import { BrowserRouter as Router }):
// src/index.jsx
// ...
import { Router, Route, Link } from 'react-router-dom';
import history from './history';
ReactDOM.render(
<Provider store={store}>
<Router history={history}>
<div>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="/login">Login</Link></li>
</ul>
<Route exact path="/" component={HomePage} />
<Route path="/login" component={LoginPage} />
</div>
</Router>
</Provider>,
document.getElementById('root'),
);
Change your current location from any place, for example:
// src/actions/userActionCreators.js
// ...
import history from '../history';
export function login(credentials) {
return function (dispatch) {
return loginRemotely(credentials)
.then((response) => {
// ...
history.push('/');
});
};
}
UPD: You can also see a slightly different example in React Router FAQ.
React Router v4 is fundamentally different from v3 (and earlier) and you cannot do browserHistory.push() like you used to.
This discussion seems related if you want more info:
Creating a new browserHistory won't work because <BrowserRouter> creates its own history instance, and listens for changes on that. So a different instance will change the url but not update the <BrowserRouter>.
browserHistory is not exposed by react-router in v4, only in v2.
Instead you have a few options to do this:
Use the withRouter high-order component
Instead you should use the withRouter high order component, and wrap that to the component that will push to history. For example:
import React from "react";
import { withRouter } from "react-router-dom";
class MyComponent extends React.Component {
...
myFunction() {
this.props.history.push("/some/Path");
}
...
}
export default withRouter(MyComponent);
Check out the official documentation for more info:
You can get access to the history object’s properties and the closest <Route>'s match via the withRouter higher-order component. withRouter will re-render its component every time the route changes with the same props as <Route> render props: { match, location, history }.
Use the context API
Using the context might be one of the easiest solutions, but being an experimental API it is unstable and unsupported. Use it only when everything else fails. Here's an example:
import React from "react";
import PropTypes from "prop-types";
class MyComponent extends React.Component {
static contextTypes = {
router: PropTypes.object
}
constructor(props, context) {
super(props, context);
}
...
myFunction() {
this.context.router.history.push("/some/Path");
}
...
}
Have a look at the official documentation on context:
If you want your application to be stable, don't use context. It is an experimental API and it is likely to break in future releases of React.
If you insist on using context despite these warnings, try to isolate your use of context to a small area and avoid using the context API directly when possible so that it's easier to upgrade when the API changes.
Now with react-router v5 you can use the useHistory hook like this:
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>
);
}
read more at: https://reacttraining.com/react-router/web/api/Hooks/usehistory
Simplest way in React Router 4 is to use
this.props.history.push('/new/url');
But to use this method, your existing component should have access to history object. We can get access by
If your component is linked to Route directly, then your component already has access to history object.
eg:
<Route path="/profile" component={ViewProfile}/>
Here ViewProfile has access to history.
If not connected to Route directly.
eg:
<Route path="/users" render={() => <ViewUsers/>}
Then we have to use withRouter, a heigher order fuction to warp the existing component.
Inside ViewUsers component
import { withRouter } from 'react-router-dom';
export default withRouter(ViewUsers);
That's it now, your ViewUsers component has access to history object.
UPDATE
2- in this scenario, pass all route props to your component, and then we can access this.props.history from the component even without a HOC
eg:
<Route path="/users" render={props => <ViewUsers {...props} />}
This is how I did it:
import React, {Component} from 'react';
export default class Link extends Component {
constructor(props) {
super(props);
this.onLogout = this.onLogout.bind(this);
}
onLogout() {
this.props.history.push('/');
}
render() {
return (
<div>
<h1>Your Links</h1>
<button onClick={this.onLogout}>Logout</button>
</div>
);
}
}
Use this.props.history.push('/cart'); to redirect to cart page it will be saved in history object.
Enjoy, Michael.
According to React Router v4 documentation - Redux Deep Integration session
Deep integration is needed to:
"be able to navigate by dispatching actions"
However, they recommend this approach as an alternative to the "deep integration":
"Rather than dispatching actions to navigate you can pass the history object provided to route components to your actions and navigate with it there."
So you can wrap your component with the withRouter high order component:
export default withRouter(connect(null, { actionCreatorName })(ReactComponent));
which will pass the history API to props. So you can call the action creator passing the history as a param. For example, inside your ReactComponent:
onClick={() => {
this.props.actionCreatorName(
this.props.history,
otherParams
);
}}
Then, inside your actions/index.js:
export function actionCreatorName(history, param) {
return dispatch => {
dispatch({
type: SOME_ACTION,
payload: param.data
});
history.push("/path");
};
}
Nasty question, took me quite a lot of time, but eventually, I solved it this way:
Wrap your container with withRouter and pass history to your action in mapDispatchToProps function. In action use history.push('/url') to navigate.
Action:
export function saveData(history, data) {
fetch.post('/save', data)
.then((response) => {
...
history.push('/url');
})
};
Container:
import { withRouter } from 'react-router-dom';
...
const mapDispatchToProps = (dispatch, ownProps) => {
return {
save: (data) => dispatch(saveData(ownProps.history, data))}
};
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Container));
This is valid for React Router v4.x.
I offer one more solution in case it is worthful for someone else.
I have a history.js file where I have the following:
import createHistory from 'history/createBrowserHistory'
const history = createHistory()
history.pushLater = (...args) => setImmediate(() => history.push(...args))
export default history
Next, on my Root where I define my router I use the following:
import history from '../history'
import { Provider } from 'react-redux'
import { Router, Route, Switch } from 'react-router-dom'
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<Router history={history}>
<Switch>
...
</Switch>
</Router>
</Provider>
)
}
}
Finally, on my actions.js I import History and make use of pushLater
import history from './history'
export const login = createAction(
...
history.pushLater({ pathname: PATH_REDIRECT_LOGIN })
...)
This way, I can push to new actions after API calls.
Hope it helps!
this.context.history.push will not work.
I managed to get push working like this:
static contextTypes = {
router: PropTypes.object
}
handleSubmit(e) {
e.preventDefault();
if (this.props.auth.success) {
this.context.router.history.push("/some/Path")
}
}
Be careful that don't use react-router#5.2.0 or react-router-dom#5.2.0 with history#5.0.0. URL will update after history.push or any other push to history instructions but navigation is not working with react-router. use npm install history#4.10.1 to change the history version. see React router not working after upgrading to v 5.
I think this problem is happening when push to history happened. for example using <NavLink to="/apps"> facing a problem in NavLink.js that consume <RouterContext.Consumer>. context.location is changing to an object with action and location properties when the push to history occurs. So currentLocation.pathname is null to match the path.
In this case you're passing props to your thunk. So you can simply call
props.history.push('/cart')
If this isn't the case you can still pass history from your component
export function addProduct(data, history) {
return dispatch => {
axios.post('/url', data).then((response) => {
dispatch({ type: types.AUTH_USER })
history.push('/cart')
})
}
}
I struggled with the same topic.
I'm using react-router-dom 5, Redux 4 and BrowserRouter.
I prefer function based components and hooks.
You define your component like this
import { useHistory } from "react-router-dom";
import { useDispatch } from "react-redux";
const Component = () => {
...
const history = useHistory();
dispatch(myActionCreator(otherValues, history));
};
And your action creator is following
const myActionCreator = (otherValues, history) => async (dispatch) => {
...
history.push("/path");
}
You can of course have simpler action creator if async is not needed
Here's my hack (this is my root-level file, with a little redux mixed in there - though I'm not using react-router-redux):
const store = configureStore()
const customHistory = createBrowserHistory({
basename: config.urlBasename || ''
})
ReactDOM.render(
<Provider store={store}>
<Router history={customHistory}>
<Route component={({history}) => {
window.appHistory = history
return (
<App />
)
}}/>
</Router>
</Provider>,
document.getElementById('root')
)
I can then use window.appHistory.push() anywhere I want (for example, in my redux store functions/thunks/sagas, etc) I had hoped I could just use window.customHistory.push() but for some reason react-router never seemed to update even though the url changed. But this way I have the EXACT instance react-router uses. I don't love putting stuff in the global scope, and this is one of the few things I'd do that with. But it's better than any other alternative I've seen IMO.
If you are using Redux, then I would recommend using npm package react-router-redux. It allows you to dispatch Redux store navigation actions.
You have to create store as described in their Readme file.
The easiest use case:
import { push } from 'react-router-redux'
this.props.dispatch(push('/second page'));
Second use case with Container/Component:
Container:
import { connect } from 'react-redux';
import { push } from 'react-router-redux';
import Form from '../components/Form';
const mapDispatchToProps = dispatch => ({
changeUrl: url => dispatch(push(url)),
});
export default connect(null, mapDispatchToProps)(Form);
Component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
export default class Form extends Component {
handleClick = () => {
this.props.changeUrl('/secondPage');
};
render() {
return (
<div>
<button onClick={this.handleClick}/>
</div>Readme file
);
}
}
I was able to accomplish this by using bind(). I wanted to click a button in index.jsx, post some data to the server, evaluate the response, and redirect to success.jsx. Here's how I worked that out...
index.jsx:
import React, { Component } from "react"
import { postData } from "../../scripts/request"
class Main extends Component {
constructor(props) {
super(props)
this.handleClick = this.handleClick.bind(this)
this.postData = postData.bind(this)
}
handleClick() {
const data = {
"first_name": "Test",
"last_name": "Guy",
"email": "test#test.com"
}
this.postData("person", data)
}
render() {
return (
<div className="Main">
<button onClick={this.handleClick}>Test Post</button>
</div>
)
}
}
export default Main
request.js:
import { post } from "./fetch"
export const postData = function(url, data) {
// post is a fetch() in another script...
post(url, data)
.then((result) => {
if (result.status === "ok") {
this.props.history.push("/success")
}
})
}
success.jsx:
import React from "react"
const Success = () => {
return (
<div className="Success">
Hey cool, got it.
</div>
)
}
export default Success
So by binding this to postData in index.jsx, I was able to access this.props.history in request.js... then I can reuse this function in different components, just have to make sure I remember to include this.postData = postData.bind(this) in the constructor().
so the way I do it is:
- instead of redirecting using history.push, I just use Redirect component from react-router-dom
When using this component you can just pass push=true, and it will take care of the rest
import * as React from 'react';
import { Redirect } from 'react-router-dom';
class Example extends React.Component {
componentDidMount() {
this.setState({
redirectTo: '/test/path'
});
}
render() {
const { redirectTo } = this.state;
return <Redirect to={{pathname: redirectTo}} push={true}/>
}
}
Use Callback. It worked for me!
export function addProduct(props, callback) {
return dispatch =>
axios.post(`${ROOT_URL}/cart`, props, config)
.then(response => {
dispatch({ type: types.AUTH_USER });
localStorage.setItem('token', response.data.token);
callback();
});
}
In component, you just have to add the callback
this.props.addProduct(props, () => this.props.history.push('/cart'))
React router V4 now allows the history prop to be used as below:
this.props.history.push("/dummy",value)
The value then can be accessed wherever the location prop is available as
state:{value} not component state.
As we have a history already included in react router 5, we can access the same with reference
import React from 'react';
import { BrowserRouter, Switch, Route } from 'react-router-dom';
function App() {
const routerRef = React.useRef();
const onProductNav = () => {
const history = routerRef.current.history;
history.push("product");
}
return (
<BrowserRouter ref={routerRef}>
<Switch>
<Route path="/product">
<ProductComponent />
</Route>
<Route path="/">
<HomeComponent />
</Route>
</Switch>
</BrowserRouter>
)
}
step one wrap your app in Router
import { BrowserRouter as Router } from "react-router-dom";
ReactDOM.render(<Router><App /></Router>, document.getElementById('root'));
Now my entire App will have access to BrowserRouter. Step two I import Route and then pass down those props. Probably in one of your main files.
import { Route } from "react-router-dom";
//lots of code here
//somewhere in my render function
<Route
exact
path="/" //put what your file path is here
render={props => (
<div>
<NameOfComponent
{...props} //this will pass down your match, history, location objects
/>
</div>
)}
/>
Now if I run console.log(this.props) in my component js file that I should get something that looks like this
{match: {…}, location: {…}, history: {…}, //other stuff }
Step 2 I can access the history object to change my location
//lots of code here relating to my whatever request I just ran delete, put so on
this.props.history.push("/") // then put in whatever url you want to go to
Also I'm just a coding bootcamp student, so I'm no expert, but I know you can also you use
window.location = "/" //wherever you want to go
Correct me if I'm wrong, but when I tested that out it reloaded the entire page which I thought defeated the entire point of using React.
Create a custom Router with its own browserHistory:
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';
export const history = createBrowserHistory();
const ExtBrowserRouter = ({children}) => (
<Router history={history} >
{ children }
</Router>
);
export default ExtBrowserRouter
Next, on your Root where you define your Router, use the following:
import React from 'react';
import { /*BrowserRouter,*/ Route, Switch, Redirect } from 'react-router-dom';
//Use 'ExtBrowserRouter' instead of 'BrowserRouter'
import ExtBrowserRouter from './ExtBrowserRouter';
...
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<ExtBrowserRouter>
<Switch>
...
<Route path="/login" component={Login} />
...
</Switch>
</ExtBrowserRouter>
</Provider>
)
}
}
Finally, import history where you need it and use it:
import { history } from '../routers/ExtBrowserRouter';
...
export function logout(){
clearTokens();
history.push('/login'); //WORKS AS EXPECTED!
return Promise.reject('Refresh token has expired');
}
you can use it like this as i do it for login and manny different things
class Login extends Component {
constructor(props){
super(props);
this.login=this.login.bind(this)
}
login(){
this.props.history.push('/dashboard');
}
render() {
return (
<div>
<button onClick={this.login}>login</login>
</div>
)
/*Step 1*/
myFunction(){ this.props.history.push("/home"); }
/**/
<button onClick={()=>this.myFunction()} className={'btn btn-primary'}>Go
Home</button>
If you want to use history while passing a function as a value to a Component's prop, with react-router 4 you can simply destructure the history prop in the render attribute of the <Route/> Component and then use history.push()
<Route path='/create' render={({history}) => (
<YourComponent
YourProp={() => {
this.YourClassMethod()
history.push('/')
}}>
</YourComponent>
)} />
Note: For this to work you should wrap React Router's BrowserRouter Component around your root component (eg. which might be in index.js)
In index.js push directly or throw dispatch works well:
...
import { push } from 'react-router-redux'
const browserHistory = createBrowserHistory()
export const store = createStore(
rootReducer,
applyMiddleware(thunkMiddleware, routerMiddleware(browserHistory))
)
// in v5 this line is deprecated
export const history = syncHistoryWithStore(browserHistory, store)
history.push('/any') // works well
store.dispatch(push('/any')) // works well
ReactDOM.render((
<Provider store={store}>
<Router history={history}>
<App />
</Router>
</Provider>
), document.getElementById('root'))
App.js
class App extends Component {
render() {
return (
<div className="app">
<Switch>
<Route path="/" component={Main} />
<Route path="/any" component={Any} />
</Switch>
</div>
);
}
}
export default withRouter(connect(/*...*/)(App))
but in redux-thunk action all attempts ends by rewriting url, but without re-rendering
...
export function myAction(){
return (dispatch) => {
// fetch something and then I want to redirect...
history.push('/any') // change url but not re-render
dispatch(push('/any')) // change url but not re-render
store.dispatch(push('/any')) // change url but not re-render
}
}
This myAction is calling fetch() inside and should redirect after success.
If I run this.props.history.push('/any') inside component, it works! but I need to run redirect inside thunk action after successful fetch()
I was trying wrap all components with withRouter or Route, but didn't help.
Inject history object into your component and use push like this:
import { withRouter } from 'react-router-dom'
import { connect } from 'react-redux'
#withRouter
#connect(({auth})=>({auth}))
class App extends Component {
// on redux state change
componentWillReceiveProps(nextProps) {
if(!nextProps.auth)
this.props.history.push('/login')
}
render() {
return (
<div>
<Button
// on button click
onClick={this.props.history.push('/')}
>
Home page
</Button>
</div>
);
}
}
I made workaround by delegating state of successfull fetch() to the component (thanks #oklas) where is history.push() or <Redirect> working:
{this.props.fetchSuccessfull && <Redirect to="/any" />}
But still waiting for better solution by calling push() directly from thunk action.
Well, let me then submit another not perfect solution by passing the history object in the dispatch to the action. I guess it's more a beginners-solution but is IMHO simple to understand (and therefore simple to maintain which is the most important thing in software-development)
Using <BrowserRouter> makes all React-compoments having the history in their props. Very convenient. But, as the problem description stated, you want it outside a React Component, like an action on Redux-Thunk.
Instead of going back to <Router> I chose to stick to BrowserRouter.
The history object cannot be accessed outside React Components
I did not like going back to <Router> and using something like react-router-redux
Only option left is to pass along the history object to the action.
In a Auth-ForgotPassword component:
const submitHandler = (data) => {
dispatch(authActions.forgotpassword({data, history:props.history}));
}
In the action function
export const forgotpassword = ({forgotpasswordData, history}) => {
return async dispatch => {
const url = settings.api.hostname + 'auth/forgotpassword'; // Go to the API
const responseData = await fetch(
url,
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify(forgotpasswordData),
}
);
history.push('/auth/forgotpassword/success');
}
}
And now we all wait for the final elegant solution :-)
I couldn't wait and I jumped into using the latest alpha version of react-router v4. The all-new <BrowserRouter/> is great in keeping your UI in sync with the browser history, but how do I use it to navigate programmatically?
The router will add a history object to your component in the props hash. So in your component, simply do:
this.props.history.push('/mypath')
Here is a full example:
In App.js:
import React from 'react'
import {BrowserRouter as Router, Route} from 'react-router-dom'
import Login from './Login'
export default class App extends React.Component {
render() {
return (
<Router>
<div>
<Route exact path='/login' component={Login} />
</div>
</Router>
)
}
}
In Login.js:
import React, {PropTypes} from 'react'
export default class Login extends React.Component {
constructor(props) {
super(props)
this.handleLogin = this.handleLogin.bind(this)
}
handleLogin(event) {
event.preventDefault()
// do some login logic here, and if successful:
this.props.history.push(`/mypath`)
}
render() {
return (
<div>
<form onSubmit={this.handleLogin}>
<input type='submit' value='Login' />
</form>
</div>
)
}
}
In the past you might have used browserHistory to push a new path. This won't work with react-router v4. Instead you have make use of React's context and router's transitionTo method.
Here's a simple example:
import React from 'react';
class NavigateNext extends React.Component {
constructor() {
super();
this.navigateProgramatically = this.navigateProgramatically.bind(this);
}
navigateProgramatically(e) {
e.preventDefault();
this.context.router.transitionTo(e.target.href)
}
render() {
return (
<Link to={"/next-page"}
onClick={this.navigateProgramatically}
>Continue</Link>
);
}
}
NavigateNext.contextTypes = {
router: React.PropTypes.object
};
transitionTo is just one of available router methods. router object also contains blockTransitions(getPromptMessage), createHref(to) and replaceWith(loc) which are worth checking out.
Here's official react-router tutorial that mentions above method.
If you wanna learn more about using react's context check out the docs.
I don't have enough reputation to comment, but in answer to #singularity's question, you have to include the context properties you wish to make available on the component class' contextTypes static property.
From the React docs on context:
If contextTypes is not defined, then context will be an empty object.
In this case:
class NavigateNext extends React.Component {
// ...
static contextTypes = {
router: PropTypes.object
}
// ...
}
Unlike propTypes, contextTypes actually cause React to behave differently and is not only for typechecking.
Using withRouter will add router properties to you component, then you can access the history and use push like you did with v3:
import React from 'react';
import { withRouter } from 'react-router-dom';
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
input: '',
};
this._submit = this._submit.bind(this);
}
render() {
return (
<form onSubmit={this._submit}>
<input type="text" onChange={(event) => this.setState({input: event.target.value})}/>
<button type="submit">Submit</button>
</form>
);
}
_submit(event) {
event.preventDefault();
this.props.history.push(`/theUrlYouWantToGoTo`);
}
}
export default withRouter(Form);
react-router v4 beta is released and the API changed a little bit. Instead of this.context.router.transitionTo(e.target.href) Do, this.context.router.push(e.target.href) if you are using latest version.
Link to new doc: https://reacttraining.com/react-router/#context.router
If you need to navigate outside of a component at a location that you are unable to pass in the history object from a component similar to how you would do with browserHistory in older versions you can do the following.
First create a history module
History.js:
import createBrowserHistory from 'history/createBrowserHistory'
export default createBrowserHistory();
Then when you are declaring the Router make sure to import Router from react-router and not react-router-dom (which is just a wrapper to react-router version but creates history object automatically) and pass in the history module you just created
Root.js (or wherever you do this):
import Router from 'react-router/Router'
import history from './history'
...
class Root extends Component{
render() {
return (
<Router history={history}>
...
</Router>
);
}
}
Now your application will use the custom created history you created. You can now import that history module anywhere and just do history.replace and so forth just like you would of done with browserHistory in the past.
SomeModule.js:
import history from './history';
export default ()=>{
// redirecting to login page using history without having to pass it in
// from a component
history.replace('/login')
}
Of course this is not the recommended way just as using browserHistory in the old versions was not the recommended way since things like server side rendering won't work, but if you don't care about that this can often be the right solution.
An extra benefit doing this is you could augment the history object to things lie parsed query string params like this for example:
import createBrowserHistory from 'history/createBrowserHistory'
import queryString from 'query-string';
const history = createBrowserHistory();
history.location.query = queryString.parse(history.location.search);
history.listen(() => {
history.location.query = queryString.parse(history.location.search);
});
export default history;
If you need to access history outside of components (for example in redux actions) react-router has published their original solution here.
Basically you have to create your own history object:
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
And pass it to your router:
import { Router } from 'react-router-dom';
ReactDOM.render((
<Router history={history}> // <<-- the history object
<App/>
</Router>
), document.getElementById('root'))
Note: you have to use plain Router instead of BrowserRouter or HashRouter here!
If you export the history now, you can work with it anywhere:
import history from './history';
history.push('/home');
I found using state, a ternary operator and <Redirect> worked best. I think this is also the prefered way since it is closest to the way v4 is set up.
In the constructor()
this.state = {
redirectTo: null
}
this.clickhandler = this.clickhandler.bind(this);
In the render()
render(){
return (
<div>
{ this.state.redirectTo ?
<Redirect to={{ pathname: this.state.redirectTo }} /> :
(
<div>
..
<button onClick={ this.clickhandler } />
..
</div>
)
}
In the clickhandler()
this.setState({ redirectTo: '/path/some/where' });
Hope it helps. Let me know.
use withRouter:
import React, { PropTypes } from 'react'
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)
It is really difficult with react-router. None of the options are straight-forward. this.props.history gave me undefined. But
window.location='/mypath/';
worked for me in version 5.0.0. Don't know whether it is the right method.