Everything you wanted to know about on enter hooks but were afraid to ask
The onEnter hook is a lifecycle hook specific to react-router - you can only attach onEnter hooks
to Route components, like so:
<Route path="/" component={MyComponent} onEnter={myOnEnterHook} />
What you attach to the onEnter prop is a function.
That function can expect to receive the following arguments:
const myOnEnterHook = function (nextRouterState, replace, done) {
// your slick code here
}
Let's break each of these down:
nextRouterState is an object that contains the upcoming internal state of the Router component.
It usually contains key-value pairs like 'location', 'params', and 'routes' - the main things that react-router handles for you. If you need to access the routeParams of a Route (ex. /stories/:storyId), this is the place to access that! (In this example, you would be able to get the storyId by saying nextRouterState.params.storyId).
replace is a function that allows you to dynamically change the url. It is similar to hashHistory.push. It can either take a pathname, or an object with more configuration. You might use this in an onEnter hook to dynamically re-direct a user from one Route to another. For example, say you have a Route like this:
<Route path="/adminsOnly" component={AdminStuff} />
If would be a shame if commoners were able to see that component. We might implement some access control like so:
const adminsOnlyHook = function (nextRouterState, replace) {
const currentUser = store.getState().currentUser;
if (!currentUser.isAdmin) {
replace('/normalFolks')
}
}
<Route path="/adminsOnly" component={AdminStuff} onEnter={adminsOnlyHook} />
replace is different from hashHistory.push in that it does not immediately change the URL. When you use hashHistory.push, that immediately causes the url transition. The transition that you create with replace must wait until the hook finishes running!
done is the trickiest argument. It's a special function, which may be used to signal to react-router when to actually transition to rendering the component.
The first thing to note is that it matters whether or not done is included in your list of arguments. That means there is a difference between this:
const adminsOnlyHook = function (nextRouterState, replace) {
const currentUser = store.getState().currentUser;
if (!currentUser.isAdmin) {
replace('/normalFolks');
}
}
and this:
// note that we've included done as an argument
const adminsOnlyHook = function (nextRouterState, replace, done) {
const currentUser = store.getState().currentUser;
if (!currentUser.isAdmin) {
replace('/normalFolks');
}
}
For the first example (without including 'done'), the onEnter hook will run to completion and will transition to rendering its component as soon as it completes.
This is not the case for the function that contains 'done' as an argument. Because the second function uses 'done', react-router will block rendering until done is invoked. That means that right now, our 'adminsOnlyHook' with done will NEVER actually transition to showing the component! That's bad!
To fix this, we need to invoke 'done' to let react-router know it's okay to transition.
const adminsOnlyHook = function (nextRouterState, replace, done) {
const currentUser = store.getState().currentUser;
if (!currentUser.isAdmin) {
// remember - replace does NOT stop the thread - we keep going after this
replace('/normalFolks');
}
// this will be invoked regardless of whether the user is an admin or not
done();
}
Using the done callback is useful if you want to totally prevent transition to a certain state until you have done something (possible asynchronous). For example, if we needed to fetch the user asynchronously in our 'adminsOnlyHook', we may want to use the 'done' callback to make sure that non-admins don't get a 'peek' of the admin content.
onEnter hooks are not passed any props from your own state (be that state that's held locally by React compoents, or your Redux store).
Because of this, it's perfectly okay to import store from './yourStore and access state directly by saying store.getState, and
dispatching action using store.dispatch.
onEnter and componentDidMount are often used for the same purpose - fetching data from the server when the component is loading. They can often be used interchangeably. There is one big important difference though.
Remember in Juke that when we created our list of playlists, switching between playlists didn't cause the view to update because the Playlist component had already mounted, so the componentDidMount hook did not run again. We needed to use componentWillReceiveProps to solve this.
onEnter hooks do not have this problem - they run every time the Route is entered, regardless of whether the component it renders is mounted or not.
For this reason, you should generally favor onEnter hooks over componentDidMount for doing things like fetching data from the server that will be displayed in the component.
This is up to you. If you have many onEnter hooks, you may write them in a hooks.js file and import them into the file(s) containing your Routes. If you don't have many, you may write them in the same file as your Routes.