Skip to content

Commit

Permalink
feat: accept a callback for setOptions
Browse files Browse the repository at this point in the history
  • Loading branch information
satya164 committed Feb 3, 2023
1 parent f8f98b5 commit 923d199
Show file tree
Hide file tree
Showing 6 changed files with 116 additions and 92 deletions.
43 changes: 24 additions & 19 deletions example/types.check.tsx
Expand Up @@ -84,7 +84,12 @@ export const PostDetailsScreen = ({

expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<StackNavigationOptions>();
.toMatchTypeOf<
| Partial<StackNavigationOptions>
| ((
mergedOptions: Partial<StackNavigationOptions>
) => Partial<StackNavigationOptions>)
>();

expectTypeOf(navigation.addListener)
.parameter(0)
Expand Down Expand Up @@ -126,10 +131,12 @@ export const FeedScreen = ({

expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<StackNavigationOptions>();
expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<DrawerNavigationOptions>();
.toMatchTypeOf<
| Partial<DrawerNavigationOptions>
| ((
mergedOptions: Partial<DrawerNavigationOptions>
) => Partial<DrawerNavigationOptions>)
>();

expectTypeOf(navigation.addListener)
.parameter(0)
Expand Down Expand Up @@ -160,13 +167,12 @@ export const PopularScreen = ({

expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<StackNavigationOptions>();
expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<DrawerNavigationOptions>();
expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<BottomTabNavigationOptions>();
.toMatchTypeOf<
| Partial<BottomTabNavigationOptions>
| ((
mergedOptions: Partial<BottomTabNavigationOptions>
) => Partial<BottomTabNavigationOptions>)
>();

expectTypeOf(navigation.addListener)
.parameter(0)
Expand Down Expand Up @@ -201,13 +207,12 @@ export const LatestScreen = ({

expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<StackNavigationOptions>();
expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<DrawerNavigationOptions>();
expectTypeOf(navigation.setOptions)
.parameter(0)
.toMatchTypeOf<BottomTabNavigationOptions>();
.toMatchTypeOf<
| Partial<BottomTabNavigationOptions>
| ((
mergedOptions: Partial<BottomTabNavigationOptions>
) => Partial<BottomTabNavigationOptions>)
>();

expectTypeOf(navigation.setParams).parameter(0).toEqualTypeOf<undefined>();

Expand Down
2 changes: 2 additions & 0 deletions packages/core/src/__tests__/useNavigationCache.test.tsx
Expand Up @@ -29,6 +29,7 @@ it('preserves reference for navigation objects', () => {

const getState = () => state;
const navigation = {} as any;
const getMergedOptions = () => ({} as any);
const setOptions = (() => {}) as any;
const router = MockRouter({});

Expand All @@ -40,6 +41,7 @@ it('preserves reference for navigation objects', () => {
state,
getState,
navigation,
getMergedOptions,
setOptions,
router,
emitter,
Expand Down
30 changes: 19 additions & 11 deletions packages/core/src/types.tsx
Expand Up @@ -171,7 +171,7 @@ type NavigationHelpersCommon<
* @param action Action object or update function.
*/
dispatch(
action: NavigationAction | ((state: State) => NavigationAction)
action: NavigationAction | ((state: Readonly<State>) => NavigationAction)
): void;

/**
Expand Down Expand Up @@ -323,15 +323,15 @@ export type NavigationContainerProps = {
/**
* Callback which is called with the latest navigation state when it changes.
*/
onStateChange?: (state: NavigationState | undefined) => void;
onStateChange?: (state: Readonly<NavigationState> | undefined) => void;
/**
* Callback which is called after the navigation tree mounts.
*/
onReady?: () => void;
/**
* Callback which is called when an action is not handled.
*/
onUnhandledAction?: (action: NavigationAction) => void;
onUnhandledAction?: (action: Readonly<NavigationAction>) => void;
/**
* Whether child navigator should handle a navigation action.
* The child navigator needs to be mounted before it can handle the action.
Expand Down Expand Up @@ -381,9 +381,15 @@ export type NavigationProp<
* Update the options for the route.
* The options object will be shallow merged with default options object.
*
* @param options Options object for the route.
*/
setOptions(options: Partial<ScreenOptions>): void;
* @param update Options object or a callback which takes the options from navigator config and returns a new options object.
*/
setOptions(
updater:
| Partial<ScreenOptions>
| ((
mergedOptions: Readonly<Partial<ScreenOptions>>
) => Partial<ScreenOptions>)
): void;
} & EventConsumer<EventMap & EventMapCore<State>> &
PrivateValueStore<[ParamList, RouteName, EventMap]>;

Expand Down Expand Up @@ -418,11 +424,9 @@ export type CompositeNavigationProp<
*/
A extends NavigationProp<any, any, any, infer S> ? S : NavigationState,
/**
* Screen options from both navigation objects needs to be combined
* This allows typechecking `setOptions`
* Screen options should refer to the options specified in the first type
*/
(A extends NavigationProp<any, any, any, any, infer O> ? O : {}) &
(B extends NavigationProp<any, any, any, any, infer P> ? P : {}),
A extends NavigationProp<any, any, any, any, infer O> ? O : {},
/**
* Event consumer config should refer to the config specified in the first type
* This allows typechecking `addListener`/`removeListener`
Expand Down Expand Up @@ -573,7 +577,11 @@ export type RouteConfig<
* For a given screen name, there will always be only one screen corresponding to an ID.
* If `undefined` is returned, it acts same as no `getId` being specified.
*/
getId?: ({ params }: { params: ParamList[RouteName] }) => string | undefined;
getId?: ({
params,
}: {
params: Readonly<ParamList[RouteName]>;
}) => string | undefined;

/**
* Initial params object for the route.
Expand Down
90 changes: 48 additions & 42 deletions packages/core/src/useDescriptors.tsx
Expand Up @@ -2,9 +2,11 @@ import type {
NavigationAction,
NavigationState,
ParamListBase,
Route,
Router,
} from '@react-navigation/routers';
import * as React from 'react';
import useLatestCallback from 'use-latest-callback';

import NavigationBuilderContext, {
AddKeyedListener,
Expand Down Expand Up @@ -54,13 +56,6 @@ type Options<
>;
navigation: NavigationHelpers<ParamListBase>;
screenOptions?: ScreenOptionsOrCallback<ScreenOptions>;
defaultScreenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase>;
navigation: any;
options: ScreenOptions;
}) => ScreenOptions);
onAction: (action: NavigationAction) => boolean;
getState: () => State;
setState: (state: State) => void;
Expand Down Expand Up @@ -89,7 +84,6 @@ export default function useDescriptors<
screens,
navigation,
screenOptions,
defaultScreenOptions,
onAction,
getState,
setState,
Expand Down Expand Up @@ -129,18 +123,56 @@ export default function useDescriptors<
]
);

const getMergedOptions = ({
route,
options,
}: {
route: Route<string>;
navigation: NavigationProp<ParamListBase>;
options?: ScreenOptions;
}) => {
const config = screens[route.name];
const screen = config.props;

const optionsList = [
// The default `screenOptions` passed to the navigator
screenOptions,
// The `screenOptions` props passed to `Group` elements
...((config.options
? config.options.filter(Boolean)
: []) as ScreenOptionsOrCallback<ScreenOptions>[]),
// The `options` prop passed to `Screen` elements,
screen.options,
// Additional options
options,
];

const mergedOptions = optionsList.reduce<ScreenOptions>(
(acc, curr) =>
Object.assign(
acc,
// @ts-expect-error: we check for function but TS still complains
typeof curr !== 'function' ? curr : curr({ route, navigation })
),
{} as ScreenOptions
);

return mergedOptions;
};

const navigations = useNavigationCache<State, ScreenOptions, EventMap>({
state,
getState,
navigation,
getMergedOptions: useLatestCallback(getMergedOptions),
setOptions,
router,
emitter,
});

const routes = useRouteCache(state.routes);

return routes.reduce<
const descriptors = routes.reduce<
Record<
string,
Descriptor<
Expand All @@ -162,40 +194,12 @@ export default function useDescriptors<
const screen = config.props;
const navigation = navigations[route.key];

const optionsList = [
// The default `screenOptions` passed to the navigator
screenOptions,
// The `screenOptions` props passed to `Group` elements
...((config.options
? config.options.filter(Boolean)
: []) as ScreenOptionsOrCallback<ScreenOptions>[]),
// The `options` prop passed to `Screen` elements,
screen.options,
const mergedOptions = getMergedOptions({
route,
navigation,
// The options set via `navigation.setOptions`
options[route.key],
];

const customOptions = optionsList.reduce<ScreenOptions>(
(acc, curr) =>
Object.assign(
acc,
// @ts-expect-error: we check for function but TS still complains
typeof curr !== 'function' ? curr : curr({ route, navigation })
),
{} as ScreenOptions
);

const mergedOptions = {
...(typeof defaultScreenOptions === 'function'
? // @ts-expect-error: ts gives incorrect error here
defaultScreenOptions({
route,
navigation,
options: customOptions,
})
: defaultScreenOptions),
...customOptions,
};
options: options[route.key],
});

const clearOptions = () =>
setOptions((o) => {
Expand Down Expand Up @@ -239,4 +243,6 @@ export default function useDescriptors<

return acc;
}, {});

return descriptors;
}
17 changes: 0 additions & 17 deletions packages/core/src/useNavigationBuilder.tsx
Expand Up @@ -28,7 +28,6 @@ import {
NavigatorScreenParams,
PrivateValueStore,
RouteConfig,
RouteProp,
} from './types';
import useChildListeners from './useChildListeners';
import useComponent from './useComponent';
Expand All @@ -49,20 +48,6 @@ import useScheduleUpdate from './useScheduleUpdate';
// eslint-disable-next-line babel/no-unused-expressions
PrivateValueStore;

type NavigationBuilderOptions<ScreenOptions extends {}> = {
/**
* Default options specified by the navigator.
* It receives the custom options in the arguments if a function is specified.
*/
defaultScreenOptions?:
| ScreenOptions
| ((props: {
route: RouteProp<ParamListBase>;
navigation: any;
options: ScreenOptions;
}) => ScreenOptions);
};

type NavigatorRoute<State extends NavigationState> = {
key: string;
params?: NavigatorScreenParams<ParamListBase, State>;
Expand Down Expand Up @@ -266,7 +251,6 @@ export default function useNavigationBuilder<
ScreenOptions,
EventMap
> &
NavigationBuilderOptions<ScreenOptions> &
RouterOptions
) {
const navigatorKey = useRegisterNavigator();
Expand Down Expand Up @@ -695,7 +679,6 @@ export default function useNavigationBuilder<
screens,
navigation,
screenOptions: options.screenOptions,
defaultScreenOptions: options.defaultScreenOptions,
onAction,
getState,
setState,
Expand Down

0 comments on commit 923d199

Please sign in to comment.