Related
This little Logout.jsx component logs-out the user...
import React from 'react';
import { Redirect } from 'react-router';
import { useDispatch } from 'react-redux';
import { userLogout } from '../redux/actions/authActions';
const Logout = ({ to = '/loginForm' }) => {
const dispatch = useDispatch();
dispatch(userLogout());
return <Redirect to={to} />;
};
export default Logout;
and is used in path /logout thus:
<Switch>
...
<Route exact path="/logout" component={Logout} />
In the console it gives the dreaded (and apparently serious) message:
Cannot update a component from inside the function body of a different
component
Can someone spot why, and how to fix it?
Using react 16.13.0
I think it's just a logical mistake causing this error to pop up from another component (than Logout), try logging out once:
const Logout = ({ to = '/loginForm' }) => {
const dispatch = useDispatch();
useEffect(() => {
dispatch(userLogout());
}, [dispatch]);
return <Redirect to={to} />;
};
You don't want to dispatch (or logout) on every component render
I'm experimenting with Hooks without any state management tool (such as Redux), to get the same kind of behavior/structure I could have by using a traditional structure of classes + redux.
Usually, with a class base code I would:
ComponentDidMount dispatch to Call the API
Use actions and reducers to store the data in Redux
Share the data to any component I want by using mapStateToProps
And here where the problem is by using Hooks without Redux: 'Share the DATA with any component'.
The following example is the way I have found to share states between components by Hooks:
//app.js
import React, { useReducer } from 'react'
import { BrowserRouter } from 'react-router-dom'
import Routes from '../../routes'
import Header from '../Shared/Header'
import Footer from '../Shared/Footer'
export const AppContext = React.createContext();
// Set up Initial State
const initialState = {
userType: '',
};
function reducer(state, action) {
switch (action.type) {
case 'USER_PROFILE_TYPE':
return {
userType: action.data === 'Student' ? true : false
};
default:
return initialState;
}
}
const App = () => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<BrowserRouter>
<AppContext.Provider value={{ state, dispatch }}>
<Header userType={state.userType} />
<Routes />
<Footer />
</AppContext.Provider>
</BrowserRouter>
)
}
export default App
// profile.js
import React, { useEffect, useState, useContext} from 'react'
import { URLS } from '../../../constants'
import ProfileDeleteButton from './ProfileDeleteButton'
import DialogDelete from './DialogDelete'
import api from '../../../helpers/API';
// Import Context
import { AppContext } from '../../Core'
const Profile = props => {
// Share userType State
const {state, dispatch} = useContext(AppContext);
const userType = type => {
dispatch({ type: 'USER_PROFILE_TYPE', data: type }); <--- Here the action to call the reducer in the App.js file
};
// Profile API call
const [ profileData, setProfileData ] = useState({});
useEffect(() => {
fetchUserProfile()
}, [])
const fetchUserProfile = async () => {
try {
const data = await api
.get(URLS.PROFILE);
const userAttributes = data.data.data.attributes;
userType(userAttributes.type) <--- here I am passing the api response
}
catch ({ response }) {
console.log('THIS IS THE RESPONSE ==> ', response.data.errors);
}
}
etc.... not important what's happening after this...
now, the only way for me to see the value of userType is to pass it as a prop to the <Header /> component.
// app.js
<BrowserRouter>
<AppContext.Provider value={{ state, dispatch }}>
<Header userType={state.userType} /> <--passing here the userType as prop
<Routes />
<Footer />
</AppContext.Provider>
</BrowserRouter>
Let's say that I want to pass that userType value to children of <Routes />.
Here an example:
<AppContext.Provider value={{ state, dispatch }}>
<Routes userType={state.userType} />
</AppContext.Provider>
and then, inside <Routes /> ...
const Routes = () =>
<Switch>
<PrivateRoute exact path="/courses" component={Courses} userType={state.userType} />
</Switch>
I don't like it. It's not clean, sustainable or scalable.
Any suggestions on how to make the codebase better?
Many thanks
Joe
You don't need to pass the state as a prop to every component. Using context you can gain access to state properity in your reducer inside every child component of parent Provider. Like you have already done in the Profile.js
const {state, dispatch} = useContext(AppContext);
State property here contains state property in the reducer. So you can gain access to it by state.userType
Everything you need is within your context.
The only changes I would make is spread the data instead of trying to access it one at a time something like this
<AppContext.Provider value={{ ....state, dispatch }}>
then use const context = useContext(AppContext) within the component you need to access the data.
context.userType
Currently within my app I have multiple Routes using React Router that are wrapped within the Redux provider.
Provider
<Provider store={store}>
<Router>
<Route exact path ="/" component = {LoginPage}></Route>
<Route exact path="/login" component ={LoginPage}/>
<Route path ="/change" component={MasterPage}/>
<Route path="/change/form" component={ChangeForm}/>
<Route path="/change/table" component={ExpandTable} />
</Router>
</Provider>
As it stands I'm confused how I'm supposed to being passing/accessing the state of the store within the components.
The only state that I really need to be storing is the current status of the user login and want to use this to change what is rendered on the other routes.
Reducer
I have a reducer which looks like:
export default (state = {}, action) => {
switch (action.type) {
case 'SIMPLE_ACTION':
return {
loggedIn: action.loggedIn,
User: action.User,
Group: action.Group
}
default:
return state
}
}
Action
And an action which looks like
export const simpleAction = () => dispatch => {
dispatch({
type: 'SIMPLE_ACTION',
payload: data
})
}
Now I'm currently working under the assumption that connecting them to the store using
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
would allow me to access the store within that component, but it seems that isn't the case.
I'm a little confused as to how to access the store state within the component and also how to properly write the action/reducer to change this state.
So I found where I was going wrong, I needed to pass the props to the route using:
render={(props) => <LoginPage {...props} login={store.getState()} />}>
And in my configureStore() I was passing rootReducer instead of the simple reducer I had made.
Your assumption is correct. It's a good practice to follow the connect pattern. You can create containers for components and do the connection there.
Something like this:
LoginPage.js
const LoginPage = ({loggedIn}) => {
console.log(loggedIn);
return null; // Write your markup here
}
LoginPage.propTypes = {
loggedIn: PropTypes.bool
}
export default LoginPage;
LoginPageContainer.js
import LoginPage from './LoginPage';
let LoginPageContainer = ({loggedIn}) => {
return <LoginPage loggedIn={loggedIn} />
};
LoginPageContainer.propTypes = {
loggedIn: PropTypes.bool
//...
}
const mapStateToProps = state => {
return {
loggedIn: state.........
};
};
LoginPageContainer = connect(mapStateToProps)(LoginPageContainer);
export default LoginPageContainer;
NOTE: This approach is usually used for more complex logic. In your case the LoginPage itself can be a container, so you don't need separate component.
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 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)