react navigation path as parameter
This commit is contained in:
parent
33c3fc9441
commit
591ce3876b
135
content/posts/2022.05.01.react-navigation-path-as-parameter.md
Normal file
135
content/posts/2022.05.01.react-navigation-path-as-parameter.md
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
---
|
||||||
|
title: "Using a path as a parameter in React Navigation"
|
||||||
|
date: 2022-05-01T17:49:02-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 paramete. 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<RoutingStackParams>();
|
||||||
|
export const LINKING = {
|
||||||
|
prefixes: ["bulgur-cloud://"],
|
||||||
|
config: {
|
||||||
|
screens: {
|
||||||
|
Login: "",
|
||||||
|
Dashboard: "s/:store/", // Can't do "s/:store/*" or something like that
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<NavigationContainer linking={LINKING}>
|
||||||
|
<Stack.Navigator initialRouteName="Login">
|
||||||
|
<Stack.Screen name="Login" component={Login} />
|
||||||
|
<Stack.Screen name="Dashboard" component={Dashboard} />
|
||||||
|
</Stack.Navigator>
|
||||||
|
</NavigationContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
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[/](?<store>[^/]+)[/](?<path>.*)$/.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) => {
|
||||||
|
const route = state.routes[0];
|
||||||
|
// 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.
|
Loading…
Reference in a new issue