Skip to content

Instantly share code, notes, and snippets.

@austin-sa-wang
Last active April 7, 2020 08:47
Show Gist options
  • Save austin-sa-wang/919afbb9167d9cf6727ce169d2a91b59 to your computer and use it in GitHub Desktop.
Save austin-sa-wang/919afbb9167d9cf6727ce169d2a91b59 to your computer and use it in GitHub Desktop.
onboarding wizard design for simplicity and flexbility
/**
* Simple wizard
* design philosophy: simplicity, wysiwyg, isolated responsibilities
* key design decision: each wizard step is aware that it's part of a wizard
* upside: simple and flexible
* downside: more boilerplate
* case-by-case: when we need to add or rearrange the steps, we also need to modify existing steps (smell: Shotgun Surgery)
* however, this is aligned with the key design decision
*
* alternative key design: opposite - isolated steps that are unaware of the fact that they're part of a wizard
* we plug components into a grand wizard framework. handle all business logics for the steps inside the wizard
* updside: minimal boilerplate
* downside: rigid, all logics is coupled and wizard needs to implement everything for the steps
* case-by-case: super easy to use in some situations. For example, an instruction wizard without any business logic
*/
// use this for cross step data store if necessary. Isolated. Not integrated into wizard
const OnboardingWizardStore = React.createContext({})
// ------------------- isolated Step definitions --------------------------
const WizardFirstStep = (props) => {
const [state, setState] = useState({})
const saveFirstStepAndProceed = () => {
doBusinessStuffSuchAsSave(firstPageState)
props.onNext()
}
return (
<WizardStepLayout>
<input onChange={setState}>{{state}}</input>
// manually define the buttons is definitely boilerplate, but it's the simplest to maintain and update
<WizardStepLayoutButtons>
<button onClick={saveFirstStepAndProceed}>next</button>
<button onClick={onNext}>skip</button>
</WizardStepLayoutButtons>
</WizardStepLayout>
)
}
const WizardSecondStep = (props) => {
const onboardingWizardStore = React.useContext(OnboardingWizardStore) // use this for cross step data store
const [state, setState] = useState({})
const saveFirstStepAndProceed = () => {
doBusinessStuffSuchAsSave(firstPageState)
props.onNext()
}
return (
<WizardStepLayout>
<input onChange={setState}>{{state}}</input>
<WizardStepLayoutButtons>
<button onClick={saveFirstStepAndProceed()}>next</button>
<button onClick={onBack}>back</button> // we can change the buttons easily
</WizardStepLayoutButtons>
</WizardStepLayout>
)
}
// ----------------- setup of wizard itself. the glue code -----------
function OnboardingWizard () {
// wizard is only responsbile of tracking and navigating the steps, nothing more.
const {
currentStep,
setCurrentStep,
goToNextStep,
goToPreviousStep
} = useWizard({ // implementation of useWizard is omitted.
steps: [STEP.INFO, STEP.CONNECT, STEP.INVITE, STEP.INVITE_CUSTOMEMAIL]<StepsEnum>
})
const WizardContent = () => {
if (currentStep === 1)
return <WizardFirstStep onNext={goToNextStep} />
if (currentStep === 2) {
return <WizardSecondStep onNext={goToNextStep} />
}
return (
<OnboardingWizardStore.Provider> // use this for cross step data store
<WizardContent />
</OnboardingWizardStore.Provider>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment