0

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.

Elyasaf755
  • 2,239
  • 18
  • 24
  • What do you mean by expose? No other component can use it? Then you can define in the same file as the component file right and not expose it – Tushar Shahi Oct 19 '22 at 09:18
  • But how do I expose it only to the children? I want each stepper to expose its data to its children with a hook. See the `StepThree` example. – Elyasaf755 Oct 19 '22 at 10:36

1 Answers1

0

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>
);
RubenSmn
  • 4,091
  • 2
  • 5
  • 24
  • Thanks for your answer. That's actually pretty much what I did. The thing is, let's say I'm at the last step, and I want to control what to do when the user clicks on "Finish". The problem is that the "Finish" button is defined on the parent component, but I need to control the behavior of the Finish's onClick based on the current step's data. For example - make a post request of a form in the last step when the user clicks on "Finish". So, for now I inject the data to the children from the parent component, so I can set an "onFinish" callback from the children, but I'm looking for a clean way – Elyasaf755 Oct 19 '22 at 10:41
  • I see your problem now, maybe it is an option to define the `Stepper` as a ContextProvider and then you can create the useStepper and get all the data via the context? – RubenSmn Oct 19 '22 at 11:40
  • That's what I initially tried to do, but then it ruins the reusability of the Stepper. I thought of making a function that will return the stepper and its context, but then it's not really intuitive to use and the developer will have to configure things before actually using the stepper. I'm looking for a more automatic & intuitive way to do it, if there is any way. – Elyasaf755 Oct 19 '22 at 12:27
  • So as I am understanding the `Stepper` needs to be reuseable, it needs to handle going to the next/previous step and you want to have the data used in the children accessible for all the children/steps. The children/steps can be anything right? – RubenSmn Oct 22 '22 at 18:40
  • @RubenSmh Exactly. But the `Stepper` is just an example of a problem I have; I'd actually want to know if it's possible regardless to this specific problem, and if yes - to learn this pattern, it'd make so many coding problems so much easier for me. – Elyasaf755 Oct 23 '22 at 08:11