1

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>
        )
    }
}
1
  • Well because you're directly calling it in the JSX, so when the component renders, it calls that function. But why are you passing this.props.background to copyToClipBoard anyway? You already have access to it inside that function Commented Apr 16, 2021 at 6:13

2 Answers 2

2

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>
    
Sign up to request clarification or add additional context in comments.

1 Comment

solution 3rd is the most simpler, I have to go with it. Thanks!!
0

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>

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.