Access browserHistory in redux - reactjs

How can I access browserHistory on redux? I'm passing the browserHistory to a browserRouter from react-router. I want a reducer to listen for an action and the push another url. What's the best solution for this?

First, you'll need to have access to the Route props like history, location, and match in a component. You will have these props automatically if the rendered component was created by a Route component from react-router. Otherwise, you will need to use withRouter from react-router to decorate your component with the Route props.
Example using withRouter:
// MyComponent before
import { connect } from 'react-redux'
const MyComponent = (props) => (<div>{props.message}</div>)
export default connect()(MyComponent)
// MyComponent after
import { connect } from 'react-redux'
import { withRouter } from 'react-router-dom'
const MyComponent = (props) => (<div>{props.message}</div>)
export default withRouter(connect()(MyComponent))
In the after example, MyComponent you will have access to history under props.history (or this.props.history if in a class component).
After you have access to history in your component, you can redirect the app using props.history.push('/a/new/url'). It sounds like you want to use it in an action to change the state. If that's the case, I recommend passing in your reference to history into the action as an argument. Here is an example of an action which creates a new Post and then redirects to a list view of Posts in the same category. Note: I'm using redux-thunk in this example in order to be able to dispatch async actions.
In my PostForm component, I create a variable for history:
const rrHistory = this.props.history
Later, I pass this variable into the createPost action when the Save button is clicked:
createPost(model.title, model.body, model.author, model.category, rrHistory)
Then in the action code, after some async stuff finishes with the API server, we use the rrHistory variable reference to redirect to the categories list view:
rrHistory.push(`/${postCategory}/${newPost.id}`)
Note, that this is a working example, but it is far from perfect. You can also check out this SO Answer for other navigation ideas Programmatically navigate using react router V4

import { withRouter } from 'react-router-dom';
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));

Related

react redux: private route not rendering layout

Code Sandbox link:
and trying to follow this article
On successful login(/auth/login), the user should be routed to the dashboard(/admin/summary). If the login is successful, I am also storing an access token.
I have a PrivateRoute component for this. The problem is that on successful login, the URL is getting updated but the component is not getting rendered.
PS: about the dashboard, this is a single page application so, the dashboard has topbar, sidebar, and the right content and altogether these things are coupled inside <AdminLayout/>. So, in my AppRouter, I have to render the <AdminLayout/> and just any one component.
All the react and redux code is included in the code sandbox.
Since in your code you create your own history object (it happens in you history.js file, when you call createBrowserHistory()) but doesn't pass it to your Router, nothing happens.
There are 2 possible solutions:
1. Don't create a history object yourself, but use useHistory hook inside your component
Working Demo
With this approach, you should remove history.push from login.actions.js (which imports history) and use history.push in Login.js (which uses useHistory hook):
// login.actions.js
...
loginService.login(userid, password, rememberPassword).then(
(userid) => {
dispatch(success(userid, password, rememberPassword));
// history.push(from); <-- commented out!
},
(error) => { ... }
);
};
...
// Login.js
function handleSubmit(e) {
...
const { from } = {
from: { pathname: "/admin/summary" }
};
history.push(from) // <-- added!
dispatch(loginActions.login(inputs, from));
...
}
useHistory exposes the history object of BrowserRouter (I think this is implied in this official blog post).
2. Create a history object yourself, but pass it to a Router component
Working Demo
This approach would require you to make several changes:
Creating the history object on your own means you become responsible to provide it to a router component, but it can't be a BrowserRouter, but the base Router component (see these Github answers: 1, 2).
Once you import Router (instead of BrowserRouter), you need to get rid of any useLocation and useHistory imports, otherwise you'll get errors.
I also had to unify the history object export and imports, so that it is exported as the default export (i.e., export default history), and it is imported as the default import (i.e., import history from "./history"; instead of import { history } from "./history")
(P.S: this approach can be seen implemented elsewhere on SO, for example here or here (the latter explicitly installs history, but it's not needed in your case).

No location change upon connected-react-router push() from within the react-redux connect()

Dispatching the push action which was defined in mergeProps parameter of the react-redux connect() dispatches redux action but does not trigger url page change. I also wrapped the withRouter around the redux container but no seems to help.
//connect.js
import { Component } from './Component';
import { withRouter } from 'react-router-dom';
import { connect } from 'react-redux';
import { push } from 'connected-react-router';
import { onFetch, onUpdate } from '../action-creators';
export default withRouter(connect(state => state,
{
onFetch,
onUpdate,
push
},
( state, actions ) => ({
...state,
onFetch: actions.onFetch,
onUpdate: index => {
actions.onUpdate( index );
actions.push( `/${ index }` );
}
})
)( Component ));
What can I do to also trigger actual page update along with the LOCATION_CNANGE redux action using the create-react-router package? At this point, only the connected-react-router middleware and the redux store are being updated but no actual page change.
Fixed it by modifying a redirect 'from' prop.
Root cause: a redirect was placed at the end of the switch child routes list as a catchall rerouting to the main page. However, all requests (including valid ones except '/') went to the catchall redirect and were rerouted to main page.

Redux connect "blocks" navigation with react-router-redux

In an application using react, redux and react-router, I'm using react-router-redux to issue navigation actions. I found that wrapping routes in a component with connect blocks navigation.
I made a sample with CodeSandbox that illustrates the issue: sample.
As is, the navigation doesn't work. However, if in ./components/Routes.jsx, this line:
export default connect(() => ({}), () => ({}))(Routes);
Is replaced by:
export default Routes;
It works.
Any idea how I could use connect in a component that wraps routes without breaking navigation?
See the troubleshooting section in react-redux docs.
If you change Routes.jsx export to:
export default connect(() => ({}), () => ({}), null, { pure: false })(Routes);
it will work.
This is because connect() implements shouldComponentUpdate by default,
assuming that your component will produce the same results given the
same props and state.
route changes, but props don't so the view doesn't update.
You could achieve same with withRouter hoc.
Not meant to be a duplicate.
I fixed it with withRouter like this
import { withRouter } from 'react-router-dom';
and
export default withRouter( connect(mapStateToProps)(App) );
See Redux, Router integration docs here
Have you ever encountered the warning message:
Warning: You cannot change <Router history>
Well use withRouter from react-router-dom
I have searched for this for so long because the Redux was recreating my App.jsx component which has <Route> </Route> as parents and this warning just freezes the routing in my app. I wanted to have React/Redux component, because I needed to pass authenticated props to the Route component, and redirect base on it, simple.
So import { withRouter } from 'react-router-dom'
and surround your component which is connected to redux with:
export default withRouter(connect(mapStateToProps)(App));
Something more:
Most of the times if you want to communicate with the router, takes some props, pass something else to it, get history, locations form it and you are using Redux in your app, surround this component with withRouter and you will have access to these properties as props.

How to access current route from anywhere in my app?

I am trying to get information about the current route on a non-Route component.
React Router provides singleton versions of history (browserHistory
and hashHistory) that you can import and use from anywhere in your
application.
Previously, it would seem you could use browserHistory, but that no longer appears to be supported. I'm using react-router-redux^4.0.8 and #types\react-router-redux^5.0.1.
How can I access the current route location?
This is very simple. You can use the withRouter HOC
You can get access to the history object's properties and the closest
Route's match via the withRouter higher-order component. withRouter
will pass updated match, location, and history props to the wrapped
component whenever it renders.
It is used liked this:
import {withRouter} from "react-router-dom"
#withRouter
class NonRouteComponent extends Component {
render() {
// you have access to this.props.match
// you have access to this.props.history
// you have access to this.props.location
const { match, location, history } = this.props
return <h1>Hi {location.pathname}</h1>;
}
}
If you don't use decorators aka #withRouter you can export the component like so:
class NonRouteComponent extends Component { ... }
export default withRouter(NonRouteComponent);

Correct way to pass a click handler from Container, which doesn't modify the state?

I am using React and Redux.
I have a banner component where banners are shown in a carousel.
I have BannerContainer.js which is connected to redux and Banner.js which is component.
On click of a carousel, I need to do 2 things
Redirect user to another url'
Fire a GTM event
None of the above actions modify the state. Should I pass an onClick handler via mapDispatchToProps from my container?
What should be the correct way?
If you don't need to dispatch any action, I don't think you need to pass the function in mapDispatchToProps. A local function inside BannerContainer should be enough for you requirements. BTW, what is GTM event? I don't know the abbr.
I would still pass that in connect, because that's business logic that shouldn't be hidden down your component tree. Otherwise if you need to change the GTM stuff or change routes you will have to find the component.
Create a thunk action creator that performs your side-effects and map it using mapDispatchToProps. If your component is not a top-level Route component, you can use withRouter to get access to the routing context. You can even compose both decorators or enhancers into own in your container:
import { connect, compose } from 'redux'
import { withRouter } from 'react-router'
import YourComponent from '../components/YourComponent'
// your dispatch
const mapDispatchToProps = (dispatch, ownProps) => {
return {
onBannerClick(e){
//do your GTM stuff
//redirect the user!
ownProps.history.push('/gohere')
}
}
}
// combine your enhancers, order is significant if you want
// to have withRouter() props in your connect methods
const enhance = compose(
withRouter(),
connect(mapStateToProps, mapDispatchToProps),
)
export default enhance(YourComponent)

Resources