william007
william007

Reputation: 18545

How to instantiate ref after passing to prop

The code below pass jsx from parent to child; The problem is that the reference row.descBox is undefined, maybe due to jsx in parent.js is not yet run; how should I make it point to the span element?

This is Parent.js

import { useEffect, useRef, useState } from "react";
import "./styles.css";
import Child from "./Child";

export default function Parent() {
  const taskRefs = useRef([]);
  const [cellContent, setCellContent] = useState([]);

  useEffect(() => {
    const cellContent = [
      {
        content: (
          <>
            <h1>
              <span
                ref={(el) => {
                  taskRefs.current[0] = el;
                }}
              >
                inside span
              </span>
            </h1>
          </>
        ),
        descBox: taskRefs.current[0]
      }
    ];

    setCellContent(cellContent);
  }, []);

  return <Child cellContent={cellContent} />;
}

This is Child.js

import { useEffect, useState } from "react";
import "./styles.css";

export const Child = ({ cellContent }) => {
  return (
    <div className="App">
      {cellContent.map((row, i) => {
        {
          console.log(row.descBox);//this is null!
          return <div key={i}>{row.content}</div>;
        }
      })}
    </div>
  );
};

export default Child;

Try the code at codesandbox

Upvotes: 0

Views: 53

Answers (1)

pilchard
pilchard

Reputation: 12920

The callback to set the ref inside the stored jsx won't be run until the component is mounted in the DOM and logging inside the map() is before that happens (you haven't even returned it yet). You'll need to log in a useEffect in order to see it set. I've added log calls to each step in the snippet below to illustrate the order.

As a side note, can't set descBox to taskRefs.current[0] as it will then be locked as undefined. You will need to assign it either the ref object taskRefs or the nested array assigned to taskRefs.current as either of these will be updated by reference when the ref is finally set).

const { useState, useEffect, useRef } = React;

function App() {
  const taskRefs = useRef([]);
  const [cellContent, setCellContent] = useState([]);

  useEffect(() => {
    const cellContent = [
      {
        content: (
          <span
            ref={(el) => {
              console.log("setting ref");
              taskRefs.current[0] = el;
            }}
          >
            inside span
          </span>
        ),
        descBox: taskRefs
      }
    ];

    setCellContent(cellContent);
  }, []);

  return <Child cellContent={cellContent} />;
}

function Child({ cellContent }) {

  useEffect(() => {
    if (cellContent.length) {
      console.log("inside useEffect. Ref:",cellContent[0].descBox.current[0]);
    } else {
      console.log('first render.');
    }
  });

  return (
    <div className="App">
      {cellContent.map((row, i) => {
        console.log("inside map. Ref:", row.descBox.current[0]);
        return <div key={i}>{row.content}</div>;
      })}
    </div>
  );
};

const container = document.getElementById('root');
const root = ReactDOM.createRoot(container);
root.render(<App />);
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>

<div id='root'></div>

sandbox

Upvotes: 1

Related Questions