Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save emeraldsanto/7b60e86cf92f1b41ee3e6dad9fe33b2e to your computer and use it in GitHub Desktop.

Select an option

Save emeraldsanto/7b60e86cf92f1b41ee3e6dad9fe33b2e to your computer and use it in GitHub Desktop.
Achieving type safe deep linking in React Native with react-navigation #9
const navigators = {
stack: createStackNavigator,
tab: createBottomTabNavigator,
} as const;
type NavigatorType = keyof typeof navigators;
type NavigatorProps<T extends NavigatorType> = ComponentProps<
ReturnType<typeof navigators[T]>['Navigator']
>;
type ScreenMap = Partial<
{
[key in Navigator | Screen]: {
screen: ComponentType<any>;
options?: StackNavigationOptions;
initialParams?: key extends keyof ScreenParameterMap ? ScreenParameterMap[key] : never;
};
}
>;
interface PartialNavigatorConfiguration<
TNavigatorType extends NavigatorType,
TScreens extends ScreenMap
> {
name: Stacks;
type: TNavigatorType;
screens: TScreens;
linking: {
path?: string;
initialRouteName?: Navigator | Screen;
screens: Record<keyof TScreens, string | PathConfig>;
};
options?: Omit<NavigatorProps<TNavigatorType>, 'children'>;
render?: ComponentType<Omit<PartialNavigatorConfiguration<TNavigatorType, TScreens>, 'render'>>;
}
interface NavigatorConfiguration<
TNavigatorType extends NavigatorType,
TScreens extends ScreenMap
> extends Omit<PartialNavigatorConfiguration<TNavigatorType, TScreens>, 'render'> {
render: ComponentType<any>;
}
export function makeNavigator<
TNavigatorType extends NavigatorType,
TScreens extends ScreenMap
>(
config: PartialNavigatorConfiguration<TNavigatorType, TScreens>,
): NavigatorConfiguration<TNavigatorType, TScreens> {
if (config.render) {
const Component = config.render;
return {
...config,
render: () => <Component {...config} />,
};
}
const Parent = navigators[config.type]();
return {
...config,
render: () => (
<Parent.Navigator>
{renderScreens(Parent, config.screens)}
</Parent.Navigator>
);
};
}
export function renderScreens<TScreens extends ScreenMap>(
Navigator: TypedNavigator<any, any, any, any, any>,
screens: TScreens,
mapper?: (
entry: TScreens[keyof TScreens],
) => Omit<ComponentProps<typeof Navigator['Screen']>, 'children'>,
) {
return Object.entries(screens).map(([name, entry]) => (
// @ts-ignore due to an odd union type bug...
<Navigator.Screen
key={name}
name={name}
options={entry.options}
component={entry.screen}
initialParams={entry.initialParams}
{...mapper?.((entry as unknown) as TScreens[keyof TScreens])}
/>
));
}
export function mergeOptions<TOptions extends NavigatorProps<keyof typeof navigators>>(
...xs: Array<Partial<TOptions>>
): TOptions {
return xs.reduce<TOptions>((output, x) => {
return {
...output,
...x,
screenOptions: {
...output.screenOptions,
...x.screenOptions,
},
};
}, {} as TOptions);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment