Aqib Naeem
Aqib Naeem

Reputation: 441

Binding functions in react js

I am binding function in two ways, the first one is working fine, but its a bad approach to use es6 function syntax inside return method as it calls a new method on every instance, all the code is working fine, i just need help with binding function. Binding in return statement works fine.

import React, { Component } from 'react';
import './ColorBox.css';

export default class ColorBox extends Component{
    //Constructor
    constructor(props){
        super(props);
        this.state = { copied: false};
    }
    
    // Not binded because its called inside the binded function, althrough it doesnot matter
    changeCopyState(){ 
        this.setState({ copied: true}, () => (
            setTimeout(() => (
                this.setState({copied: false})
            ), 1500)
        ))
    }

    // Function to copy color code to clipboard
    copyToClipBoard(str) {
        const element = document.createElement('textarea');
        element.value = str;
        document.body.appendChild(element);//insert textarea into html body
        element.select();
        document.execCommand('copy');//internal command to copy from text area
        document.body.removeChild(element);//remove after coping command
        this.changeCopyState();
    }

    render(){
        let {name, background} = this.props;
        let {copied} = this.state;  
        return (
            <div style={{ background}} className="ColorBox">
                <div style={{ background }} className={`copy-overlay ${ copied && "show"}`} /> 
                    <div className="box-content">
                        <span>{name}</span>
                    </div>
                    <button className="copy-button" onClick={() => this.copyToClipBoard(background)}>Copy</button>
                <span className="more-shades">More</span>
            </div>
        )
    }
}

now if i try to bind the function inside the constructor, it will give me an error of exceeding limit on calling function etc, why is this behaviour occurring.

import React, { Component } from 'react';
import './ColorBox.css';

export default class ColorBox extends Component{
    //Constructor
    constructor(props){
        super(props);
        this.state = { copied: false};
        this.copyToClipBoard = this.copyToClipBoard.bind(this);
    }
    
    // Not binded because its called inside the binded function, althrough it doesnot matter
    changeCopyState(){ 
        this.setState({ copied: true}, () => (
            setTimeout(() => (
                this.setState({copied: false})
            ), 1500)
        ))
    }

    // Function to copy color code to clipboard
    copyToClipBoard(str) {
        const element = document.createElement('textarea');
        element.value = str;
        document.body.appendChild(element);//insert textarea into html body
        element.select();
        document.execCommand('copy');//internal command to copy from text area
        document.body.removeChild(element);//remove after coping command
        this.changeCopyState();
    }

    render(){
        let {name, background} = this.props;
        let {copied} = this.state;  
        return (
            <div style={{ background}} className="ColorBox">
                <div style={{ background }} className={`copy-overlay ${ copied && "show"}`} /> 
                    <div className="box-content">
                        <span>{name}</span>
                    </div>
                    <button className="copy-button" onClick={this.copyToClipBoard(background)}>Copy</button>
                <span className="more-shades">More</span>
            </div>
        )
    }
}

Upvotes: 1

Views: 778

Answers (2)

Drew Reese
Drew Reese

Reputation: 203427

Issue

onClick={this.copyToClipBoard(background)} invokes the callback immediately, thus causing the render looping you see.

Solutions

  1. Convert copyToClipBoard to a curried function so it consumes a background argument, closed over in scope, and returns a function to be used as the callback.

    // Function to copy color code to clipboard
    copyToClipBoard(str) {
      return () => {
        const element = document.createElement('textarea');
        element.value = str;
        document.body.appendChild(element); //insert textarea into html body
        element.select();
        document.execCommand('copy'); //internal command to copy from text area
        document.body.removeChild(element); //remove after coping command
        this.changeCopyState();
      };
    }
    

    Usage:

    <button
      className="copy-button"
      onClick={this.copyToClipBoard(background)} // <-- returns callback handler
    >
      Copy
    </button>
    
  2. Convert copyToClipBoard to a curried arrow function so this is bound automatically and you don't need the binding in the constructor.

    // Function to copy color code to clipboard
    copyToClipBoard = (str) => () => {
      const element = document.createElement('textarea');
      element.value = str;
      document.body.appendChild(element); //insert textarea into html body
      element.select();
      document.execCommand('copy'); //internal command to copy from text area
      document.body.removeChild(element); //remove after coping command
      this.changeCopyState();
    };
    

    Usage:

    <button
      className="copy-button"
      onClick={this.copyToClipBoard(background)} // <-- returns callback handler
    >
      Copy
    </button>
    
  3. background is available already in props, just consume it from there.

    // Function to copy color code to clipboard
    copyToClipBoard(str) {
      const { background } = this.props; // <-- destructure from props
    
      const element = document.createElement('textarea');
      element.value = background; // <-- use background
      document.body.appendChild(element); //insert textarea into html body
      element.select();
      document.execCommand('copy'); //internal command to copy from text area
      document.body.removeChild(element); //remove after coping command
      this.changeCopyState();
    }
    

    Usage:

    <button
      className="copy-button"
      onClick={this.copyToClipBoard} // <-- just attach handler
    >
      Copy
    </button>
    

Upvotes: 2

Yamo93
Yamo93

Reputation: 512

It's probably because of a circular reference. You're accessing something which is accessing something, resulting in an infinite amount of calls and causing the call stack to exceed its limit.

You could instead just do the binding inside of the onClick method:

<button className="copy-button" onClick={this.copyToClipBoard.bind(this, background)}>Copy</button>

Upvotes: 0

Related Questions