1

I am facing an issue and don't understand why my code doesn't work.

The point :

  • I have a series of buttons, each one representing keyboard keys, and the content of each button is the keyboard key "value" (A, B, C, D,…)
  • I have a SFC named Key.js which creates a button with a given letter associated (done in an external component function)
  • In the render() function of App.js, I map LETTERS (an array containing all letters from A to Z) to create the button list
  • When I click on one button, I want to get the letter associated with it (this I had done) but also get the HTML button element clicked (to check for specific attributes etc…). This is the problem


Here is the code for Key.js :

const Key = ({ letter, onClick }) => (
    <button className={`keyboardLetter`} onClick={() => onClick(letter)} >{ letter }</button>
);

export default Key;


Here is the code rendering it in App.js :

render() {
    return (
        <div className="keyboard">
            {
                // LETTERS is an array containing letters from A to Z
                LETTERS.map((letter, index) => (
                    <Key letter={letter} onClick={this.handleKeyClick} key={index} />
                ))
            }
        </div>
    )
}


Here is the code for the handleKeyClick method :

// Arrow function for binding
handleKeyClick = (letter) => {
    // Here I do several operations for my app to work (setState() etc…)

    // I can get the letter I clicked by doing :
    console.log(letter);

    // The point is I can not retrieve the HTML button element clicked, even with :
    console.log(letter.target) // returns undefined

    // letter.currentTarget, .parentElement or .parentNode also return undefined
};


I have found a workaround, but if I understood correctly is not the best practice to do. It is changing the onClick event on Key.js SFC to this :

onClick={(event) => onClick(letter, event)}

And then use the event property like so in my method :

handleKeyClick = (letter, event) => { }

With this I can get the button element clicked with event.target, but if I understood correctly would cause less performances and seems more trouble and code to get my App working as wanted.

  • I don't understand why my binding with the arrow function on handleKeyClick doesn't let me get to the HTML element clicked ?


Thank you very much for your time and help.

Tom

2
  • 2
    Have you tried refs? reactjs.org/docs/refs-and-the-dom.html Commented May 11, 2020 at 12:46
  • I will get into the refs but for now I don't think I need it, as I am following a tutorial and we will see the refs after this activity. Don't you think there is another possibility with my current code ? Could you also confirm that the workaround I did is not good practice ? Thank you very much Commented May 11, 2020 at 12:52

3 Answers 3

1

There is no need to access your button's element in this case.

You can achieve it by giving your function multiple parameters groups so that you preconfigure it:

handleKeyClick = (letter) => (ev) => {
    console.log(letter);
    console.log(ev);
};

This function will need to be called twice to execute what's inside. The first call will set the letter parameter and return a function that only requires the event.

You can now call the function within the click function itself:

LETTERS.map((letter, index) => (
    <Key letter={letter} onClick={this.handleKeyClick(letter)} key={index} />
))

The handler won't be called directly, this parameter only sets the letter variable in the function.

Here is how you can get the event information, by making the event directly to your function:

<button className={`keyboardLetter`} onClick={onClick} >{ letter }</button>

You were previously ignoring the event returned by onClick which is in its first parameter : onClick={(/* event variable missing */) => onClick(letter)}

Sign up to request clarification or add additional context in comments.

11 Comments

First, thank you for your answer. Unfortunatly, console.log(letter) and console.log(ev) returns the same thing : the letter in the button element. I still cannot get to the HTML button element (ev.target, currentTarget or .parentElement still return undefined)
Sorry, I think I missread the question about getting the button's ref. I edited my answer.
Out of curiosity, for what reason do you need the button's ref ?
Your solution works perfectly, thank you. However, what do you thing would be the best, in terms of performances and maintenance / usability, between the workaround shown at the end of my question (using the event returned in onClick as first parameter) and using your technique ? I need to get the button's ref to do some checking on some of its attributes (classList.contains(), stuff like that)
Thanks a lot for hooking me up to the hooks !! It really is a better DX for me to code in React with hooks / functional components than classes. Real game changer here ! Thank you very much for pointing me out in this direction
|
0

In your Key.js file, you need to send the event if you want to access it later.

const Key = ({ letter, onClick }) => (
    <button className={`keyboardLetter`} onClick={(e) => onClick(e,letter)} >{ letter }</button>
);

Add both event and letter arguments to your handleKeyClick function

const handleKeyClick = (e, letter) => {
   console.log(letter);
   console.log(e.target);
};

Pass click event and letter to your handleKeyClick function

LETTERS.map((letter, index) => (
  <Key letter={letter} onClick={(e, letter) => this.handleKeyClick(e, letter)} key={index} />
))

1 Comment

This is like the workaround I posted at the end of my question, except for the fact that using it, I don't need to pass the click event and letter to my handleKeyClick function (as you mentioned as the last step). Using this workaround, it works with onClick={this.handleKeyClick} without passing it any parameters. What would be the difference if I do pass the parameters and if I don't ? Also, isn't it causing less performances ?
0
    const Key = ({ letter, onClick }) => (
    <button className={`keyboardLetter`} onClick={(e) => onClick(e, letter)} >{letter}</button>
);

 handleKeyClick = (e, letter) => {
        debugger
        //e -- event
        //letter 
    }

  render() {
        let LETTERS = ["a", "b"]
        return (
            <Fragment>
                <div className="keyboard">
                    {
                        LETTERS.map((letter, index) => (
                            <Key letter={letter} onClick={(e) => { this.handleKeyClick(e, letter) }} key={index} />
                        ))
                    }
                </div>
            </Fragment>
        );
    }

4 Comments

This is like the workaround I showed in the end of my question. However, using this technique, in the render() of App.js, I can use it like so : onClick={this.handleKeyClick} with no parameters or function calling it. What would be the difference between the two ? Thank you very much
if you want to pass more than two values you should have to use this way. onClick={(e) => { this.handleKeyClick(e, letter) }} this implies a method inside a method. Thank you
Won't this make the component thinking it got some new different props every time ? Leading it to re-render even when it is superfluous ? Is there a better approach to this ?
Please check the below YouTube video . This video sort out the performance issues of the button click functionality youtube.com/watch?v=xa-_FIy2NgE its a new information for me too Thank you.

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.