Entering/Leaving hooks of routing for Redux app

Thought on redux-tower

Yuki Kodama
4 min readJan 4, 2017

What is a routing in React/Redux? It’s a component switcher essentially. But the most of real world apps need to load data before showing a component which is associated with a route, prevent leaving from the current route to keep unsaved data, and check whether the user is logged in or not before entering the admin section. These tasks can be done by a mechanism “hooks”. An entering hook handles data fetching and authentication. A leaving hook is useful for dirty check and cleaning up something before page transitions.

In this post, I’d like to focus on the hooks in client-side routing, especially in React + Redux stack.

Use of react-router in real world

react-router is a de facto standard as a routing library in React ecosystem without exaggeration. If you’re using React + Redux stack, you may have heard it once.

For instance, KeystoneJS uses a combo of react-router and react-router-redux. Since they use it as a component switcher, there is no place to insert hooks. Instead of hooks, KeystoneJS relies on redux-thunk to initiate data fetching. In addition to it, a loading flag in Redux’s state is useful for preventing a blank page while data fetching. So when a user clicks a link, it dispatches a thunk and is executed in the thunk middleware. Then the flag state is turned to “loading”, and back to “ready” when the loading is finished.

I’d like to introduce one more example. The use case of Recruit Technologies is slightly hacky but interesting. They use React + Redux + react-router + redux-async-loader. redux-async-loader provides a react-router middleware wraps target components with a higher order component that blocks rendering of child components until the data fetching is complete. onEnter callback, which is provided by react-router, is used for authentication. This is similar to the entering hook I said in above, but how about data fetching? Unfortunately, it’s NOT recommended since it may cause blocking UI and make it non-responsive. onLeave is a callback that is executed on leaving its route. However, this cannot be used to prevent page transitions because it can’t access “router” object.

You may understand it’s too complicated but important for apps.

Timing of running hooks

I realized that there are two candidates of timing to run hooks; Component and Route. A component hook is called when a component is mounted or unmounted. A route hook is called when you enter or leave a route. This is the same timing as the location change. Here is an example code built with react-router.

This routing works well. Hmm … really? Another routing is focused on hooks.

The route “A” has “PageA” with entering hook “E-A” and leaving hook “L-A”. On the other hands, the route “B” has no component with entering and leaving hooks. What is happened when I move from A to B? The leaving hook of A is executed and then the entering hook of B is called. No problem. But I confused at B → C. If the leaving hook of B is a component hook, I expect that the entering hook of C will run, but PageB is kept showing and the leaving hook of B is not executed even when the location is changed. This means all of react-router’s hooks are route hooks, not component hooks.

In addition to that, unfortunately, the “onLeave” callback is not so useful (explained above). Even if I use setRouteLeaveHook with withRouter HoC, it’s just a delegation of “beforeunload” callback of the browser. After all, I can’t prevent page transitions without relying on the annoying confirmation dialog. There is no way to hook location changes by Back or Forward button of the browser.

Asymmetric entering/leaving hooks and Intermediate route

In redux-tower, a route is a control flow that contains a request for showing a component. It’s not associated with a component directly. To achieve keeping a component and not triggering a leaving hook of it, the intermediate route is useful pattern. Since running a leaving hook before location changes, a leaving hook is executed before next component change. This is an asymmetric because a entering hook is executed on location change.

I assume you are in “/posts/1”. To edit the post, move to “/posts/1/edit”. This location change triggers “/posts/:id/edit” route and runs “edit” Generator function. Because of it’s a saga, “PostEdit” component is shown after loading data is complete. Don’t forget “dirtyCheck” leaving hook is queued for later component change. If you try to back to “/posts/1” without saving the data, the component change in “show” Generator function triggers the leaving hook which is queued. Note that the leaving hook is not called at the time of location change. The component change will be cancelled if “dirtyCheck” hook yields false.

To pass dirty check, let’s think about the case of saving before leaving. When you click the save button in the edit page, “/posts/:id/update” route is activated and executed “update” Generator function. Since “PageEdit” component is still shown, the leaving hook is not called yet, even if the location is changed to “/posts/1/update”. It waits until updating is complete and redirects to “/posts/:id”. In the destination of redirection, which is “show” function, the leaving hook “dirtyCheck” is called but the dirty flag is already cleared, the component change in “show” function won’t be prevented at this time.

Perfect Routing Model

To be fair, I’d like to note about the downside of my choice on redux-tower. “show” function in the above redux-tower’s routing has two effects; loading post data and changing component. In this route action, a leaving hook is executed after loading data. What does this mean? There is unintended a lag time until triggering the leaving hook. Waiting data loading is not needed.

This issue is partially resolved by the feature of Generator function. I can inspect what effects are yielded without running it. If the action yields a component change, I’m able to call a leaving hook before running the action. If a conditional statement is used in it, I can’t predict perfectly, but it’s better than nothing at all.

--

--

Yuki Kodama

Web Developer. JavaScript / Ruby on Rails / AWS. Using Vim and loving coffee. ex-TortoiseHg maintainer.