Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tvler/413cdcd11f7c12bc392eab6149cc19b2 to your computer and use it in GitHub Desktop.
Save tvler/413cdcd11f7c12bc392eab6149cc19b2 to your computer and use it in GitHub Desktop.
The accompanying code samples for Opendoor's React Navigation upgrade blog post
/**
* A function to create namespaced enums, where the return type
* is a literal reflection of the returned enum value itself.
*
* Ex:
* createNamespacedEnum({
* setA: ['a', 'b'],
* setB: ['a', 'b'],
* });
*
* // Generated object and type:
* {
* setA: { a: 'setA.a', b: 'setA.b' }
* setB: { a: 'setB.a', b: 'setB.b' }
* }
*/
export function createNamespacedEnum<
Namespace extends string,
EnumKey extends string,
EnumKeys extends Array<EnumKey>,
Input extends Record<Namespace, [...EnumKeys]>
>(
input: Input,
): {
[namespace in StringKeys<Input>]: {
[enumKey in ArrayElementType<Input[namespace]>]: `${namespace}.${enumKey}`;
};
} {
type EnumValue = `${Namespace}.${EnumKey}`;
type PartialNamespacedEnum = Record<Namespace, Record<EnumKey, EnumValue>>;
const partialNamespacedEnums: Array<PartialNamespacedEnum> = (Object.entries(input) as Array<
[key: Namespace, value: EnumKeys]
>).map(([namespace, enumKeys]) => {
const enumObj = {} as Record<EnumKey, EnumValue>;
for (const enumKey of enumKeys) {
const enumValue: EnumValue = `${namespace}.${enumKey}`;
enumObj[enumKey] = enumValue;
}
return { [namespace]: enumObj } as PartialNamespacedEnum;
});
const namespacedEnum = Object.assign({}, ...partialNamespacedEnums);
return namespacedEnum;
}
// Auxiliary types:
/**
* Extracts the keys of a type, and casts them all to be strings
* (a key can naturally be a string, number, or symbol).
*/
export type StringKeys<T extends { [k: string]: any }> = T extends infer G
? `${string & keyof G}`
: never;
/**
* Gets all the element types of an array and returns them all as a union
*/
type ArrayElementType<Arr> = Arr extends Array<infer ElementType> ? ElementType : never;
import { NavigationProp, ParamListBase } from '@react-navigation/native';
import { uniqueId } from 'lodash';
// Opendoor internal implementation
// of a v1 navigation prop
type NavigationV1Proxy = any;
export const createNavigationV1Proxy = (
navigationV5: NavigationProp<ParamListBase>,
): NavigationV1Proxy => {
const goBack: NavigationV1Proxy['goBack'] = () => {
navigationV5.goBack();
};
const addListener: NavigationV1Proxy['addListener'] = (eventNameV1, cb) => {
let eventName: 'focus' | 'blur' | undefined;
switch (eventNameV1) {
case 'willFocus':
case 'didFocus':
eventName = 'focus';
break;
case 'willBlur':
case 'didBlur':
eventName = 'blur';
break;
}
// Early exit
if (!eventName) {
return { remove: () => {} };
}
const remove = navigationV5.addListener(eventName, cb);
return { remove };
};
const setParams: NavigationV1Proxy['setParams'] = (params) => {
navigationV5.setParams(params);
};
const navigate: NavigationV1Proxy['navigate'] = (nameOrRoute, possibleParams) => {
if (typeof nameOrRoute === 'string') {
const name = nameOrRoute;
navigationV5.navigate(name, possibleParams);
} else {
const route = nameOrRoute;
navigationV5.navigate({
name: route.routeName,
key: route.key?.toString(),
params: route.params,
});
}
};
const pop: NavigationV1Proxy['pop'] = () => {
navigationV5.goBack();
};
const push: NavigationV1Proxy['push'] = (routeName, params) => {
navigationV5.navigate({
name: routeName,
params,
key: `${routeName}.${uniqueId()}`,
});
};
const isFocused: NavigationV1Proxy['isFocused'] = () => {
return navigationV5.isFocused();
};
const navigationV1Proxy: NavigationV1Proxy = {
goBack,
addListener,
navigate,
setParams,
pop,
push,
isFocused,
};
return navigationV1Proxy;
};
type GlobalParamList = {
// Opendoor's global ParamList
};
/**
* The ParamList for a specific navigator in our app.
* Defaults to including every navigator.
*/
export type ParamList<
NavigatorName extends keyof typeof Routes = keyof typeof Routes,
RouteNamesWithParams extends keyof GlobalParamList = Extract<
RouteNames<NavigatorName>,
keyof GlobalParamList
>
> = Pick<GlobalParamList, RouteNamesWithParams> &
Omit<
{
[key in RouteNames<NavigatorName>]: undefined;
},
RouteNamesWithParams
>;
// Auxiliary types:
export type RouteNames<
NavigatorName extends keyof typeof Routes = keyof typeof Routes
> = LeafStrings<typeof Routes[NavigatorName]>;
/**
* A type that returns a union of all of an object's string values
* of an arbitrary depth.
*
* Ex:
* type Obj = {
* a: { key1: 'value1'; key2: 'value2' };
* b: { key3: 'value3'; key4: 'value4' };
* };
*
* // "value1" | "value2" | "value3" | "value4"
* export type Values = LeafStrings<Obj>;
*/
export type LeafStrings<T extends Record<string, any> | string> = T extends string
? T
: LeafStrings<T[keyof T]>;
/**
* Extracts the keys of a type, and casts them all to be strings
* (a key can naturally be a string, number, or symbol).
*/
export type StringKeys<T extends { [k: string]: any }> = T extends infer G
? `${string & keyof G}`
: never;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment