--- title: "Using a path as a parameter in React Navigation" date: 2022-05-01T17:49:02-04:00 lastmod: 2022-05-09T04:28:00-04:00 draft: true toc: false images: tags: - dev - react - javascript - typescript --- I've been trying to integrate [React Navigation](https://reactnavigation.org/) into [Bulgur Cloud](https://github.com/SeriousBug/bulgur-cloud) and I hit an issue when trying to use a path as a parameter. What I wanted to do was to have a route where the rest of the path in the route would be a parameter. For example, I can do this in my backend: ```rust #[get("/s/{store}/{path:.*}")] pub async fn get_storage(// ... ``` This route will match all paths like `/s/user/`, as well as `/s/user/foo/` and `/s/user/foo/bar.txt`. The key is that the path portion is a path with an arbitrary number of segments. Unfortunately there doesn't seem to be built-in support for this in React Navigation. Here's what I had at first: ```ts export type RoutingStackParams = { Login: undefined; Dashboard: { store: string; path: string; }; }; export const Stack: any = createNativeStackNavigator(); export const LINKING = { prefixes: ["bulgur-cloud://"], config: { screens: { Login: "", Dashboard: "s/:store/", // Can't do "s/:store/*" or something like that }, }, }; function App() { return ( ); } ``` This would cause the URLs for the `Dashboard` to look like `/s/user/?path=file`. I read through all the docs, looked up many examples, and scoured any Stackoverflow answers I could find. Nope, nobody seems to be talking about this. This feels like such a fundamental piece of routing tech to me that I'm shocked that not only is there no built-in support, nobody seems to be questioning why it doesn't exist. Thankfully some folks in the [Reactiflux](https://www.reactiflux.com/) discord pointed me towards the right way: using [`getStateFromPath`](https://reactnavigation.org/docs/navigation-container#linkinggetstatefrompath) and [`getPathFromState`](https://reactnavigation.org/docs/navigation-container#linkinggetpathfromstate) to write a custom formatter and parser for the URL. This is made easier thanks to the fact that you can still import and use the built-in formatter and parser, and just handle the cases that you need to. Here's what I implemented: ```ts export const LINKING = { prefixes: ["bulgur-cloud://"], config: { screens: { Login: "", Dashboard: "s/:store/", }, }, getStateFromPath: (path: string, config: any) => { // For the Dashboard URLs only... if (path.startsWith("/s/")) { const matches = // ...parse the URL as /s/:store/...path /^[/]s[/](?[^/]+)[/](?.*)$/.exec( path, ); const out = { routes: [ { name: "Dashboard", path, params: { store: matches?.groups?.store, path: matches?.groups?.path, }, }, ], }; return out; } // For all other URLs fall back to the built-in const state = getStateFromPath(path, config); return state; }, getPathFromState: (state: any, config: any) => { // Getting the "top route" if we're using a stack navigator const route = state.routes[state.routes.length - 1]; // For the Dashboard routes only... if (route?.name === "Dashboard") { // ...directly put the path into the URL const params: RoutingStackParams["Dashboard"] = route.params; return `/s/${params.store}/${params.path}`; } // For all other routes fall back to the built-in return getPathFromState(state, config); }, }; ``` I'm not sure how the get the types to be a little nicer, but just going with `any` is fine for me in this case since it's a very small portion of the codebase. > Edit: Made the following change after noticing that this code didn't work with a stack navigator > > ```ts > // Getting the "top route" if we're using a stack navigator > const route = state.routes[state.routes.length - 1]; > ```