React Refs not working with a dynamically generated component in a higher-order component (HOC)

74 Views Asked by At

I'm encountering an issue with React refs that's a bit perplexing. I have a higher-order component (HOC) that dynamically generates child components based on a prop, and I'm trying to use refs to access those child components. However, it seems like the refs aren't being properly set, and I can't figure out why.

Here's a simplified example of my code:

import React, { useRef, useEffect } from 'react';

const DynamicComponent = ({ name }) => {
  // Some component logic here...
  return <div>{name}</div>;
};

const HOC = ({ dynamicComponentProps }) => {
  const dynamicRef = useRef();

  useEffect(() => {
    console.log(dynamicRef.current); // This logs 'null'
    // Some other logic with dynamicRef.current...
  }, [dynamicRef.current]);

  const renderDynamicComponent = () => {
    return dynamicComponentProps.map((props, index) => (
      <DynamicComponent ref={dynamicRef} key={index} {...props} />
    ));
  };

  return <div>{renderDynamicComponent()}</div>;
};

const App = () => {
  const dynamicComponentProps = [
    { name: 'Component A' },
    { name: 'Component B' },
    { name: 'Component C' },
  ];

  return <HOC dynamicComponentProps={dynamicComponentProps} />;
};

In the useEffect block inside the HOC, the dynamicRef.current is always null, even though the components are rendered. Am I missing something about refs in dynamically generated components, or is there a better approach to achieve what I'm trying to do?

What I Tried:

I attempted to use React refs in a higher-order component (HOC) that dynamically generates child components based on a prop. I created a ref (dynamicRef) within the HOC and assigned it to each dynamically generated child component.

What I Expected:

I expected that when I access dynamicRef.current in the useEffect block, it would point to the last dynamically generated child component, allowing me to perform actions or access properties of that component.

What Actually Resulted:

Surprisingly, when I logged dynamicRef.current, it always showed null, indicating that the refs weren't being properly set. Despite the components being rendered, the ref wasn't pointing to any of them.

1

There are 1 best solutions below

3
DuckDuckGoose On BEST ANSWER

The problem is that you are trying to assign the same ref for all your components. a ref needs to be assined to a single instance of a component so we can modify your code to the following

import React, { useRef, useEffect } from "react";

const DynamicComponent = React.forwardRef(({ name }, ref) => {
  // Some component logic here...
  return <div ref={ref}>{name}</div>;
});

const HOC = ({ dynamicComponentProps }) => {
  const dynamicRefs = dynamicComponentProps.map(() => useRef(null));

  useEffect(() => {
    dynamicRefs.forEach((ref) => {
      console.log(ref.current); // This should log the div element
      // Some other logic with ref.current...
    });
  }, [dynamicRefs]);

  const renderDynamicComponent = () => {
    return dynamicComponentProps.map((props, index) => (
      <DynamicComponent ref={dynamicRefs[index]} key={index} {...props} />
    ));
  };

  return <div>{renderDynamicComponent()}</div>;
};

const App = () => {
  const dynamicComponentProps = [
    { name: "Component A" },
    { name: "Component B" },
    { name: "Component C" },
  ];

  return <HOC dynamicComponentProps={dynamicComponentProps} />;
};

export default App;

in this updated version we have a seperate ref foreach component. to make this work we add to modfiy a few other things as well

  1. changed DynamicComponent to forwardRef so that we can pass a ref
  2. in the useEffect we log the array of refs
  3. in renderDynamiccomponent we assign a different ref to each component

EDIT

As pointed out in the comments the above code does break react rule of calling hook in side a loop so we can change the code a little instead of having an array of refs we now have a ref containings an array

import React, { useRef, useEffect } from "react";

const DynamicComponent = React.forwardRef(({ name }, ref) => {
  // Some component logic here...
  return <div ref={ref}>{name}</div>;
});

const HOC = ({ dynamicComponentProps }) => {
  const dynamicRefs = useRef(
    dynamicComponentProps.map(() => ({ current: null }))
  );

  useEffect(() => {
    dynamicRefs.current.forEach((ref) => {
      console.log(ref.current); // This should log the div element
      // Some other logic with ref.current...
    });
  }, [dynamicRefs.current]);

  const renderDynamicComponent = () => {
    return dynamicComponentProps.map((props, index) => (
      <DynamicComponent
        ref={dynamicRefs.current[index]}
        key={index}
        {...props}
      />
    ));
  };

  return <div>{renderDynamicComponent()}</div>;
};

const App = () => {
  const dynamicComponentProps = [
    { name: "Component A" },
    { name: "Component B" },
    { name: "Component C" },
  ];

  return <HOC dynamicComponentProps={dynamicComponentProps} />;
};

export default App;