Set and access refs on {children} from parent with cloneElement

Viewed 17

I'm trying to create a full page scrolling effect similar to fullpage.js.

I would like to accomplish this by using components like this:

 <FullPageContainer>
        <FullPageSection>1</FullPageSection>
        <FullPageSection>2</FullPageSection>
        <FullPageSection>3</FullPageSection>
 </FullPageContainer>

The full page container should be able to control its view and scroll to any of its children via a function scroll(). Scroll, keyboard, and UI elements would trigger this function.

I was thinking I could create refs within <FullPageContainer> and set them on the children. This way no matter how many children were nested, they would all be linked up by refs. However I am having trouble with the implementation.

FullPageContainer

import React from "react";

export const FullPageContainer = ({
  children,
}: {
  children: React.ReactNode;
}) => {
  // Create one ref for each child
  let refs = children.map(() => React.useRef<HTMLDivElement>());

  const childrenWithProps = React.Children.map(children, (child, index) => {
    if (React.isValidElement(child)) {
      return React.cloneElement(child, { ref: refs[index] });
    }
    return child;
  });

  return (
    <div>
      <button
        onClick={() => {
          console.log(refs);
        }}
      >
        click
      </button>
      {childrenWithProps}
    </div>
  );
};

FullPageSection

import React from "react";

export const FullPageSection = React.forwardRef(
  ({
    children,
    ref,
  }: {
    children: React.ReactNode;
    ref: React.RefObject<HTMLDivElement>;
  }) => {
    return (
      <div
        ref={ref}
        style={{
          height: "100vh",
          width: "100%",
        }}
      >
        {children}
      </div>
    );
  }
);

I can't seem to access the refs from FullPageContainer. From my understanding, the refs should be set once the mounts, so I added a button to check the state of refs after everything renders. All of them are still coming back as undefined!

1 Answers

You don't need to create those ref, but use ref from child instead,

Might be this what you want?

export const FullPageContainer = ({
  children,
}: {
  children: React.ReactNode;
}) => {
  // Create one ref for each child
  let refs = useRef([]);

  const childrenWithProps = React.Children.map(children, (child, index) => {
    if (React.isValidElement(child)) {
      return React.cloneElement(child, { ref: (ref) => (refs.current[index] = ref) });
    }
    return child;
  });

  return (
    <div>
      <button
        onClick={() => {
          console.log(refs.currrent);
        }}
      >
        click
      </button>
      {childrenWithProps}
    </div>
  );
};


export const FullPageSection = ({
    children
  }: {
    children: React.ReactNode
  }) => {
    return (
      <div
        style={{
          height: "100vh",
          width: "100%",
        }}
      >
        {children}
      </div>
    );
  };
Related