user1354934
user1354934

Reputation: 8841

How do I access refs of a child component in the parent component

If I have something like

<Parent>
  <Child1 />
  <Child2 />
  <Child3 />
</Parent>

And I want to access from Child2 where I have refs="child2refs", how can I do that?

Upvotes: 107

Views: 264050

Answers (8)

arpl
arpl

Reputation: 3633

Recommended for hooks-based React

Ref forwarding is an opt-in feature that lets some components take a component reference they receive, and pass it further down (in other words, “forward” it) to a child.

To do this, we need to make sure the child component has been decorated with the forwardRef function, and the parent needs to create refs using useRef and then pass that ref using the ref={ref} property.

After mounting, the reference itself is reference to the child component, and reference.current points to the child component's

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

const LibraryButton = forwardRef((props, ref) => (
  <input type="text" {...props} ref={ref} placeholder="..."/>
));

function autofocusClick(evt) {
  console.log("fancy!"); 
}
    
const AutoFocus = (props) => {
  const childRef = useRef();

  useEffect(() => childRef.current.focus(), []);

  return <LibraryButton onClick={autofocusClick} ref={childRef} />;
}

Recommended for class-based React (16.3 and newer)

For older React code bases that don't use hooks, we need to use the createRef mechanism instead:

import React from "react";
    
const LibraryButton = React.forwardRef((props, ref) => (
  <input type="text" ref={ref} {...props} placeholder="..."/>
));

function autofocusClick(evt) {
  console.log("fancy!"); 
}

class AutoFocus extends React.Component {
  constructor(props) {
    super(props);
    this.childRef = React.createRef();
  }

  componentDidMount() {
    this.childRef.current.focus();
  }

  render() {
    return <LibraryButton onClick={autofocusClick} ref={this.childRef} />;
  }
}

Forwarding refs HOC example

Created Components are forwarding their ref to a child node.

function logProps(Component) {
  class LogProps extends React.Component {
    componentDidUpdate(prevProps) {
      console.log('old props:', prevProps);
      console.log('new props:', this.props);
    }
 
    render() {
      const {forwardedRef, ...rest} = this.props;
 
      // Assign the custom prop "forwardedRef" as a ref
      return <Component ref={forwardedRef} {...rest} />;
    }
  }
 
  // Note the second param "ref" provided by React.forwardRef.
  // We can pass it along to LogProps as a regular prop, e.g. "forwardedRef"
  // And it can then be attached to the Component.
  return React.forwardRef((props, ref) => {
    return <LogProps {...props} forwardedRef={ref} />;
  });
}

See Forwarding Refs in React docs.

Recommended for React versions older than 16.3

If it cannot be avoided the suggested pattern extracted from the React docs would be:

import React, { Component } from 'react';

const Child = ({ setRef }) => <input type="text" ref={setRef} />;

class Parent extends Component {
    constructor(props) {
        super(props);
        this.setRef = this.setRef.bind(this);
    }

    componentDidMount() {
        // Calling a function on the Child DOM element
        this.childRef.focus();
    }

    setRef(input) {
        this.childRef = input;
    }

    render() {
        return <Child setRef={this.setRef} />
    }
}

The Parent forwards a function as prop bound to Parent's this. When React calls the Child's ref prop setRef it will assign the Child's ref to the Parent's childRef property.

Upvotes: 106

Alessia
Alessia

Reputation: 959

If you want to get the ref of children when in the parent component

// children is an array
const updatedChildren = Children.map(children, (child) => {
  return { ...child.props, ref: child.ref };
}),

// children is not an array
const childrenRef = children.ref;

Upvotes: 0

Monsignor
Monsignor

Reputation: 2947

If everything you have is props.children:

const Parent = (p: {children: JSX.Element}) => {
    const childRef = useRef()

    return React.cloneElement(p.children, { ref: childRef })
}
<Parent>
  <SingleChild />
</Parent>

Note that it will fail if your child cannot have a ref, e.g. React.Fragment.

Upvotes: 13

Ryan Weiss
Ryan Weiss

Reputation: 1318

Here is how I solve the problem for dynamic components:

On the parent, dynamically create references to the child components, for example:

class Form extends Component {
    fieldRefs: [];

    // dynamically create the child references on mount/init
    componentWillMount = () => {
        this.fieldRefs = [];
        for(let f of this.props.children) {
            if (f && f.type.name == 'FormField') {
                f.ref = createRef();
                this.fieldRefs.push(f);
            }
        }
    }

    // used later to retrieve values of the dynamic children refs
    public getFields = () => {
        let data = {};

        for(let r of this.fieldRefs) {
            let f = r.ref.current;
            data[f.props.id] = f.field.current.value;
        }

        return data;
    }
}

The Child component (ie <FormField />) implements it's own 'field' ref, to be referred to from the parent:

class FormField extends Component {
    field = createRef();
    
    render() {
        return(
            <input ref={this.field} type={type} />
        );
    }
}

Then in your main page, the "parent's parent" component, you can get the field values from the reference with:

class Page extends Component {
    form = createRef();

    onSubmit = () => {
        let fields = this.form.current.getFields();
    }

    render() {
        return (
            <Form ref={this.form}>
                <FormField id="email" type="email" autoComplete="email" label="E-mail" />
                <FormField id="password" type="password" autoComplete="password" label="Password" />

                <div class="button" onClick={this.onSubmit}>Submit</div>
            </Form>
        );
    }
}

I implemented this because I wanted to encapsulate all generic form functionality from a main <Form /> component, and the only way to be able to have the main client/page component set and style its own inner components was to use child components (ie. <FormField /> items within the parent <Form />, which is inside some other <Page /> component).

So, while some might consider this a hack, it's just as hackey as React's attempts to block the actual 'ref' from any parent, which I think is a ridiculous design, however they want to rationalize it.

Upvotes: 1

narmageddon
narmageddon

Reputation: 1167

Here is an example that will focus on an input using refs (tested in React 16.8.6):

The Child component:

class Child extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
  }
  render() {
    return (<input type="text" ref={this.myRef} />);
  }
}

The Parent component with the Child component inside:

class Parent extends React.Component {
  constructor(props) {
    super(props);
    this.childRef = React.createRef();
  }
  componentDidMount() {
    this.childRef.current.myRef.current.focus();
  }
  render() {
    return <Child ref={this.childRef} />;
  }
}

ReactDOM.render(
    <Parent />,
    document.getElementById('container')
);

The Parent component with this.props.children:

class Parent extends React.Component {
    constructor(props) {
        super(props);
        this.childRef = React.createRef();
    }
    componentDidMount() {
        this.childRef.current.myRef.current.focus();
    }
    render() {
        const ChildComponentWithRef = React.forwardRef((props, ref) =>
            React.cloneElement(this.props.children, {
                ...props,
                ref
            })
        );
        return <ChildComponentWithRef ref={this.childRef} />
    }
}

ReactDOM.render(
    <Parent>
        <Child />
    </Parent>,
    document.getElementById('container')
);

Upvotes: 18

OZZIE
OZZIE

Reputation: 7338

I think this guide explains it pretty well https://github.com/yannickcr/eslint-plugin-react/issues/678

class Field extends Component {
  const { inputRef } = this.props;
  render() {
    return (
      <input type="text" ref={inputRef} />
    )
  }
}

class MyComponent extends Component {
  componentDidMount() {
    this.inputNode.focus();
  }

  render() {
    return (
      <div>
        Hello, <Field inputRef={node => this.inputNode = node} />
      </div>
    )
  }
}

Upvotes: 2

Nikhil Fadnis
Nikhil Fadnis

Reputation: 860

  1. Inside the child component add a ref to the node you need
  2. Inside the parent component add a ref to the child component.
/*
* Child component
*/
class Child extends React.Component {
  render() {
    return (
      <div id="child">
        <h1 ref={(node) => { this.heading = node; }}>
          Child
        </h1>
      </div>
    );
  }
}

/*
 * Parent component
 */
class Parent extends React.Component {
  componentDidMount() {
    // Access child component refs via parent component instance like this
    console.log(this.child.heading.getDOMNode());
  }

  render() {
    return (
      <div>
        <Child
          ref={(node) => { this.child = node; }}
        />
      </div>
    );
  }
}

Demo: https://codepen.io/itsfadnis/pen/aLWVVx?editors=0011

Upvotes: 22

omarjmh
omarjmh

Reputation: 13888

First access the children with: this.props.children, each child will then have its ref as a property on it.

Upvotes: 16

Related Questions