Elyasaf755
Elyasaf755

Reputation: 3559

Expose a Custom Hook to Children (children only) in React

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

Answers (1)

RubenSmn
RubenSmn

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

Related Questions