feat: add warning on accessing the state object on route prop

This commit is contained in:
Satyajit Sahoo
2020-11-04 20:51:22 +01:00
parent 4c2379cec1
commit ec7b02af2c
4 changed files with 78 additions and 7 deletions

View File

@@ -18,9 +18,8 @@ type Props<
> = {
screen: RouteConfig<ParamListBase, string, State, ScreenOptions, EventMap>;
navigation: NavigationProp<ParamListBase, string, State, ScreenOptions>;
route: Route<string> & {
state?: NavigationState | PartialState<NavigationState>;
};
route: Route<string>;
routeState: NavigationState | PartialState<NavigationState> | undefined;
getState: () => State;
setState: (state: State) => void;
options: object;
@@ -38,6 +37,7 @@ export default function SceneView<
screen,
route,
navigation,
routeState,
getState,
setState,
options,
@@ -86,7 +86,7 @@ export default function SceneView<
const context = React.useMemo(
() => ({
state: route.state,
state: routeState,
getState: getCurrentState,
setState: setCurrentState,
getKey,
@@ -95,7 +95,7 @@ export default function SceneView<
addOptionsGetter,
}),
[
route.state,
routeState,
getCurrentState,
setCurrentState,
getKey,

View File

@@ -3,11 +3,17 @@ import type {
PartialState,
NavigationState,
} from '@react-navigation/routers';
import { SUPPRESS_STATE_ACCESS_WARNING } from './useRouteCache';
export default function getFocusedRouteNameFromRoute(
route: Partial<Route<string>> & { state?: PartialState<NavigationState> }
): string | undefined {
SUPPRESS_STATE_ACCESS_WARNING.value = true;
const state = route.state;
SUPPRESS_STATE_ACCESS_WARNING.value = false;
const params = route.params as { screen?: unknown } | undefined;
const routeName = state

View File

@@ -12,6 +12,7 @@ import NavigationBuilderContext, {
} from './NavigationBuilderContext';
import type { NavigationEventEmitter } from './useEventEmitter';
import useNavigationCache from './useNavigationCache';
import useRouteCache from './useRouteCache';
import NavigationContext from './NavigationContext';
import NavigationRouteContext from './NavigationRouteContext';
import type {
@@ -113,9 +114,11 @@ export default function useDescriptors<
emitter,
});
return state.routes.reduce<
const routes = useRouteCache(state.routes);
return routes.reduce<
Record<string, Descriptor<ParamListBase, string, State, ScreenOptions>>
>((acc, route) => {
>((acc, route, i) => {
const screen = screens[route.name];
const navigation = navigations[route.key];
@@ -151,6 +154,7 @@ export default function useDescriptors<
navigation={navigation}
route={route}
screen={screen}
routeState={state.routes[i].state}
getState={getState}
setState={setState}
options={routeOptions}

View File

@@ -0,0 +1,61 @@
import * as React from 'react';
import type {
ParamListBase,
NavigationState,
Route,
} from '@react-navigation/routers';
import type { RouteProp } from './types';
type RouteCache = Map<Route<string>, RouteProp<ParamListBase, string>>;
/**
* Utilites such as `getFocusedRouteNameFromRoute` need to access state.
* So we need a way to suppress the warning for those use cases.
* This is fine since they are internal utilities and this is not public API.
*/
export const SUPPRESS_STATE_ACCESS_WARNING = { value: false };
/**
* Hook to cache route props for each screen in the navigator.
* This lets add warnings and modifications to the route object but keep references between renders.
*/
export default function useRouteCache<State extends NavigationState>(
routes: State['routes']
) {
// Cache object which holds route objects for each screen
const cache = React.useMemo(() => ({ current: new Map() as RouteCache }), []);
if (process.env.NODE_ENV === 'production') {
// We don't want the overhead of creating extra maps every render in prod
return routes;
}
cache.current = routes.reduce((acc, route) => {
const previous = cache.current.get(route);
if (previous) {
// If a cached route object already exists, reuse it
acc.set(route, previous);
} else {
const proxy = { ...route };
Object.defineProperty(proxy, 'state', {
get() {
if (!SUPPRESS_STATE_ACCESS_WARNING.value) {
console.warn(
"Accessing the 'state' property of the 'route' object is not supported. If you want to get the focused route name, use the 'getFocusedRouteNameFromRoute' helper instead: https://reactnavigation.org/docs/screen-options-resolution/#setting-parent-screen-options-based-on-child-navigators-state"
);
}
return route.state;
},
});
acc.set(route, proxy);
}
return acc;
}, new Map() as RouteCache);
return Array.from(cache.current.values());
}