Replies: 18 comments 12 replies
-
Expo Router is based in react-navigation, which that means you can use react-navigation props to type your rote params. |
Beta Was this translation helpful? Give feedback.
-
Edit for Expo Router v1: maybe the Seems like in Expo Router v1, export default function NewGuest() {
// ...
return (
// ...
<Link
href={`/?firstName=${firstName}&lastName=${lastName}`}
>
Add
</Link> export default function Index() {
const { firstName, lastName } = useLocalSearchParams(); // 💥 Not fully typed As shown above, this is indeed still not typed - maybe Edit: Now Original post below: Would be good to get an example of usage of Eg. does this example below from the the link from @koka0012 translate 1-to-1 to usage in import type { NativeStackScreenProps } from '@react-navigation/native-stack';
type RootStackParamList = {
Home: undefined;
Profile: { userId: string };
Feed: { sort: 'latest' | 'top' } | undefined;
};
type Props = NativeStackScreenProps<RootStackParamList, 'Profile'>; For example, one difference that I could imagine would be that the keys would be lowercase in type RootStackParamList = {
home: undefined;
profile: { userId: string };
feed: { sort: 'latest' | 'top' } | undefined;
}; Maybe also in the cc @EvanBacon |
Beta Was this translation helpful? Give feedback.
-
Yes please to exported type for route's! |
Beta Was this translation helpful? Give feedback.
-
Having that, directly from |
Beta Was this translation helpful? Give feedback.
-
My focus is primarily on getting things working consistently without errors, but I would love to have some fully automated type system in the future. Off the top, we probably need the following:
Further, it would be nice to have some automatic typing (far in the future as this is fragile and strenuous to maintain):
@marklawlor probably has some good ideas for this. |
Beta Was this translation helpful? Give feedback.
-
Yeah I think one of the main things I needed was just a generic that would give me the props that each route has, I dont (right now) need type safe route params etc e.g. import { RouteProps } from "expo-router"
interface MyParams {
id: string
}
export default function MyRoute(props: RouteProps<MyParams>) {
const id = props.params.id // or whatever the actual route props object is
... |
Beta Was this translation helpful? Give feedback.
-
The thing mentioned by @karlhorky is a deal breaker for me. In React Navigation, we can type check parameters passed to routes with TypeScript. import { NativeStackScreenProps } from "@react-navigation/native-stack"
import { Text, TouchableNativeFeedback } from "react-native"
export type RootStackParamList = {
Entry: { nth_visitor: number }
Greet: { message: string }
}
export default function EntryScreen({ route, navigation }: NativeStackScreenProps<RootStackParamList, "Entry">) {
const { nth_visitor } = route.params
return (
<TouchableNativeFeedback
onPress={() => navigation.push("Greet", { message: `Hi, you are the ${nth_visitor} visitor` })}
>
<Text>A Link</Text>
</TouchableNativeFeedback>
)
} If anything gets wrong, TypeScript will raise errors. // Accessing a non-existent property
const { non_existent_key } = route.params // Property 'non_existent_key' does not exist on type 'Readonly<{ nth_visitor: number; }>'.ts(2339) // Pushing with a wrong value type
onPress={() => navigation.push("Greet", { message: 123 })} // Type 'number' is not assignable to type 'string'.ts(2322)
// Pushing without a required property
onPress={() => navigation.push("Greet", {})} // Argument of type '{}' is not assignable to parameter of type '{ message: string; }'. |
Beta Was this translation helpful? Give feedback.
-
I updated my post above to also mention |
Beta Was this translation helpful? Give feedback.
-
@karlhorky Maybe with a factory method? That will be TypeScript only changes (no breaking change involved).
import expoRouterFactory from "expo-router"
type RootStackParamList = {
home: undefined
profile: { userId: string }
feed: { sort: "latest" | "top" } | undefined
}
const { Link, useSearchParams, useRouter } = expoRouterFactory<RootStackParamList>()
export { Link, useSearchParams, useRouter }
import { useSearchParams, Link } from "./custom-expo-router.tsx"
export default function ProfileScreen() {
const { userId } = useSearchParams<"profile">()
return <Link href={{ pathname: "feed", param: { sort: "top" } }}>Feed</Link>
} |
Beta Was this translation helpful? Give feedback.
-
It's like React Navigation, I do it like this.
Now I can use like this
|
Beta Was this translation helpful? Give feedback.
-
On the routed component side, I'm using that to get the right types, and validating data: function useSearchParamsTyped<
T extends Record<string, 'string' | 'number' | 'string[]' | 'number[]'>,
>(
params: T,
): {
[K in keyof T]: T[K] extends 'string'
? string
: T[K] extends 'number'
? number
: T[K] extends 'string[]'
? string[]
: T[K] extends 'number[]'
? number[]
: never
} {
const searchParams = useSearchParams()
const result = {} as any
for (const key in params) {
if (params[key] === `string`) {
if (typeof searchParams[key] !== `string`)
throw new Error(`Invalid type for '${key}': '${params[key]}'`)
result[key] = searchParams[key]?.toString()
} else if (params[key] === `number`) {
if (Number.isNaN(Number(searchParams[key])))
throw new Error(`Invalid type for '${key}': '${params[key]}'`)
result[key] = Number(searchParams[key])
} else if (params[key] === `string[]`) {
if (!Array.isArray(searchParams[key]))
throw new Error(`Invalid type for '${key}': '${params[key]}'`)
result[key] = searchParams[key]
} else if (params[key] === `number[]`) {
if (!Array.isArray(searchParams[key]))
throw new Error(`Invalid type for '${key}': '${params[key]}'`)
result[key] = (searchParams[key] as Array<string>).map(Number)
} else {
throw new Error(`Invalid type ${params[key]}`)
}
}
return result
} |
Beta Was this translation helpful? Give feedback.
-
Oh nice, it looks like both So it seems that this is resolved: import { Text } from 'react-native';
import { useLocalSearchParams } from 'expo-router';
export default function Route() {
const { user, query } = useLocalSearchParams<{ user: string; query?: string }>();
return <Text>User: {user}</Text>;
} |
Beta Was this translation helpful? Give feedback.
-
Any update? Still not able to type params. |
Beta Was this translation helpful? Give feedback.
-
If your still having this issue, make sure updated both Expo SDK and Expo Router. The type generation is performed via The types generated are type UnknownOutputParams = Record<string, string | string[]>;
export function useLocalSearchParams<
T extends AllRoutes | UnknownOutputParams = UnknownOutputParams
>(): T extends AllRoutes ? SearchParams<T> : T; You cannot type a search parameter as a |
Beta Was this translation helpful? Give feedback.
-
Beta Was this translation helpful? Give feedback.
-
Anyone can help me out to type correctly for params? This is on Expo Router V3 |
Beta Was this translation helpful? Give feedback.
-
Based on @mon-jai solution. I'm not expert in TypeScript but I've managed following to get working:
Caveats:
export type RootStack = {
// your routes here like:
// "/": { foo: string }
}
type RouteParam<Pathname extends keyof RootStack> = RootStack[Pathname] extends undefined
? { pathname: Pathname }
: { pathname: Pathname; params: RootStack[Pathname] }
export type Href = {
[K in keyof RootStack]: RouteParam<K>
}[keyof RootStack]
export type HrefOrPathname = Href | keyof RootStack
const expoRouterFactory = <T extends RootStack>() => {
return {
Link: ExpoLink as unknown as (
props: PropsWithChildren<Omit<LinkProps, "href">> & {
href: HrefOrPathname
},
) => ReturnType<typeof ExpoLink>,
Redirect: ExpoRedirect as (
props: PropsWithChildren<{ href: Href }>,
) => ReturnType<typeof ExpoRedirect>,
useNavigation: useExpoNavigation as <K extends keyof T>() => T[K],
useRouter: useExpoRouter as <T extends RootStack>() => {
navigate: (href: HrefOrPathname) => void
push: (href: HrefOrPathname) => void
replace: (href: HrefOrPathname) => void
back: () => void
setParams: (params?: T[keyof T]) => void
},
useSearchParams: useExpoSearchParams as <K extends keyof T>() => T[K],
useLocalSearchParams: useExpoLocalSearchParams as <K extends keyof T>() => T[K],
}
}
const { Link, Redirect, useLocalSearchParams, useRouter } = expoRouterFactory<RootStack>()
export { Link, Redirect, useLocalSearchParams, useRouter } Inside component: // typed
const { foo } = useLocalSearchParams<"/">() |
Beta Was this translation helpful? Give feedback.
-
I created this hook to solve the issue. This hook will:
|
Beta Was this translation helpful? Give feedback.
-
Is there an exported type we can use to type the props?
In Next.js this is type like:
app/blog/[id].js
Beta Was this translation helpful? Give feedback.
All reactions