Deprecation guidance
Enhanced router (1/8) New hook useINRouter()
One routing hook to replace three, delivered across eight pull requests.
This is a pull request I opened at InterNations. It is laid out below the way my colleagues would have come across it: as a review page, at the point where they had to decide whether to take the change on.
The description is the part on display here. The diff that went with it is private, and links to the repository and to our internal tools are flattened to plain text, so nothing below will take you somewhere it shouldn’t.
Enhanced router (1/8) New hook useINRouter()#7070
This is the first PR in a series. All changes in this series go together conceptually, but the changes have been separated into smaller chunks to make them easier to review.
The tests may not pass in most PRs, but they will pass in the last one (because the tests will run against the cumulative changes).
Enhanced Router PRs
This is part of a series of PRs on referential stability which are meant to be reviewed in the following order:
- Adding the new hook PR #7070
- Applying the new hook in common app files PR #7071
- Applying new hook to simple use cases PR #7072
- Remove useRouteNavigation PR #7073
- Remove useRouteNavigation PR #7074
- Remove useRouteNavigation PR #7075
- Remove useRouteNavigation PR #7076
- Remove useRouteNavigation PR #7077
Description
Next’s useRouter() hook is not referentially stable. This means that many of our components which call useRouter will re-render when they don’t need to (i.e. when there’s no new data to receive from useRouter).
Also, we’ve been running into problems with circular dependencies related to the Route classes go method. And arguably, this method doesn’t belong in the class because it’s a function of navigation, rather than a particular ‘quality’ of the route instance.
Lastly, we have several different hooks related to routing—useRouter, useRouteNavigation, useCurrentRoute, etc—and it would be nice to bring all those together into one cohesive hook.
What’s changing
All the route related stuff is now rolled into one hook:
- All
NextRoutervalues and functions currentRoutego()for navigating to routes
const { go, currentRoute, asPath, pathname, query, isReady, push, replace, ... } = useINRouter() ^^ ^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | | | ⌞ all values/functions from Next's `useRouter` are passed through | | | ⌞ the `TRoute` for the current page | ⌞ navigates to routes (replacement for `route.go()`)Usage
These changes are mandatory and must be used from now on:
- Instead of
route.go(), usego(route) - No more
routeNavoruseRouteNavigation
These changes are optional:
- Instead of
useRouter(), calluseINRouter()useINRouter()is preferred, but there are some places where we cannot use it (e.g. outside of some providers)
- Instead of
useCurrentRoute(), get the same value fromuseINRouter()- This depends on whether you need the other values returned by
useINRouterin the same component. If you do need them, then you might as well get everything from one hook call:const { currentRoute, asPath, query } = useINRouter(). But if all you need iscurrentRoute, then it’s fine to just calluseCurrentRoute.
- This depends on whether you need the other values returned by
Before
import { useCurrentRoute } from '@data/store/slices/route/getters'import { myRoute } from '@routes/catalog'import { useRouteNavigation } from '@routes/utils/useRouteNavigation'import { useRouter } from 'next/router'
function Component() { const currentRoute = useCurrentRoute() const { asPath, query } = useRouter() const routeNav = useRouteNavigation() const onClick = () => myRoute.go(routeNav)
// ...}After
import { myRoute } from '@routes/catalog'import { useINRouter } from '@routes/utils/useINRouter'
function Component() { // Now, `go` and `currentRoute` come from the same hook as the rest of the `useRouter` values const { currentRoute, go, asPath, query } = useINRouter() const onClick = () => go(myRoute)
// ...}Deprecating useRouteNavigation
tl;dr: Times have changed, we no longer need it
useRouteNavigation was created as a way to provide routes with some information that was only available in a React scope. For example, route.go() needs access to NextRouter and the QueryClient to make decisions about how it will navigate.
Back when it was created, we didn’t have a way to share this information with TRoute objects. Nowadays we have a global store that can do this. Also, at that time, importing NextRouter into the Route.ts class file seemed to break Jest, but now it seems to work fine. Perhaps something changed with either Next or Jest in the last couple of years.
Most importantly, there are drawbacks to useRouteNavigation:
- It can cause components to re-render even when the component itself has nothing new to display
- Sometimes we need to call
.gooutside of a React scope, which means we need to prop-drill or passrouteNavaround a lot
After these PRs, you can navigate to a route just by calling route.go() with no arguments.