How to type multiple refs with forwardRef in React?

759 Views Asked by At

I have Parent component where I define some refs. Then I create an object of all these refs. And then pass this object to ref of child component.

Parent component is just a page component, child components are sections on this page. And Inside child components I have button, by click on which I scroll to next section.


const Home: React.FC = () => {
  const effectiveCoursesRef = useRef(null);
  const reviewsRef = useRef(null);
  const howWorksRef = useRef(null);
  const scienceBehindRef = useRef(null);
  const FAQRef = useRef(null);

  const refObject = {
    effectiveCoursesRef,
    reviewsRef,
    howWorksRef,
    scienceBehindRef,
    FAQRef,
  };
  
  return (
    <MainLayout
    >
      <main>
        <EffectiveCourses ref={refObject} />
        <Reviews ref={refObject} />
        <HowSleepSchoolWork ref={refObject} />
        <ScienceBehind ref={refObject} />
        <FAQ ref={refObject} />
      </main>
    </MainLayout>
  );
};

export default Home;

In child component I'm getting refs in forwardRef


const ScienceBehind = forwardRef((_, ref: any) => (
  <section className={`${styles.section}`} ref={ref.scienceBehindRef}>
    <div className="container">
      <h2 className={styles.sectionTitle}>Science behind Sleep School</h2>
      <ArrowButton ref={ref.FAQRef} color="#082B4B" />
    </div>
  </section>
));

export default ScienceBehind;

This approach works fine, but I have typescript errors. Please help me to figure out how to type this code and solve errors. Or a better approach how to achieve the same. Here is error from Parent component And Here is error from child component

Any help appriciated! Regards, Anna.

I have tried to type it, but I still have error.

enter image description here

2

There are 2 best solutions below

3
sterling On

Sometime ago i was also facing somewhat similar issue as yours and this did the trick for me. Although I am not sure if this will work for you as well but you can try adding type when you define your ref.

const scienceBehindRef = useRef<ScienceBehindRefType>(null);

Edit 2023-08-31

Sorry about that, i tried to recreate your issue what is basicly happening is that when you wrap your ScienceBehind with forwardRef it expects a ref in the ref key

And in your case you are passing a object containing the refs

<ScienceBehind
  ref={refObject} // expects a ref
/>

I would suggest you should do it like this if you want to pass multiple refs in a component

<ScienceBehind
  ref={refObject.scienceBehindRef} // or just pass scienceBehindRef directly
  otherRefs={refObject}
/>
0
Ann On

I still not resolved problem with typings, but for now it seems not resolvable. Because React doeasn't expect we will pass multiple refs with forward ref I guess. If someone come up with a solution please reply.

I implemented another approach. Maybe this will help for someone as well. The approach is passing normal ref and onClick to child component, inside onClick call goToSection function. In this approah I do not need to pass ref to child and I do not have typings problem.

Here is Parent

const Home: React.FC = () => {
  const effectiveCoursesRef = useRef(null);
  const reviewsRef = useRef(null);
  const howWorksRef = useRef(null);
  const scienceBehindRef = useRef(null);
  const FAQRef = useRef(null);
  const moreSleepHelpRef = useRef(null);

  const isTabletOrMobile = useMediaQuery({ query: `(max-width: 875px)` });


  return (
    <MainLayout
    >
      <main>
        <HowSleepSchoolWork
          ref={howWorksRef}
          onClick={() =>
            goToSection(document, scienceBehindRef, isTabletOrMobile)
          }
        />
        <ScienceBehind
          ref={scienceBehindRef}
          onClick={() => goToSection(document, FAQRef, isTabletOrMobile)}
        />
        <FAQ
          ref={FAQRef}
          onClick={() =>
            goToSection(document, moreSleepHelpRef, isTabletOrMobile)
          }
        />
      </main>
    </MainLayout>
  );
};

export default Home;

Here is Child component

const ScienceBehind = forwardRef(
  ({ onClick }: Props, ref: React.Ref<HTMLDivElement>) => (
    <section className={`${styles.section}`} ref={ref}>
      <div className="container">
        <div>
          <ArrowButton color="#082B4B" onClick={onClick} />
        </motion.div>
      </div>
    </section>
  ),
);

export default ScienceBehind;

Here is goToSection function

export const goToSection = (document, ref, isTabletOrMobile) => {
  if (document) {
    const header = document.querySelector(`header`);
    const headerHeight = header.offsetHeight;
    window.scrollTo({
      top: isTabletOrMobile
        ? ref.current.offsetTop
        : ref.current.offsetTop - headerHeight,
      behavior: `smooth`,
    });
  }
};