Reputation: 3559
I'm not sure the title is correct, so let me try to explain what I'm trying to achieve.
Let's say I have a flow in my application that has 3 steps in it, so I create a component (let's call it Stepper
) with 3 child components where each child is a component that renders the corresponding step.
I want to expose a custom hook to the child components of Stepper
, let's call it useStepper
.
This is how Stepper
would look like (JSX-wise):
export const Stepper = (props) => {
...some logic
return (
<SomeWrapper>
{props.children}
</SomeWrapper>
);
};
so I can make components like this:
export SomeFlow = () => {
return (
<Stepper>
<StepOne />
<StepTwo />
<StepThree />
</Stepper>
);
};
Now this is how I want things to work inside Stepper
's children, let's take StepThree
as an example:
export const StepThree = () => {
const exposedStepperData = useStepper();
... some logic
return (
...
);
};
Now, it's important that the Stepper
will be reusable; That means - each Stepper
instance should have its own data/state/context that is exposed through the useStepper
hook.
Different Stepper instances should have different exposed data.
Is it possible to achieve this? I tried to use Context API but I was not successful. It's also weird that I couldn't find anything about it on the internet, maybe I searched wrong queries as I don't know what patten it is (if it exists).
Note:
I achieved a similar behavior through injected props from parent to its children, but it's not as clean as I want it to be, especially with Typescript.
Upvotes: 0
Views: 723
Reputation: 4682
I recently came across something like this, it was solved by pouring all the components/steps in an array and let the hook manage which component/step to show. If you want it to be more reusable you could pass in the children
to the array.
I hope this helps you in the right direction
useStepper.ts
import { ReactElement, useState } from "react";
export const useStepper = (steps: ReactElement[]) => {
const [currentStepIndex, setCurrentStepIndex] = useState(0);
const next = () => {
setCurrentStepIndex((i: number) => {
if (i >= steps.length - 1) return i;
return i + 1;
});
};
const back = () => {
setCurrentStepIndex((i: number) => {
if (i <= 0) return i;
return i - 1;
});
};
const goTo = (index: number) => {
setCurrentStepIndex(index);
};
return {
currentStepIndex,
step: steps[currentStepIndex],
steps,
isFirstStep: currentStepIndex === 0,
isLastStep: currentStepIndex === steps.length - 1,
goTo,
next,
back,
};
};
Stepper.tsx
// const { currentStepIndex, step, isFirstStep, isLastStep, back, next } =
// useStepper([<StepOne />, <StepTwo />, <StepThree />]);
const { currentStepIndex, step, isFirstStep, isLastStep, back, next } =
useStepper([...children]);
return (
<div>
{!isFirstStep && <button onClick={back}>Back</button>}
{step}
<button onClick={next}>{isLastStep ? "Finish" : "Next"}</button>
</div>
);
Upvotes: 0