How to dispatch actions on location change with react router v4 - reactjs

I am using react-router-4.0.0-beta.4 with redux, I am trying to figure out the best way to dispatch actions on route change. I see that a few versions ago there was an API/event hook for this here. But I see that it is now gone. Trying to do the modern equivalent of what redux-router did.

Seems like the best way to dispatch an action is to do so at component's componentWillMount(), componentDidMount() or componentWillUnmount() methods.
IGNORE WHATS BELOW
There is onEnter prop. Like here comet-frontend
onEnter(nextState, replace, callback?)
Called when a route is about to be entered. It provides the next
router state and a function to redirect to another path. this will be
the route instance that triggered the hook.
https://github.com/ReactTraining/react-router/blob/master/docs/API.md#onenternextstate-replace-callback
BUT, there still is onChange prop, doing exactly what you described. Maybe tell us what exactly is your issue. Because seems like you're doing something wrong.

So you have to wrap your main application component in a withRouter and react based on props changing in the props that withRouter passes down, namely the props.location.pathname for example.
You can see a discussion of how it may work here https://github.com/ReactTraining/react-router/issues/4603

Related

How to re-render a component from another component in React

I'm new to react and here is my question , is there's a way to re-render a component from another component?
I'm using Redux and some of the global state is effecting component B .
But in my example component B is not re-rendered after some Redux state is changing from component C .
component C and B are not father/child to each other ,
is there a simple way to do it?
thanks
Every component will re-render if any of the states connected to it change. So in order to cause a re-render, simply include that state in both components connect function.
There is an option on how to re-render component inside component in React 16.x using Fragments. Documentation on this can be found here.
Short explanation: Your DOM will not be polluted with extra nodes, but will allow your app to use less memory which is always great. More in-depth explanation available here.
A frequent problem in Redux that causes this symptom is not using the spread operator to update the state object in your reducer. Try and return something like { ...state, newValue: 'food' }
You can use useNavigate and navigate to the same url you are on. For example, as the last line of your function, you can say navigate("/...your current url....")
window.location.reload() is not the best option everytime. It works on localhost, but for example on when you deploy it to the internet by using services such as "Netlify", it can can cause "not found url" error

How do I dispatch an action to reload the current route?

I'm using React with React Router and Redux. I want to be able to dispatch an action from a component, that ends up reloading the current route. Is this possible ?
I would use browserHistory.push but the problem with this is that I'd first have to know the current route inside the component, which I don't, at least not without adding some cumbersome hacks to the component.
Any help is highly appreciated!
React-Router has a method to refresh/reload the page that you are currently.
https://github.com/reactjs/react-router/blob/v0.13.3/modules/createRouter.js#L435-L437
Router.refresh()
You just need to call it from any part on your application. I don't think that you need to create an action for that.
inside a component you can use
this.context.router.push()
config before use
Component.contextTypes = {
router: function () {
return React.PropTypes.func.isRequired;
}
};

Is there a clean way to conditionally load and render different components for the same React Router route?

The use case is that I want to map the root (/) to one of two different components based on whether the user is logged in or not, and I want these two components to reside in different bundles and lazily loaded, so simply putting the login check in the render() method would not do.
I tried to use dynamic route definition with require.ensure() to lazily load the component, and it works for the first time, but after changing the login state the component doesn't get updated (even if I navigate to another route and back to / ).
I tried to force re-rendering the router by setting props on the component that contains the router, both manually and by making it a Redux connected component, and I also tried to add a listener to the Redux store and change the component state in response to login change, but in all of the attempts I got the error "You cannot change ; it will be ignored" and the component doesn't change.
My ugly solution is to have the different component loading code outside of the router, listen to the login state change and in response load the matching component and set it in the wrapping component's state, which is referenced in the render() code. Is there a clean "React-Router-ish" way to do what I want?
React Router 4 pretty much solves this as it made the route configuration part of the component rendering, so having conditional rendering is the same whether it's based on the location or on other props/state.
The closest thing to a clean "React-Router-ish" way to do that is to use the React Router Enterhooks.
An enter hook is a user-defined function that is called when a route is about to be rendered. It receives the next router state as its first argument. The replace function may be used to trigger a transition to a different URL.
So, use the onEnter(nextState, replace, callback?) attribute on your <Route />.
Called when a route is about to be entered. It provides the next router state and a function to redirect to another path. this will be the route instance that triggered the hook.
If callback is listed as a 3rd argument, this hook will run asynchronously, and the transition will block until callback is called.
The general best practice I follow is to place the auth-check flow away from your routes, and place it inside the transition events/hooks.
The usual behavior is - before the route handler actually gets rendered, check the auth, and redirect the user to another route. In your case, if you want to use the same route, but render different components - you should be able to do that using the same technique too. However, that's not a common thing (based on what I've seen), but it should be possible.
For a complete example of this approach, here's the auth-flow code example you can check. It is shared by the creators of React Router, so it looks credible to me.
PS: My answer is valid for React Router versions > 0.13.x.

Firing callback on query change in react router

I'm looking for solution for paging in routing with react-router and redux.
React-router don't fire callback in onEnter hook if only query changes, Router.run method is deprecated, so I'm a bit puzzled. Are there any other thing to do besides manually subscribing on location.change or use of react's lifecycle hooks like willReceiveProps?
Per the comments, the only hook left to you on the <Router> directly is onUpdate. You might also be able to intercept query parameters via a custom RoutingContext, but we don't currently consider that a public API.
We're looking to add a better solution for this use case in the future, but the approaches outlined are the only ones available for the 1.0.0 release.
For anyone using v2.0/v3.0, you can use the route's onChange hook to respond to query changes.
<Route
component={...}
path="..."
onChange={(nextState, replace, callback) => {
// Do something in response to a query change...
}}
/>
onChange(prevState, nextState, replace, callback?)
Called on routes when the location changes, but the route itself
neither enters or leaves. For example, this will be called when a
route's children change, or when the location query changes. It
provides the previous router state, the next router state, and a
function to redirect to another path. this will be the route instance that triggered the hook. If callback is listed as a 4th argument, this hook will run asynchronously, and the transition will block until callback is called.
https://github.com/remix-run/react-router/blob/v3/docs/API.md#onchangeprevstate-nextstate-replace-callback

Passing actions down the stack via props

I have been using Redux for a few weeks now and I am very happy with it and I am getting used to a Redux way. I am using it with React. Still plenty to learn as both things are new to me.
I have a one problem - maybe I am doing something wrong ... Let me show you:
I have a component structure that looks like this:
App //root of the application aka smart component
CampaignTable
CampaignHeaderRow
CampaignHeader
CampaignDataRow
CampaignData
The App component is initialized as(only related code):
import * as DashboardActions from '../actions.js'
function select(state){
return {
campaigns: state.campaigns, // array of campaign objects, has name, id, time created etc
order: state.order // sort format "byWhichField"
// will affect the way how campaigns are displayed
}
}
function mapDispatchToProps(dispatch){
return bindActionCreators(DashboardActions, dispatch)
}
export default connect(select, mapDispatchToProps)(App);
App has now access to state and all actions as props.
The problem I see with it right now is:
I would like CampaignHeader to fire the action that will change the state.order state. Let say I will make <th>Text</th> inside CampaignHeader clickable. This will fire the action to change state.order which will in turn affect campaigns order on a next rerender.
So I have my action available inside App props. To pass it down to
CampaignHeader I would have to:
pass it down to CampaignHeader as props
assign it to variable inside CampaignHeader and pass it down as props to CampaignHeaderRow
assign it to variable inside CampaignHeaderRow and pass it down as props to CampaignHeader
assign it to variable inside CampaignHeader and fire the action inside onClick event....
This is a lot of boilerplate, assignments and bag passing! Just to get action fired.
All the components along the way are aware of this action.
When I decided to implement this feature I have opened CampaignHeader component file. I have added the logic and called the action, I have added the action to action file. All I needed is to get a props set. CampaignHeader component doesn't hold a reference to its parent so I didn't know straight away where should this props be injected from(in this example is obvious but I hope you get a point).
What if I will have even deeper component structure?
Is this approach correct?
Could I tackle this problem differently?
UPDATE:
As #Errorpro suggested will it be ok to connect single action and state.order to CampaignHeader?
Worried about: If I will do it once I will be doing it all the time.
There's a discussion in the issue-section of the Redux github repo about wether it's okay to use multiple connects or if everything should be passed down from the top through props, and in there Dan Abramov (the creator of Redux say's:
[...]
Nobody advocates a single connect.
[...]
The "single" only refers to small apps like the one we create in the
example. Please feel free to amend the docs to better clarify this. I
am now busy with other projects so please don't expect this issue to
get any movement unless somebody makes a PR. You can do it too.
The comment probably makes more sense in context though so check out the entire issue thread https://github.com/rackt/redux/issues/419#issuecomment-140782462
If you use redux you should know about dumb and smart component. So we use this sctructure:
component
index.js
Component.js
ComponentContainer.js
Dumb component just get props and render it. More interesting in smart component. Here it is:
export default compose(
relay({
fragments: {
viewer: () => Relay.QL`
fragment on Viewer {
any data from relay
}
`,
},
}),
connect(
null,
{
onCreate: createUserAction,
},
(stateProps, actionProps, parentProps) => ({
...parentProps,
onCreate={() => actionProps.onCreate('user')},
})
),
)(Component);
So, parentProps and onCreate function will be in dumb component's props. There you can use this.props.onCreate and invoke it or pass it farther.
Passing the actions - like any other props - from parent to child to grandchild etc is the idiomatic React way. In my opinion your approach is correct; even if it feels wrong.
But there are a couple of alternatives.
There is a feature in React called context. Context permits the passing of fields from a higher order component to a lower order component whilst skipping the middlemen. However, it's an experimental feature so I would recommend avoiding it for now. https://facebook.github.io/react/docs/context.html
Additionally, there is a Redux specific way where you can make any lower order node of your choosing a "smart component" (in the Redux sense). That is, you wrap your class export in the connect function to plug it directly to the store, in the exact same way you do for the Root node.
Personally I tend to stick to the top-down way. There may be a fair bit of boilerplate involved but at least it means your application is easy to reason about.

Resources