We're currently developing implementing a wizard in React Native application using Atomic Design (Pages, Templates, Organisms, Molecules, Atoms), and React Navigation.
The wizard will have a progress bar that must be present on every screen. We are facing a structure challenge. How should we create and develop the children components?
We have thought of 2 approaches:
We create a "Layout" template component that will handle the progress bar, instantiate this component in an Atomic Design Page and pass a children into it. We change each page using a navigation. This violates the Page principle, which should be instantiated a simple Template and pass information to it.
We create a "Master Layout" which conditionally renders the required UI. This is controlled through a context in which children components update. This one does not have cross-cutting concerns. But one of our teammembers is concerned that it hides the implementation from the children (Note that children return
<></>
empty.
Here's the date of birth page that better describes it:
- The green bounding box represents the "Layout" on 1. and the "Master Layout" on 2.
- The orange bounding box is the content that changes across each wizard step.
Note that both approaches render the same UI.
Approach 1:
// pages/UpdateProfileDOB/index.tsx
// This has cross-cutting concerns and injects an Organism (UpdateProfileDOB) inside the
// OnboardingStepTemplate (Template)
export const UpdateProfileDOBPage: FC = () => {
const { updateProfile, defaultInitialValues, loading } =
useUpdateOnboardingProfile();
return (
<OnboardingStepTemplate
title={t`What’s your birth date?`}
loading={loading}
progress={20}>
<UpdateProfileDOBForm
initialValues={defaultInitialValues}
handleSubmit={updateProfile}
/>
</OnboardingStepTemplate>
);
};
Approach 2:
// pages/OnboardingUpdateDOBPage/index.tsx
// This doesn't have cross-cutting concerns, and uses a hook to update the parent
// This hook is rendered through a context.
// It returns a Fragment.
export const OnboardingUpdateDOBPage: FunctionComponent = props => {
const { updateProfile, defaultInitialValues, loading } =
useUpdateOnboardingProfile();
useOnboardingBuilder({
title: t`What's your birth date?`,
progress: 20,
loading,
builder: {
onSubmit: updateProfile,
validationSchema: updateDOBValidationSchema,
initialValues: {
dateOfBirth: defaultInitialValues.dateOfBirth || new Date(),
},
submitText: t`Continue`,
fields: [
{
name: 'dateOfBirth',
type: 'date',
label: t`Date of Birth`,
},
],
},
});
return <></>;
};
Here's the wizard approach #1:
// We take advantage of React Navigator and use `useNavigate()`
// to move between the wizards.
<OnboardingStack.Navigator
screenOptions={{
headerShown: false,
}}
initialRouteName={pageName}>
<OnboardingStack.Screen
name={Routes.SCREEN1}
component={OnboardingUpdateProfileName}
/>
<OnboardingStack.Screen
name={Routes.SCREEN2}
component={OnboardingUpdateDOBPage}
/>
</OnboardingStack.Navigator>
Here's the wizard approach #2:
// We take advantage of React Navigator and use `useNavigate()`
// to move between the wizards.
//
// Note: OnboardingStepV2Template wraps the Navigator and provides it with
// a context.
<OnboardingStepV2Template loading={loading}>
<OnboardingStack.Navigator
screenOptions={{
headerShown: false,
}}
initialRouteName={pageName}>
<OnboardingStack.Screen
name={Routes.SCREEN1}
component={OnboardingUpdateProfileName}
/>
<OnboardingStack.Screen
name={Routes.SCREEN2}
component={OnboardingUpdateDOBPage}
/>
</OnboardingStack.Navigator>
</OnboardingStepV2Template>
Additional note
- I believe react navigation is overkill and it can be modeled using a simple context rendering.
What do you think?