Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

come back to ideal api #17

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 11 additions & 11 deletions packages/example/src/FlowNavigatorExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import {useQuery} from '@tanstack/react-query';
import {getHasToPassStep4} from './queries/hasToPassStep4';
import {ActivityIndicator, StyleSheet, View} from 'react-native';
import {Step1Page} from './steps/Step1/Step1Page';
import {Step2Navigator} from './steps/Step2/Step2Navigator';
import {Step31Page} from './steps/Step3/Step3-1Page';
import {Step32Page} from './steps/Step3/Step3-2Page';
import {Step4Page} from './steps/Step4/Step4Page';
import {Step5Page} from './steps/Step5/Step4Page';
import {atom, useAtom} from 'jotai';
import {Step22Page} from './steps/Step2/Step2-2Page';

export type FlowStackParamList = {
Step1: undefined;
Expand All @@ -21,7 +22,11 @@ export type FlowStackParamList = {

const FlowNavigator = createFlowNavigator<FlowStackParamList>();

export const show = atom(false);

export const FlowNavigatorExample = () => {
const [isShow, _] = useAtom(show);

const {data: hasToPassStep4, isLoading: isStep4Loading} = useQuery(
['hasToPassStep4'],
getHasToPassStep4,
Expand All @@ -35,22 +40,17 @@ export const FlowNavigatorExample = () => {
);
}

const initialDisabledRoutes = [
'Step31',
...(hasToPassStep4 ? [] : ['Step4']),
];

return (
<FlowNavigator.Navigator
screenOptions={{headerShown: false}}
initialDisabledRoutes={initialDisabledRoutes}>
<FlowNavigator.Navigator screenOptions={{headerShown: false}}>
<FlowNavigator.Screen name="Step1" component={Step1Page} />
<FlowNavigator.Screen name="Step2" component={Step2Navigator} />
{isShow && <FlowNavigator.Screen name="Step2" component={Step22Page} />}
<FlowNavigator.Group>
<FlowNavigator.Screen name="Step31" component={Step31Page} />
<FlowNavigator.Screen name="Step32" component={Step32Page} />
</FlowNavigator.Group>
<FlowNavigator.Screen name="Step4" component={Step4Page} />
{hasToPassStep4 && (
<FlowNavigator.Screen name="Step4" component={Step4Page} />
)}
<FlowNavigator.Screen name="Step5" component={Step5Page} />
</FlowNavigator.Navigator>
);
Expand Down
11 changes: 10 additions & 1 deletion packages/example/src/steps/Step1/Step1Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,26 @@ import React from 'react';
import {Button, StyleSheet, Text, View} from 'react-native';
import {FlowInfos} from '../FlowInfos';
import {ParamListBase, useNavigation} from '@react-navigation/native';
import {useAtom} from 'jotai';
import {show} from '../../FlowNavigatorExample';

export const Step1Page = () => {
const {goToNextStep, quitFlow} =
useNavigation<FlowNavigationProp<ParamListBase>>();
const [_, setIsShow] = useAtom(show);

return (
<View style={styles.container}>
<Text style={styles.pageTitle}>Current page: 1</Text>
<FlowInfos />
<Button title="quit flow" onPress={quitFlow} />
<Button title="next" onPress={() => goToNextStep()} />
<Button
title="next"
onPress={() => {
setIsShow(true);
goToNextStep();
}}
/>
</View>
);
};
Expand Down
6 changes: 3 additions & 3 deletions packages/example/src/steps/Step2/Step2-2Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ import {FlowInfos} from '../FlowInfos';
import {FlowStackParamList} from '../../FlowNavigatorExample';

export const Step22Page = () => {
const {goBack, goToNextStep, enableRoute} =
const {goToPreviousStep, goToNextStep, navigate} =
useNavigation<FlowNavigationProp<FlowStackParamList>>();

const onNextPress = async () => {
enableRoute('Step31');
goToNextStep();
};

const onBackPress = () => {
goBack();
goToPreviousStep();
};

return (
Expand All @@ -24,6 +23,7 @@ export const Step22Page = () => {
<FlowInfos />
<Button title="next" onPress={onNextPress} />
<Button title="back" onPress={onBackPress} />
<Button title="go to home" onPress={() => navigate('Home')} />
</View>
);
};
Expand Down
3 changes: 1 addition & 2 deletions packages/example/src/steps/Step3/Step3-2Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,11 @@ import {FlowInfos} from '../FlowInfos';
import {ParamListBase, useNavigation} from '@react-navigation/native';

export const Step32Page = () => {
const {goToPreviousStep, goToNextStep, disableRoute} =
const {goToPreviousStep, goToNextStep} =
useNavigation<FlowNavigationProp<ParamListBase>>();

const onNextPress = () => {
goToNextStep();
disableRoute('Step32');
};

return (
Expand Down
3 changes: 1 addition & 2 deletions packages/example/src/steps/Step4/Step4Page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {useQueryClient, useMutation} from '@tanstack/react-query';
import {postPassedStep4} from '../../queries/hasToPassStep4';

export const Step4Page = () => {
const {goToPreviousStep, goToNextStep, disableRoute} =
const {goToPreviousStep, goToNextStep} =
useNavigation<FlowNavigationProp<ParamListBase>>();

const queryClient = useQueryClient();
Expand All @@ -18,7 +18,6 @@ export const Step4Page = () => {
onSuccess: () => {
queryClient.invalidateQueries(['hasToPassStep4']);
goToNextStep();
disableRoute('Step4');
},
},
);
Expand Down
104 changes: 80 additions & 24 deletions packages/lib/src/navigators/createFlowNavigator.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import * as React from "react";
import {
createNavigatorFactory,
NavigationContext,
ParamListBase,
useNavigation,
useNavigationBuilder,
} from "@react-navigation/native";
import { NativeStackNavigationEventMap, NativeStackView } from "@react-navigation/native-stack";
import {
NativeStackNavigationEventMap,
NativeStackView,
} from "@react-navigation/native-stack";
import {
FlowActionHelpers,
FlowRouterOptions,
Expand All @@ -19,14 +23,15 @@ import {
FlowStackNavigationOptions,
} from "../types/types";
import { FlowContext } from "./FlowContext";
import { StaticContainer } from "./fork/descriptorRender";
import { getRouteConfigsFromChildren } from "./fork/getRouteNames";

function FlowNavigator({
id,
initialRouteName,
children,
screenListeners,
screenOptions,
initialDisabledRoutes,
...rest
}: FlowNavigatorProps) {
const parentNavigation = useNavigation();
Expand All @@ -42,42 +47,93 @@ function FlowNavigator({
FlowActionHelpers<ParamListBase>,
FlowNavigationOptions,
FlowNavigationEventMap
>(buildFlowRouter(quitFlow, initialDisabledRoutes), {
>(buildFlowRouter(quitFlow), {
id,
initialRouteName,
children,
screenListeners,
screenOptions,
});

/**
* In each page, we add the flow context (Just like for useRoute, we have to create one context per route so that each page statically has their values. If we simply added a context above all the screens and deduced the flow values from useNavigation, the values would change before the navigation animation is completed.)
* We see two other ways this could be done without needing to mutate an object like that:
* - Add the context inside NativeStackView, just like NavigationRouteContext, but we didn't want to duplicate too much of the code of the stack navigator.
* - Calculate flow values from useNavigation and useRoute (state from useNavigation, current route index from useRoute). The code would be very straitfoward, but the progress indicator would be incorrect for subnavigators. Supporting subnavigators would lead to a code that didn't seem much better than those next few lines to us.
*/
Object.entries(descriptors).forEach(([_, descriptor], index) => {
const render = descriptor.render;
const routeConfigs = getRouteConfigsFromChildren<
State,
ScreenOptions,
EventMap
>(children);

// routeNames are calculated twice
const screens = routeConfigs.reduce<
Record<string, ScreenConfigWithParent<State, ScreenOptions, EventMap>>
>((acc, config) => {
if (config.props.name in acc) {
throw new Error(
`A navigator cannot contain multiple 'Screen' components with the same name (found duplicate screen named '${config.props.name}')`
);
}

acc[config.props.name] = config;
return acc;
}, {});

const routes = state.routeNames
.slice(0, state.flowIndex + 1)
.map((route) => ({
key: `route${route}`,
name: route,
}));

// descriptors is calculated twice
const newDescriptors = routes.reduce((acc, route, i) => {
acc[route.key] = {
navigation,
route,
render: () => {
const config = screens[route.name];
const screen = config.props;

const ScreenComponent = screen.getComponent
? screen.getComponent()
: screen.component;

// TODO: Make sure other components from original render are not useful
return (
<NavigationContext.Provider value={navigation}>
<FlowContext.Provider
value={{
navigationState: state,
currentStepIndex: i,
}}
>
<StaticContainer
name={screen.name}
render={ScreenComponent || screen.children}
navigation={navigation}
route={route}
>
{ScreenComponent !== undefined ? (
<ScreenComponent navigation={navigation} route={route} />
) : screen.children !== undefined ? (
screen.children({ navigation, route })
) : null}
</StaticContainer>
</FlowContext.Provider>
</NavigationContext.Provider>
);
},
options: {},
};
return acc;
}, {});

descriptor.render = () => (
<FlowContext.Provider
value={{
navigationState: state,
currentStepIndex: index,
}}
>
{render()}
</FlowContext.Provider>
);
});
const newState = { ...state, routes };

return (
<NavigationContent>
<NativeStackView
{...rest}
state={state}
state={newState}
navigation={navigation}
descriptors={descriptors}
descriptors={newDescriptors}
/>
</NavigationContent>
);
Expand Down
3 changes: 3 additions & 0 deletions packages/lib/src/navigators/fork/descriptorRender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export function StaticContainer(props: any) {
return props.children;
}
Loading