Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React - Forwarding multiple refs

Tags:

reactjs

I have a SideNav component that contains dynamically created links that need to scroll to a corresponding header in an adjacent html table (InfoTable). I've tried multiple different ways of accomplishing this to no avail.

export default class Parent extends Component {
  state = {
    categories: [],
  }

  scrollToHeader = (tableRefString) => {
    // Function to pass to SideNav to access refs in InfoTable
    this[tableRefString].scrollIntoView({ block: 'start' });
  }

  render() {
    return (
      <div>
        <SideNav
          categories={this.state.categories}
          scrollToHeader={this.scrollToHeader} />
        <InfoTable
          categories={this.state.categories} />
      </div>
    );
  }
}

export default class InfoTable extends Component {
  render() {
    return (
      <div>
        <table>
          <tbody>
            {this.props.categories.map(category => (
              <>
                // Forward the ref through InfoTableHeader to be set on the parent DOM node of each InfoTableHeader
                <InfoTableHeader />
                {category.inputs.map(input => <InfoTableRow />)}
              </>
            ))}
          </tbody>
        </table>
      </div>
    );
  }
}

In order to click a link on SideNav and scroll to the corresponding header on InfoTable, I believe that I need to forward refs that are dynamically created on Parent based on names in my categories array and set these refs to the DOM nodes for each header in InfoTable. From there I would pass a function to SideNav that could access the refs in Parent in order to scroll to the header.

  • How can I go about forwarding multiple refs at once to my InfoTable component?
  • Is there a cleaner way to accomplish what I'm trying to do? I've looked into React.findDOMNode() but refs seem to be the better option.
like image 358
EJ Morgan Avatar asked Nov 30 '18 17:11

EJ Morgan


3 Answers

I know there is an already accepted answer, and while I find @nicholas-haley's solution acceptable. I think a better way to go about it would be to use the built-in useImperativeHandle hook.

IMPORTANT: The React Hooks Api is available as of

The React hooks API Docs state:

useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with `forwardRef

This note is followed by the following example:

function FancyInput(props, ref) {
  const inputRef = useRef();
  useImperativeHandle(ref, () => ({
    focus: () => {
      inputRef.current.focus();
    }
  }));
  return <input ref={inputRef} ... />;
}
FancyInput = forwardRef(FancyInput);

Thus, in my opinion, a much cleaner solution would be to delegate the needed refs through the useImperativeHandle hook.

This way there is no need for a special ref syntax, and the component can simply return a specific type of a FatherRef; Example:

// LabelInput.js
function LabelInput(props, ref) {
    const labelRef = useRef();
    const inputRef = useRef();

    useImperativeHandle(ref, () => ({
      focus: () => {
       inputRef.current.focus();
      },
      get input() {
          return inputRef.current;
      },
      get label() {
          return labelRef.current;
      },
      // ... whatever else one may need
    }));
    return (
      <div>
        <label ref={labelRef} ... />
        <input ref={inputRef} ... />;
      </div>
    )
}
LabelInput = forwardRef(LabelInput);

function MyScreen() {
   const labelInputRef = useRef();

   const onClick = useCallback(
    () => {
//       labelInputRef.current.focus(); // works
//       labelInputRef.current.input.focus(); // works
// ... etc
    },
    []
   );

   return (
     ...
      <LabelInput ref={labelInputRef} ... />
     ....
   )
}
like image 166
Samer Murad Avatar answered Oct 19 '22 04:10

Samer Murad


I actually just picked this up from react-hook-form, but they presented a nice way to share ref and assign multiple refs at once:

<input name="firstName" ref={(e) => {
  register(e) // use ref for register function
  firstNameRef.current = e // and you can still assign to ref
}} />
like image 4
amaster Avatar answered Oct 19 '22 03:10

amaster


I had a similar situation where I needed multiple refs to be forwarded to the child of my Parent component.

I still have not found an elegant solution, however you might try passing your refs as an object, and destructuring within the forwardRef callback:

// Parent
ref={{
    ref1: this.ref1,
    ref2: this.ref2
}}

// Child
export default React.forwardRef((props, ref) => {
  const { ref1, ref2 } = ref;

  return (
    <Child1
      {...props}
      ref={ref1}
    />
    <Child2
      {...props}
      ref={ref2}
    />
  );
});

I'm not a big fan of the naming here (I'd prefer ref to be called refs), but this works in case you're in a pinch.

EDIT:

In 2020 I believe @samer-murad's answer is the best solution to this problem.

like image 52
Nicholas Haley Avatar answered Oct 19 '22 02:10

Nicholas Haley