12

I'm not trying to do anything hacky using refs. I just need the ref to the element because the element is a canvas, and to draw on a canvas you need its ref.

class Parent extends Component {
  clickDraw = () => {
    // when button clicked, get the canvas context and draw on it.
    // how?
  }

  render() {
    return (
      <div>
        <button onClick={this.clickDraw}> Draw </button>
        <Child />
      </div>
    );
  }
}


class Child extends Component {
  componentDidMount() {
    const ctx = this.canvas.getContext('2d');
    // draw something on the canvas once it's mounted
    ctx.fillStyle = "#FF0000";
    ctx.fillRect(0,0,150,75);
  }

  render() {
    return (
      <canvas width={300}
              height={500}
              ref={canvasRef => this.canvas = canvasRef}>
      </canvas>
    );
  }
}

=====

Something I tried (which technically works but feels strange) is define the <canvas> in the parent, so in its ref function, this refers to the parent component. Then I pass the <canvas> and this.canvas to the child as two separate props. I return the <canvas> (named this.props.canvasJSX) in the child's render function, and I use this.canvas (named this.props.canvasRef) to get its context to draw on it. See below:

class Parent extends Component {
  clickDraw = () => {
    // now I have access to the canvas context and can draw
    const ctx = this.canvas.getContext('2d');
    ctx.fillStyle = "#00FF00";
    ctx.fillRect(0,0,275,250);
  }

  render() {
    const canvas = (
      <canvas width={300}
              height={500}
              ref={canvasRef => this.canvas = canvasRef}>
      </canvas>
    );
    return (
      <div>
        <button onClick={this.clickDraw}> Draw </button>
        <Child canvasJSX={canvas}
               canvasRef={this.canvas} />
      </div>
    );
  }
}


class Child extends Component {
  componentDidMount() {
    const ctx = this.props.canvasRef.getContext('2d');
    // draw something on the canvas once it's mounted
    ctx.fillStyle = "#FF0000";
    ctx.fillRect(0,0,150,75);
  }

  render() {
    return this.props.canvas;
  }
}

Is there a more standard way of achieving this?

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
tscizzle
  • 11,191
  • 15
  • 54
  • 88
  • 1
    Possible duplicate of [How do I access refs of a child component in the parent component>](https://stackoverflow.com/questions/37647061/how-do-i-access-refs-of-a-child-component-in-the-parent-component) – arpl Oct 15 '17 at 17:02
  • please use getter and setter in parent Component – zloctb Oct 19 '17 at 07:23

2 Answers2

7

You should actually be using the first approach and you can access the child elements refs in the parent

class Parent extends Component {
  clickDraw = () => {
    // when button clicked, get the canvas context and draw on it.
    const ctx = this.childCanvas.canvas.getContext('2d');
    ctx.fillStyle = "#00FF00";
    ctx.fillRect(0,0,275,250);
  }

  render() {
    return (
      <div>
        <button onClick={this.clickDraw}> Draw </button>
        <Child ref={(ip) => this.childCanvas = ip}/>;
      </div>
    );
  }
}


class Child extends Component {
  constructor() {
     super();
     this.canvas = null;
  }
  componentDidMount() {
    const ctx = this.canvas.getContext('2d');
    // draw something on the canvas once it's mounted
    ctx.fillStyle = "#FF0000";
    ctx.fillRect(0,0,150,75);
  }

  render() {
    return (
      <canvas width={300}
              height={500}
              ref={canvasRef => this.canvas = canvasRef}>
      </canvas>
    );
  }
}

You can only use this approach is the child component is declared as a class.

Shubham Khatri
  • 270,417
  • 55
  • 406
  • 400
  • Thanks! Perfect except a slight typo (`refs` should be `ref`) which I made an edit for. – tscizzle Apr 26 '17 at 02:28
  • Shubham I'm not sure how to do this implementation for multiple HTML elements [my project](https://stackblitz.com/edit/react-pulnct?file=Posts.js) – Omar Oct 13 '17 at 18:25
  • 1
    @Omar, Edited your Snippet check this https://stackblitz.com/edit/react-yqcgey?file=Posts.js – Shubham Khatri Oct 13 '17 at 18:36
1

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() {
        // Call function on Child dom element
        this.childInput.focus();
    }
    setRef(input) {
        this.childInput = input;
    }
    render() {
        return <Child setRef={this.setRef} />
    }
}

The Parent passes a function as prop bound to Parent's this. React will call the Child's ref callback setRef and attach the childInput property to this which as we already noted points to the Parent.

arpl
  • 3,505
  • 3
  • 18
  • 16