Required: true
The foundation of React Location 🚀. An instance of the ReactLocation
class is required and must be provided to your application via the Router
component.
export type ReactLocationOptions<TGenerics> = {// The history object to be used internally by react-location// A history will be created automatically if not provided.history?: BrowserHistory | MemoryHistory | HashHistorystringifySearch?: SearchSerializerparseSearch?: SearchParser}
Example: Basic
import { ReactLocation } from '@tanstack/react-location'const reactLocation = new ReactLocation()
Example: Memory History
import { createMemoryHistory, ReactLocation } from '@tanstack/react-location'const history = createMemoryHistory()const reactLocation = new ReactLocation({ history })
Required: true
The Router
component is the root Provider component for the React Location instance and your route configuration in your app. Render it only once (rendering multiple routers is an anti-pattern, and straight-up not supported for good reason).
children
prop is passed, it will default to <Outlet />
which will start rendering your route matches.children
prop is passed, you must eventually render <Outlet />
where you want your routes to start renderingexport type RouterProps<TGenerics> = {// An instance of the `ReactLocation` classlocation: ReactLocation<TGenerics>basepath?: string// Children will default to `<Outlet />` if not providedchildren?: React.ReactNode// An array of route objects to matchroutes?: Route<TGenerics>[]filterRoutes?: FilterRoutesFndefaultLinkPreloadMaxAge?: numberdefaultLoaderMaxAge?: numberuseErrorBoundary?: booleandefaultElement?: SyncOrAsyncElement<TGenerics>defaultErrorElement?: SyncOrAsyncElement<TGenerics>defaultPendingElement?: SyncOrAsyncElement<TGenerics>defaultPendingMs?: numberdefaultPendingMinMs?: numbercaseSensitive?: boolean}
Example: Basic
import { ReactLocation, Router } from '@tanstack/react-location'const reactLocation = new ReactLocation()return (<Routerlocation={reactLocation}routes={[{path: '/',element: 'Home on the range!',},]}/>)
Example: With Children
import { ReactLocation, Router, Outlet } from '@tanstack/react-location'const reactLocation = new ReactLocation()return (<Routerlocation={reactLocation}routes={[{path: '/',element: 'Home on the range!',},]}><div>Header</div><Outlet /></Router>)
In React Location, routes are just an array of objects where routes can contain child arrays of more routes. It's a route tree!
For more information on creating routes and how they behave, see the Routes Guide.
export type Route<TGenerics extends PartialGenerics = DefaultGenerics> = {// The path to match (relative to the nearest parent `Route` component or root basepath)path?: string// An ID to uniquely identify this route within its siblings. This is only required for routes that *only match on search* or if you have multiple routes with the same pathid?: string// If true, this route will be matched as case-sensitivecaseSensitive?: boolean// Either (1) an object that will be used to shallowly match the current location's search or (2) A function that receives the current search params and can return truthy if they are matched.search?: SearchPredicate<UseGeneric<TGenerics, 'Search'>>// The duration to wait during `loader` execution before showing the `pendingElement`pendingMs?: number// _If the `pendingElement` is shown_, the minimum duration for which it will be visible.pendingMinMs?: number// Search filters can be used to rewrite, persist, default and manipulate search params for link that// point to their routes or child routes. See the "basic" example to see them in action.searchFilters?: SearchFilter<TGenerics>[]// An array of child routeschildren?: Route<TGenerics>[]// Route Loaders (see below) can be inline on the route, or resolved async} & RouteLoaders<TGenerics> & {// If `import` is defined, this route can resolve its elements and loaders in a single asynchronous call// This is particularly useful for code-splitting or module federationimport?: (opts: {params: UseGeneric<TGenerics, 'Params'>search: UseGeneric<TGenerics, 'Search'>}) => Promise<RouteLoaders<TGenerics>>}export type RouteLoaders<TGenerics> = {// The content to be rendered when the route is matched. If no element is provided, defaults to `<Outlet />`element?: SyncOrAsyncElement<TGenerics>// The content to be rendered when `loader` encounters an errorerrorElement?: SyncOrAsyncElement<TGenerics>// The content to be rendered when the duration of `loader` execution surpasses the `pendingMs` durationpendingElement?: SyncOrAsyncElement<TGenerics>// An asynchronous function responsible for preparing or fetching data for the route before it is renderedloader?: LoaderFn<TGenerics>// An asynchronous function responsible for cleaning up when the match cache is cleared. This is useful when// the loader function has side effects that need to be cleaned up when the match is no longer in use.unloader?: UnloaderFn<TGenerics>// An integer of milliseconds representing how long data should be cached for the routeloaderMaxAge?: number// Similar to React's useEffect hook, this function is called// when moving from an inactive state to an active one. Likewise, when moving from// an active to an inactive state, the return function (if provided) is called.onMatch?: (match: RouteMatch<TGenerics>,) => void | undefined | ((match: RouteMatch<TGenerics>) => void)// This function is called when the route remains active from one transition to the next.onTransition?: (match: RouteMatch<TGenerics>) => void// An object of whatever you want! This object is accessible anywhere matches are.meta?: UseGeneric<TGenerics, 'RouteMeta'>}export type SearchFilter<TGenerics> = (search: UseGeneric<TGenerics, 'Search'>,) => UseGeneric<TGenerics, 'Search'>
Example - Route Params
const routes: Route[] = [{path: 'invoices',children: [{path: '/',element: 'This would render at the `/invoices` path',},{path: 'new',element: `This would render at the '/invoices/new' path`,},{path: ':invoiceId',element: <Invoice />,},],},]
Example - Default / Fallback Route
const routes: Route[] = [{path: '/',},{path: 'about',},{// Passing no route is equivalent to passing `path: '*'`element: `This would render as the fallback when '/' or '/about' were not matched`,},]
Example - Default / Fallback Route with client-side redirect
const routes: Route[] = [{path: '/',element: 'I am groot!',},{path: 'about',element: 'About me.',},{element: <Navigate to="/" />,},]
Example - Root Wrapper
const routes: Route[] = [{// Defaults to:// path: '*'// element: <Outlet />loader: () => Promise.resolve({ data: 'some global data' }),children: [{path: '/',element: 'I am groot!',},{path: 'about',element: 'About me.',},{element: <Navigate to="/" />,},],},]
Example - Data Loaders
const routes: Route[] = [{path: '/',element: <Home />,},{path: 'dashboard',element: <Dashboard />,},{path: 'invoices',element: <Invoices />,// Load invoices before renderingloader: async () => ({invoices: await fetchInvoices(),}),children: [{path: 'new',element: <NewInvoice />,},{path: ':invoiceId',element: <Invoice />,// Load the individual invoice before renderingloader: async ({ params }) => ({invoice: await fetchInvoiceById(params.invoiceId),}),},],},]
Example - Code Splitting
const routes: Route[] = [{path: '/',element: <Home />,},{path: 'expensive',// Code-split Elementelement: () => import('./Expensive').then((mod) => <mod.default />),// Code-split Loaderloader: async (...args) =>import('./Expensive').then((mod) => mod.loader(...args)),},]
The useMatch
hook returns the nearest current route match within the context of where it's called. It can be used to access:
/:invoiceId
=> params.invoiceId
)Example - Route Data
function App() {return (<Routerroutes={[{path: 'invoices',element: <Invoices />,loader: async () => ({invoices: await fetchInvoices(),}),children: [{path: ':invoiceId',element: <Invoice />,loader: async ({ params }) => ({invoice: await fetchInvoiceById(params.invoiceId),}),},],},]}/>)}function Invoice() {const {data: {// You can access any data merged in from parent loaders as wellinvoices,invoice,},} = useMatch()}
Example - Route Params
function App() {return (<Routerroutes={[{path: 'invoices',element: <Invoices />,children: [{path: ':invoiceId',element: <Invoice />,},],},]}/>)}function Invoice() {const {params: { invoiceId },} = useMatch()// Use it for whatever, like in a React Query!const invoiceQuery = useQuery(['invoices', invoiceId],fetchInvoiceById(invoiceId),)}
The useMatches
hook is similar to the useMatch
hook, except it returns an array of all matches from the current match down. If you are looking for a list of all matches, you'll want to use useRouter().matches
.
Example - Route Data
function Invoice() {const matches = useMatches()}
In React Location, search params are considered first-class objects that can be immutably updated safely and consistently in a similar fashion to React.useState
s setState(replacementObj)
and setState((old) => new)
patterns.
Parsing & Serialization
The first level of search params always have standard encoding, eg. ?param1=value¶m2=value¶m3=value
. This keeps things at the root level of the search params experience fully compliant with the native URLSearchParams
API and the rest of the web ecosystem. When it comes to the values of those URLSearchParams, however, React Location offers much more power by fully supporting JSON, included nested JSON structures.
For every search param value, React Location follows the following schema:
JSON.stringify
and parsed using JSON.parse
Custom stringifySearch
and parseSearch
functions can be provided to your ReactLocation
instance to further enhance the way search objects are encoded.
Regardless of how your search params are serialized or parsed, React Location willl always provide a stable, immutable and structurally-safe object reference. This means that even though your search params' source of truth is technically a string that is changing over time, it will behave as a structurally shared immutable object.
When implementing custom search param parsing and serialization, the parseSearch
/stringifySearch
options and parseSearchWith
/stringifySearchWith
functions will come in handy.
Their types are as follows:
export type SearchParser = (searchStr: string) => Record<string, any>export type SearchSerializer = (searchObj: Record<string, any>) => stringexport function parseSearchWith(parser: (str: string) => any,): (searchStr: string) => Record<string, any>export function stringifySearchWith(stringify: (search: any) => string,): (search: Record<string, any>) => string
While unnecessary, here is an example of how to re-implement the default search param parsing and serialization for React Location:
import {ReactLocation,parseSearchWith,stringifySearchWith,} from '@tanstack/react-location'const reactLocation = new ReactLocation({parseSearch: (search: string) => {return parseSearchWith(JSON.parse)(search)},stringifySearch: (search: any) => {return stringifySearchWith(JSON.stringify)(search)},})
See more examples of custom search param parsing and serialization
The useSearch
hook provides access to the search params state for the current location. This JSON object is immutable from render to render through structural sharing so any part of it can be safely used for change-detection, even in useEffect/useMemo dependencies.
Example - Basic
import { Router, MakeGenerics } from '@tanstack/react-location'type MyLocationGenerics = MakeGenerics<{Search: {pagination?: {index?: numbersize?: number}filters?: {name?: string}desc?: boolean}}>function MyComponent() {const search = useSearch<MyLocationGenerics>()console.info(search)// {// pagination: {// index: 1,// size: 20// },// filter: {// name: 'tanner'// },// desc: true// }}
Example - Updating URL Search Params State
type MyLocationGenerics = MakeGenerics<{Search: {pagination?: {index?: numbersize?: number}filters?: {name?: string}desc?: boolean}}>>function MyComponent() {const navigate = useNavigate<MyLocationGenerics>()const nextPage = () => {navigate({// All typesafe!search: (old) => ({...old,pagination: {...old.pagination,index: old.pagination.index + 1,},}),})}// OR use something like immer!const nextPage = () => {navigate({search: (old) =>immer.produce(old, (draft) => {draft.pagination.index++}),})}}
The Link
component allows you to generate links for internal navigation, capable of updating the:
The links generated by it are designed to work perfectly with Open in new Tab
+ ctrl + left-click
and Open in new window...
. They are also capable of receiving "active" props (depending on the activeOptions
passed) to decorate the link when the link is currently active relative to the current location.
export type LinkProps<TGenerics extends PartialGenerics = DefaultGenerics> =Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, 'href' | 'children'> & {// The absolute or relative destination pathnameto?: string | number | null// The new search object or a function to update itsearch?: true | Updater<UseGeneric<TGenerics, 'Search'>>// The new has string or a function to update ithash?: Updater<string>// Whether to replace the current history stack instead of pushing a new onereplace?: boolean// A function that is passed the [Location API](#location-api) and returns additional props for the `active` state of this link. These props override other props passed to the link (`style`'s are merged, `className`'s are concatenated)getActiveProps?: () => Record<string, any>// Defaults to `{ exact: false, includeHash: false }`activeOptions?: ActiveOptions// If set, will preload the linked route on hover and cache it for this many milliseconds in hopes that the user will eventually navigate there.preload?: number// A custom ref prop because of this: https://stackoverflow.com/questions/58469229/react-with-typescript-generics-while-using-react-forwardref/58473012_ref?: React.Ref<HTMLAnchorElement>// If a function is pass as a child, it will be given the `isActive` boolean to aid in further styling on the element it returnschildren?:| React.ReactNode| ((state: { isActive: boolean }) => React.ReactNode)}export type ActiveOptions = {exact?: booleanincludeHash?: boolean}
Example: The basics
function App() {return (<div><Link to="/home">I will navigate to `/home`</Link><Link to="todos">I will navigate to `./todos`, relative to the current location</Link><Link to="..">I will navigate up one level in the location hierarchy.</Link><Link to="." hash="somehash">I will update the hash to `somehash` at the current location</Link><Link to="/search" search={{ q: 'yes' }}>I will navigate to `/search?q=yes`</Link><Linkto="."search={{someParams: true,otherParams: 'gogogo',object: { nested: { list: [1, 2, 3], hello: 'world' } },}}>I will navigate to the current location +`?someParams=true&otherParams=gogogo&object=~(nested~(list~(~1~2~3)~hello~%27world))`</Link><Linksearch={({ removeThis, ...rest }) => ({...rest,addThis: 'This is new!',})}>I will add `addThis='This is new!' and also remove the `removeThis`param to/from the search params on the current page</Link></div>)}
Example: Using getActiveProps
The following link will be green with /about
as the current location.
<Linkto="/about"getActiveProps={(location) => ({style: { color: 'green' },})}>About</Link>
Example: Using a child function for further active state customization
The following link will contain an <ActiveIcon/>
prefix when active
<Link to="/about">{({ isActive }) => {return (<>{isActive ? (<><ActiveIcon />{' '}</>) : null}About</>)}}</Link>
When rendered, the Navigate
component will declaratively and relatively navigate to any route.
export type NavigateOptions<TGenerics extends PartialGenerics = DefaultGenerics,> = {// The new relative or absolute pathnameto?: string | null// A new search params object, or a function that receives the latest search params object and returns the new one.search?: Updater<UseGeneric<TGenerics, 'Search'>>// The new hash stringhash?: Updater<string>// If set to 'true', will replace the current state instead of pushing a new one onto the stackreplace?: boolean// If `true`, the new location will replace the current entry in the history stack instead of creating a new one.fromCurrent?: boolean// If set will update the key of the new locationkey?: string// ADVANCED. Can be used to set the relative origin of the navigation for resolutionfrom?: Partial<Location<TGenerics>>}
Example
function App () {return <Navigate to='./about'>}
The useNavigate
hook allows you to programmatically navigate your application.
Usage
function MyComponent() {const navigate = useNavigate()const onClick = () => {navigate({ to: './about', replace: true })}return <button onClick={onClick}>About</button>}
The useMatchRoute
hook allows you to programmatically test both relative and absolute paths against the current or pending location. If a path is matched, it will return an object of route params detected, even if this is an empty object. This can be useful for:
function useMatchRoute(): (opts: {to?: string | number | nullsearch?: SearchPredicate<UseGeneric<TGenerics, 'Search'>>fuzzy?: booleancaseSensitive?: booleanpending?: boolean}) => undefined | Params<TGenerics>
Usage
function App() {return (<Routerroutes={[{element: <Root />,children: [{path: '/',element: 'Hello!',},{path: ':teamId',element: 'Hello!',},],},]}/>)}function Root() {const matchRoute = useMatchRoute()// If the current path is '/'matchRoute({ to: '/' }) // {}matchRoute({ to: ':teamId' }) // undefined// If the current path is `/team-1'matchRoute({ to: '/' }) // undefinedmatchRoute({ to: '/', fuzzy: true }) // {}matchRoute({ to: '/*' }) // { '*': 'team-1' }matchRoute({ to: ':teamId' }) // { teamId: 'team-1' }// If the pending path is `/team-1`matchRoute({ to: ':teamId' }) // undefinedmatchRoute({ to: ':teamId', pending: true }) // { teamId: 'team-1' }}
The MatchRoute
component is merely a component-version of useMatchRoute
. It takes all of the same options but comes with some different affordances.
null
will be rendered.children
is a React.ReactNode
, children
will be renderedchildren
is a function, it will be called with resulting match params, or an empty object if no params were found.Example: Rendering ellipsis if the pending route matches
function Example() {return (<Link to="dashboard">Dashboard{' '}<MatchRoute to="dashboard" pending>...</MatchRoute></Link>)}
The useRouter
hook can be used to gain access to the state of the parent <Router />
component. Its shape looks like this:
export type Router<TGenerics extends PartialGenerics = DefaultGenerics> =// The resolved options used in the routerRouterProps<TGenerics> & {// The current transition state of location + matches that has been successfully matched and loadedstate: TransitionState<TGenerics>// The next/pending transition state of location + matches that is being matched and loadedpending?: TransitionState<TGenerics>}export type TransitionState<TGenerics> = {status: 'pending' | 'ready'location: Location<TGenerics>matches: Match<TGenerics>[]}
The useResolvePath
hook returns a function that can be used to resolve the full path of a relative route, based on where the hook is called in the route hierarchy.
Example
function App() {return (<Routerroutes={[{path: 'workspaces',children: [{path: 'team',element: <Team />,},],},]}/>)}function Team() {const resolvePath = useResolvePath()const parentPath = resolvePath('..') // /workspaceconst parentPath = resolvePath('.') // /workspace/teamconst parentPath = resolvePath('team-1') // /workspace/team/team-1}
The usePrompt
hook allows you to programmatically prompt the user with a dialog.
Its syntax looks like this:
export function usePrompt(message: string, when: boolean | any): void
message
string to be displayed to the userwhen
boolean (or anything truthy) to conditionally prompt the userExample
function App() {const [isDirty, setDirty] = useState(false)usePrompt('There are unsaved changes, are you sure you want to leave?',isDirty,)// ...}
The Prompt
component is merely a component-version of usePrompt
. It takes all of the same options, but comes with some different affordances.
Its syntax looks like this:
export function Prompt(props: { message: string; when: boolean | any }): null
Example
function App() {const [isDirty, setDirty] = useState(false)return (<><Promptmessage="There are unsaved changes, are you sure you want to leave?"when={isDirty}/>OR<Promptmessage="There are unsaved changes, are you sure you want to leave?"when={isDirty}>Anything you want to render here</Prompt>OR{isDirty ? (// when defaults to true with <Prompt><Prompt message="There are unsaved changes, are you sure you want to leave?" />) : null}</>)// ...}
The best JavaScript newsletter! Delivered every Monday to over 76,000 devs.