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
Related
Consider the following example:
return (
<Router>
<Route component={Table} path='/table' />
</Router>
);
Let's assume we're on the /table route the entire time.
Whenever the URL changes, the Router will re-render one of the Route components (even when the path doesn't change, see: https://github.com/ReactTraining/react-router/issues/7539).
My users like to share the URLs with each other and expect the displayed page to be more or less the same. Therefore, I need to store some information into the URL, e.g. /table?country=Narnia. The problem is, when I append or edit the search query, the child component will be re-rendered (because the search inside props.location changed), which will in turn trigger the update of its children and so on. In truth, only a part of the page needs to take into account this change inside the URL, e.g. table might apply some sort of filtering logic to the data.
What is the best way to achieve this behavior? I am talking about the likes of useSelector in Redux or Context Provider Pattern with React's Context.
Using useLocation/useMatch in nested children will also result in unwanted renders - one component might be interested in changes to parameter country, but some other might want subscribe to parameter age. useLocation would cause both to re-render when the url changes.
I am trying to avoid writing custom useEffect to control rendering of components subscribing to route changes.
I have a React application where pages are linked using React router. The user is provided with several links. Each link is handled through the router.
All of the corresponding pages have similar logic before render function, so I used a URL parameter, a single Route path, and the same target component. The URL parameter is supposed to be passed to the backend service.
Since the target component is the same and only distinguishing factor is the URL parameter, once the component is rendered for any of the links, the lifecycle methods like componentWillMount, componentDidMount do not execute again. So, even if I click on another link whatever is the state created by the first hit, same is used for other links. REST call is within componentDidMount. Am I missing something?
<Route path="/location/:locationType" component={MapSelectedLocation}/>
MapSelectedLocation is supposed to handle several locationType and invoke REST service based on that.
The expected result is to execute the same code for different locationType. How can I achieve this?
You need to use componentDidUpdate lifecycle method to do the calculation or each props/state change. Put the check in this method and compare the prevProps and new props value.
Also this method will not get called on initial rendering
Like this:
componentDidMount() {
this.doCalculation();
}
componentDidUpdate(prevProps) {
if(this.props.match.params.locationType != prevProps.match.params.locationType) {
this.doCalculation()
}
}
doCalculation() {
// do the calculation here
}
In my application i have a HomePage component which has navlinks in it. the route for this component is <Route to="/browse/:item" exact component={HomePage} />. so the component has a navigation bar with NavLink links to "sub routes" of it. for example a NavLink that leads you to /browse/featured or
/browse/new and i added to this component the lifecycle method componentDidUpdate() to just console.log("UPDATED HOMEPAGE") and whenever i click a NavLink this happens:
i tried to use shouldComponentUpdate with the nextProps and nextState params to see if indeed the state or props are changed (something that will cause a re-render) but they stay the same.
Thanks in advance for your help.
EDIT:
the code is on github https://github.com/idanlo/spotify-test-frontend
components that have the problem that i have seen are AlbumView and HomePage
ANOTHER EDIT:
this is a console.log() of the two updates happening, each one displaying the props before and after the update. on the first update you can see that the url is different so the update is supposed to happen but on the second update nothing is different, everything is the same (in the state everything is the same too)
Usually there are multiple calls because of changes made to the state. Check other files to make sure that no actions that modify the state are initially called. Even though you check for differences for nextProps, the variable that changes might not be in props.
I suspect that Navlink's internal implementation uses setState with an updater function which is the source of this duplicate log
I found that when I update state with an updater function then componentDidUpdate gets fired twice!
However when I pass an object to setState - componentDidUpdate is fired only once.
setState(updater[, callback])
example:
incrementScore = () => {
this.setState(prevState => ({
score: prevState.score + 1
}));
};
VS
setState(stateChange[, callback])
example:
this.setState({quantity: 2})
I guess it's because
Think of setState() as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.
read about setState on React Docs
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
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.