Understanding components interacting (React)

Hi, I’m having trouble understanding some of the intricacies of how React components interact (I posted a similar topic yesterday but I’ve since narrowed down the question).

Say you have two React components - the first, Button:

export class Button extends React.Component {
  render() {
    return (
      <button onClick={this.props.onClick}>
        Click me!
      </button>
    );
  }
}

This returns a JSX component (which is just a regular HTML button with an onclick event attached.

The second React component, Talker:

class Talker extends React.Component {
  handleClick() {
    let speech = '';
    for (let i = 0; i < 10000; i++) {
      speech += 'blah ';
    }
    alert(speech);
  }
  
  render() {
    return <Button onClick = {this.handleClick} />;
  }
}

ReactDOM.render(
  <Talker />,
  document.getElementById('app')
);

It returns a React component, Button, and attaches an onClick event to the Button component.

What I don’t understand is why, in Button, you have to attach the onClick event as well like so:

onClick={this.props.onClick}

The actual onClick event gets added twice as an identical attribute, first in Button, then in Talker. And it works. But why does the onClick event have to be declared in the return statement of the Button component?

Hope somebody can help. :slight_smile:

Button is the child component and Talker is the parent component.

First, let’s look at the Button component class in isolation. This class has a render method which includes the statement this.props.onClick. This tells us that this component’s properties object is expected to have an entry with the key onClick. However, we haven’t specified any default properties and we haven’t set the property anywhere in the body of this Button class. Combining this with the observation that this class is being exported helps us deduce that the onClick prop will be provided by some parent component. So, we trust that when the time comes to render this button, the parent component will already have provided the missing piece.
Another thing to note about <button onClick={this.props.onClick}> is the attribute name onClick (the part on the left of the = ). We aren’t dealing with pure HTML but JSX, so case sensitivity is important. If we want to use basic HTML elements, we use lowercase e.g. <div>, <span>, <button> etc. If we want to use User-Defined Components, then we must capitalize the first letter e.g. <Button>. This helps JSX differentiate between HTML elements and component instances. (If we were working in pure HTML, then <button>, <Button>, <BUTTON> would all work and create a button element, because tags are case-insensitive in HTML. But Javascript is case sensitive and in JSX <button> and <Button> are treated differently).
<button onClick={this.props.onClick}> creates an HTML button and for this reason, onClick is not an arbitrary attribute. It is an Event attribute which “fires on a mouse click on the element”. So if we want something to happen once the button is clicked, then the onClick attribute is appropriate.

Now, let’s look at the parent Talker component class. It has an event handling method handleClick and a render method. Let’s focus on the render method: return <Button onClick = {this.handleClick} />;
Notice the capitalization of the first letter. This means that we aren’t creating a normal HTML button, but are creating an instance of the Button class component. There is nothing special about the name Button. It is the choice of the programmer. If you wanted, you could have named your class Mutton instead of Button in which case the JSX would be return <Mutton onClick = {this.handleClick} />; . Of course, Mutton is a bad choice because it doesn’t convey the intent of the component, but instead of Button & Talker components, if we had Mutton & Talker components, the code would work fine.

Since Button is not an HTML button, so the onClick attribute isn’t attaching an onClick event to the Button component. Rather a property is being passed to the Button component instance. Recall in our Button class’s render method, we had this.props.onClick and we said that some parent component would provide this missing piece. Since we want to provide the onClick property in the Button class’s props, that is why we chose to do so by <Button onClick = {this.handleClick} />.

If you are still confused, let’s change things a bit and hopefully it will make sense.
Suppose in our Button class, we edited <button onClick={this.props.onClick}> to
<button onClick={this.props.awesomeProp}>
Now, the Button class component expects that when the time is right, some parent component will provide it with the missing info.
In the Talker class, we would have to edit return <Button onClick = {this.handleClick} />; to
return <Button awesomeProp = {this.handleClick} />;
See how the attribute awesomeProp above matches what the Button component is expecting.
If we had done something like <Button greatProp = {this.handleClick} />; , it won’t work because the Button component instance is expecting to be passed a property with the name awesomeProp. The name of the attribute being provided by the parent component must match the property name expected by the child component.

If you are wondering why didn’t we write the handleClick event handler in the Button class instead of the Talker class, here are my thoughts on the reason.


So, basically what is happening is:

  • We render an instance of the Talker component class via ReactDOM.render( <Talker /> ...)

  • In the process of rendering the Talker component instance, its render method is fired up. An instance of the Button component class is rendered with a property named onClick being passed to the Button component instance. (Since Button was imported in Talker.js, so Talker knows what the word Button means. If Button wasn’t imported, it wouldn’t work because <Button> is different from <button> and without an import, the word Button would be meaningless to the Talker class),

  • In the process of rendering the Button component instance, its render method is fired up. An HTML button with text ‘Click me!’ is created. The HTML button has an onClick Event Attribute whose value is the handleClick event handler passed by the parent Talker component.

1 Like

Hi,

Thank you so much for the detailed response - it was extremely helpful. I think I understand what you’re saying, so I broke it down like so:

  1. The object generated by Button is a regular HTML button with an undefined onClick event.

  2. When the Button is being modified by Talker, it’s actually in the form of a React component ( Button ) and not an HTML object ( button ).

  3. Once the Button is being modified by Talker, it is no longer possible to attach regular HTML events or attributes seeing as it’s now a React component and not an HTML object.

  4. So Talker actually just “reassigns” the undefined onClick event of this instance of the Button component. Had the HTML button generated by Button not been assigned an onClick event within the return statement of Button, it would not be possible to assign a method to the onClick event within Talker’s return statement because you cannot assign regular HTML attributes or events to a React component in the same way you would an HTML object. This is why it needs to be assigned as an attribute (or expected prop) to the base HTML object and then reassigned in the parent (or child?) component Talker.

  5. Should you then want a button that executes another method, you could write another React component to generate it from the same original Button component as this just returns an HTML button as a component with an undefined onClick event that can then be reassigned.

Is this correct? If so, it actually makes… well, perfect sense. :slight_smile:

I tested it by trying to add an attribute of “disabled” to both the HTML object within Button and the returned Button within Talker. It only disables the button correctly if it is applied to the original HTML button, and not to the Button that is being returned within Talker.