React Beyond Basics
Working Components

Working with Components

Here is a summary of what we learned so far about React components.

  • A React component can be one of two types. It can be either a function component or a class component. Sometimes you will hear different terms to describe these two types, like stateless and stateful. Function components are also often associated with the presentational concept.

  • A function component is the simplest form of a React component. It is a simple function with a simple contract: A function component receives an object of properties, which is known as props. It returns what looks like HTML, but is really a special JavaScript syntax called JSX.

  • A class component also acts like a function that receives props, but it does that through an instance object instantiated from a class.

  • This private internal state is what gives React its reactive nature. When the state of a component changes, React will re-render the DOM elements represented by that component in the browser.

  • The State and Props objects have one important difference. Inside a component, the State object can be changed while the Props object represents fixed values. Components can only change their internal state, not their properties. However, the props of a component can get changed when that component’s parent component re-renders.

  • Components have special lifecycle methods that we can use to customize their story. We can do that while mounting, updating, and unmounting each component.

Here is an example of a simple React component. One without any input and with a simple h1 in a div output:

class Hello extends React.Component {
  render() {
    return (
      <div className="container">
        <h1>Getting Started</h1>
      </div>
    );
  }
}

ReactDOM.render(<Hello />, mountNode);

The Hello component above is using the JSX syntax, but remember that it can also directly use the React API itself:

class Hello extends React.Component {
  render() {
    return (
      React.createElement(
        "div",
        { className: "container" },
        React.createElement("h1", null, "Getting Started")
      )
    );
  }
}

ReactDOM.render(React.createElement(Hello), mountNode);

This version is what JSX compiles into before the code is ready to be used by the browser. It is extremely important that you always remember this translation step. We do not send JSX tags to the browser. We always send React.createElement calls. The React.createElement calls are the virtual representation of your application DOM. React efficiently translates them into official DOM operations that it performs in the browser.

With that, I think you are ready to create a simple React application.

Creating and Using a React Component

Let’s create a simple React component that returns an HTML button element. We can use the function syntax:

function Button() {
  return (
    <button>Go</button>
  );
}

The actual JavaScript that the browser sees when we use the button element in JSX is a call to the React.createElement function:

function Button() {
  return (
    React.createElement("button", null, "Go")
  );
}

While you can use React this way without JSX, it would be a lot harder to code and maintain. So, let’s stick with JSX.

The function above is a complete and simple React component. Let’s use it.

We use a component by instantiating an element from it and mounting that element in the browser. We use the ReactDOM.render method to do that.

ReactDOM.render(<Button />, mountNode);

WARNINIG: Remember that if you are not testing with the jsComplete playground you will have to replace mountNode with a reference to a DOM element in your main HTML file.

The <Button /> syntax above is the instantiating step that creates a React element.

A React function component receives the props object as its first argument. This props argument allows us to make the component reusable. For example, instead of hard-coding the "Go" label of the button above, we can pass Button a label attribute, like we do with regular HTML elements:

ReactDOM.render(<Button label="Save"/>, mountNode);

Then, we can access this label attribute inside the function returned JSX with a curly bracket for props.label.

function Button(props) {
  return (
    <button>{props.label}</button>
  );
}

The props argument is an object that holds all the values that were passed to the React element when it was instantiated.

Playground link for the code so far: jsdrops.com/rg-3.1

Making a Component Interactive

So far, we have a button HTML element and it is rendered through a React component.

Let’s now add some interactivity to this so-far boring example. Let’s make that button element increment a counter value on every click and display that value as its label. For example, let’s make the label of this button start with the number 1 and when the user clicks the button we’ll make the label change to 2, then 3 and so on.

Since this is something that needs to be reflected in the Button rendered output, it belongs to the state of the Button component. We need Button to re-render itself every time the counter value changes. We cannot use a prop here because a React component’s props cannot be changed (from within the component). By using the special React state object, we will be utilizing React’s reactive nature and we will not need to worry about how to take the changes to the browser. React will do that for us.

The Button component is currently a function component. Let’s transform it into a class component. This is very simple. We define a class and make it extend React.Component.

class Button extends React.Component {

}

In that class, we define a render method that returns the component’s JSX, which is the HTML button for this example.

render() {
  return (
    <button>1</button>
  );
}

This is a little bit more code than a function component, but we can now use a private state on this class-based Button component.

To use a state object, we first initialize it. The state object is a simple instance property, so we can initialize it inside the constructor function of the Button class. We just define the normal constructor function (which receives a props object in React-based classes) and then we need to call the super method to honor the inheritance of the component.

constructor(props) {
  super(props);
  this.state = { counter: 1 };
}

After that, we initialize this.state to whatever we want. The keys of this state object are the various elements of the state. For our case, we need a counter state, which starts from 1.

Now, inside the render method, since we can write any JavaScript expression within curly brackets, we can read the value of the new counter state element that we initialized on the state using this.state.counter.

render() {
  return (
    <button>{this.state.counter}</button>
  );
}

The “this” keyword refers to the Button instance that ReactDOM associated with the DOM element that was mounted in the browser.

You can try and change that counter state value to see how the mounted Button will render the values you put on the state.

Playground link for the code so far: jsdrops.com/rg-3.2

There is another shorter syntax to define the initial state, which is to simply use a class-field directly without a constructor call:

class Button extends React.Component {
  state = { counter: 1 };

  render() {
    return (
      <button>{this.state.counter}</button>
    );
  }
}

When you configure your own React application, you will have to use something like Babel anyway to compile JSX into JavaScript. It is an easy win to also include and use the JavaScript features that are well on their way to becoming an official part of the language.

In this example so far, we have a state object and an HTML button element that displays a counter value that we initialized on that state. Now we need to change that value when we click the button. We need to define a click handler on that button. We can use React’s onClick event for this. We define it on the HTML button element itself. For example:

function func() {}

<button onClick={func} />
Remember that unlike DOM event handlers, which use strings, React event handlers use actual JavaScript function references.

This function can be a global one (like func above), or an inline function:

<button onClick={() => {}} />

However, the standard practice is to define a handler function as an instance property in the component class. Let’s name it handleClick:

class Button extends React.Component {
  state = { counter: 1 };

  handleClick = () =>
    console.log('Button is clicked!!');

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.counter}
      </button>
    );
  }
}

I used the class-field syntax for handleClick as well. Because of the arrow function that I was able to use there, the handleClick function will be bound to any instance created through this component. When handleClick is invoked on any instance of this component, the keyword “this” will refer to that instance that represents the DOM element on which the user is clicking.

The job of this handleClick function is a straight-forward one: read the current counter value from the state object using this.state.counter. Then increment this value and update the component state with the new incremented value.

We need to use React’s setState method to update a component state. Here’s how that method can be used for what we want to do:

handleClick = () =>
  this.setState({
    counter: this.state.counter + 1
  });

The button element should now increment its label on every click.

Playground link for the code so far: jsdrops.com/rg-3.3

This was simple and powerful. We defined an event handler for the onClick method. Every time the user clicks the button, the handleClick function will be executed. The function reads the current state of the counter value, increments it, and then sets the state to the new incremented value. React takes care of all the rendering needed after these changes so you do not have to worry about that.

Note that we did not update the state object directly. We have to use React’s setState method when we want to update any element on the state. For example, you cannot do this:

// WRONG:
this.state.counter = this.state.counter + 1;

Internally, React might potentially batch multiple setState calls for performance. Because of that, when we both read and write to the state object inside the handleClick function, we could theoretically hit a race condition. The general rule of thumb is whenever you need to update the state using a value from the current state, use the other contract of the setState method which receives an updater function reference instead of an object as its first argument:

this.setState(prevState => {});

The updater function receives a prevState object that we can confidently use without worrying about race conditions. The updater function returns the object that we want React to use to set the state. Our counter value example above becomes:

this.setState(prevState => ({
  counter: prevState.counter + 1
}));

Theoretically, you only need to use the updater function syntax of setState if the update depends on the current state. However, it may be a good idea to make a habit of always using the updater function syntax.

To see the asynchronous nature of setState in action, try to repeat the object-based setState call inside handleClick twice and verify how that would not increment the counter by 2 on each click. If you repeated the updater-function-based setState call twice in handleClick, the counter would increment by 2 on each single click.

React might actually call setState synchronously, but that is a fact that you can ignore. Do not depend on such edge cases.

Here is the code we have for this example so far:

class Button extends React.Component {
  state = { counter: 1 };

  handleClick = () =>
    this.setState(prevState => ({
      counter: prevState.counter + 1
    }));

  render() {
    return (
      <button onClick={this.handleClick}>
        {this.state.counter}
      </button>
    );
  }
}

ReactDOM.render(<Button />, mountNode);

Lifting the State

So far in this example, we have only seen one component. Let’s add more. Let’s split the Button component that we have so far into two components:

  • Keep a Button component to represent a button element, but with a static label.

  • Add a new CounterDisplay component to display the counter’s value. This new CounterDisplay component is a pure presentational one with no state or interactions of its own. We can safely use the function component syntax to write it.

const CounterDisplay = (props) => (
  <div>COUNTER VALUE HERE...</div>
);

Note how I used the arrow function short syntax above to avoid typing curly brackets and a return keyword. I write most of my functions this way.

This CounterDisplay div will not show up in the DOM because we have not included it in the rendered elements yet. We just defined the component above.

Since we have two elements to render now (Button and CounterDisplay), we will need to group them together under one element description and tell ReactDOM to render both of them. One way to do that is to create a parent element and make this new parent element render the two elements that we have.

We need to create a new component to render this new parent element. The question now is: what should we name this new parent component?

Believe it or not, naming your components and their state/props elements is a very hard task that will affect the way these components work and perform. The right names will force you into the right design decisions. Take some time and think about every new name you introduce to your React apps.

Since this new parent component will host a CounterDisplay with a Button that increments the displayed counter, we can think of it as the counter value manager. Let’s name it CounterManager.

We previously placed the counter value on the state of Button. We now need to use it inside CounterDisplay and continue to have Button increment that value. Both Button and CounterDisplay, who will be siblings in the new DOM tree, will need to have access to the same counter value.

Sibling components can’t access each other’s state or data. However, a parent component can pass down any state or data to its children components. If we moved the state of the counter value to the new CounterManager parent component, that component can pass it down to its children, which will be CounterDisplay and Button. This means we need have a state element in the CounterManager component.

In addition to hosting the state, CounterManager will host an action function to change that state, and it will render its two children passing down the counter value to CounterDisplay and a reference of the action function to Button. Let’s name the action function incrementCounter. Here’s a version of the CounterManager component to implement all of that.

class CounterManager extends React.Component {
  state = { counter: 1 };

  incrementCounter = () =>
    this.setState(prevState => ({
      counter: prevState.counter + 1
    }));

  render() {
    return (
      <div>
        <Button onClick={this.incrementCounter} />
        <CounterDisplay counter={this.state.counter} />
      </div>
    );
  }
}

Note a few of things about the code above:

  • Since the counter state was moved to CounterManager, the code for the new incrementCounter action function is the exact same code we had before in the Button component click handler.

  • To render two React elements, we had to wrap them in a div element. You can also return an array of elements from the render method and you can also wrap the elements in the special React.Fragment component, which would not introduce a new DOM node. I like this latter approach, so I’ll use it in the final code below.

  • We passed a reference to incrementCounter down to Button and named it onClick on that level. The Button component does not need to be aware of the nature of the click. It will just execute the instructions it receives from its boss, the CounterManager component.

Now, Button can just execute its onClick prop value and it no longer needs any state of its own.

class Button extends React.Component {

  render() {
    return (
      <button onClick={this.props.onClick}>+1</button>
    );
  }

}

Note how I changed the HTML button element’s label to +1 since we no longer need it to display the counter’s value. Also note to access a property value in a class component, the syntax you need is this.props.propName.

The CounterDisplay component can simply display the counter value it now receives as a prop:

const CounterDisplay = ({ counter }) => (
  <div style={{ fontSize: 20, padding: 20}}>
    {counter}
  </div>
);

Note how I destructured the counter property from the props object argument value. This is a common practice in function components.

Also note how I added a style property to the div element in the component output. This style property is a special one in React. It expects an object of styles written according to the DOM syntax. The style object I used above will be converted to the following inline HTML string: “font-size: 20px; padding: 20px;”.

From the point of view of CounterDisplay, the counter value is not part of a state object. It is just a value that CounterManager is passing to it. CounterDisplay will always just display that in the UI.

Here’s is the full code of this example so far:

class Button extends React.Component {
  render() {
    return (
      <button onClick={this.props.onClick}>+1</button>
    );
  }
}
const CounterDisplay = ({ counter }) => (
  <div style={{ fontSize: 20, padding: 20}}>
     {counter}
   </div>
);
class CounterManager extends React.Component {
  state = { counter: 1 };

  incrementCounter = () =>
    this.setState(prevState => ({
      counter: prevState.counter + 1
    }));

  render() {
    return (
      <>
        <Button onClick={this.incrementCounter} />
        <CounterDisplay counter={this.state.counter} />
      </>
    );
  }
}

ReactDOM.render(<CounterManager />, mountNode);

Note how I used the empty bracket <></> syntax above to replace the div in CounterManager. This syntax compiles into <React.Fragment></React.Fragment>, which allows us to render multiple siblings in a component without having to wrap them in an element like div

Making a Component Reusable

Challenge

Components are all about reusability. Let’s make the Button component reusable by changing it so that it can increment with any value, not just 1.

We will change the CounterManager component to have a button that increments with 1, another button that increments with 5, another with 10, and so on. This part is easy. You can change the render function in CounterManager to something like:

render() {
  return (
    <>
      <Button onClick={this.incrementCounter} /> {/* +1  */}
      <Button onClick={this.incrementCounter} /> {/* +5  */}
      Button onClick={this.incrementCounter} /> {/* +10 */}
      <CounterDisplay counter={this.state.counter} />
    </>
  );
}

All the Button elements rendered above will have a +1 label and will increment the counter with 1. We want to make them display a different label that is specific to each Button and make them perform a different action based on a value that is specific to each Button. Remember that you can pass any specific value to a React element as a prop.

Here’s the UI I have in mind after clicking each button once:

picture6

The counter above started with 1, we then added 1, then 5, then 10 to get to 17.

Before we go through this exercise, take some time and think about it and try to implement it yourself. It is mostly straightforward; you just need to customize a few things and make them generic.

Give it a shot. Come back when you are ready to compare your solution with mine.

Solution

The first thing we need is to make the +1 label in the button component a customizable one.

To make something customizable in a React component, we introduce a new prop for that component and make the component use its value (which the parent component can now control). For our example, we can make the Button component receive the amount to increment (1, 5, 10) as a new prop named clickValue. We can change the render method in CounterManager to pass the values we want to test with to this new prop.

render() {
  return (
    <>
      <Button onClick={this.incrementCounter}
              clickValue={1} />

      <Button onClick={this.incrementCounter}
              clickValue={5} />

      <Button onClick={this.incrementCounter}
              clickValue={10} />

      <CounterDisplay counter={this.state.counter} />
    </>
  );
}

Note a couple of things about this code so far:

  • I did not name the new property with anything related to counter. The Button component does not need to be aware of the meaning of its click event. It just needs to pass this clickValue along when a click event is triggered. For example, naming this new property counterValue would not be the best choice because now, in the Button component, we read the code to understand that a Button element is related to a counter. This makes the Button component less reusable. For example, if I want to use the same Button component to append a letter to a string, its code would be confusing.

  • I used curly brackets to pass the values of the new clickValue property (clickValue={5}). I did not use strings there (clickValue="5"). Since I have a mathematical operation to do with these values (every time a Button is clicked), I need these values to be numbers. If I pass them as strings I would have to do some string-to-number conversion when the add operation is to be executed.

The other thing we need to make generic in the CounterManager component is the incrementCounter action function. It cannot have a hardcoded +1 operation as it does now. Similar to what we did for the Button component, to make a function generic, we make it receive an argument and use that argument’s value. For example:

incrementCounter = (incrementValue) =>
  this.setState(prevState => ({
    counter: prevState.counter + incrementValue
  }));

Now all we need to do is make the Button component use its clickValue as its label and make it invoke its onClick action with its clickValue as an argument.

The first part is easy. We just use this.props.clickValue as the label of the HTML button element.

<button onClick={this.props.onClick}>
  +{this.props.clickValue}
</button>

However, we cannot keep the onClick handler as it is. We have to invoke the handler with this.props.clickValue as an argument now. A React event handler like onClick expects a function reference. We cannot pass it a function call.

The simplest way to do what is needed here is to wrap the function call that we need with an inline arrow function. For example:

<button onClick={
  () => this.props.onClick(this.props.clickValue)
}>
  +{this.props.onClick}
</button>

This will do the task just fine. The three buttons should now increment with their three different click values.

An alternative to using an inline function is to use the bind function and create new functions that remember their arguments. For example:

<button onClick={
  this.props.onClick.bind (this, this.props.clickValue)
}>
  +{this.props.onClick}
</button>

The problem with both of the approaches above is that every time React renders and re-renders a Button component, we will be creating new functions. This might be okay for a small example like what we are doing here, but if there are many calls to render and re-render that Button component, creating a new function on each render call starts to become an issue. Fortunately, we can use class-field methods to avoid this. We just move the logic into a class-field method and use a reference to it in the render method. For example:

class Button extends React.Component {
  handleClick = () =>
    this.props.onClick(this.props.clickValue);

  render() {
    return (
      <button onClick={this.handleClick}>
        +{this.props.clickValue}
      </button>
    );
  }
}

Although this is a bit more code, it is a much better approach because now when React re-renders any element from this component, it will not create a new function per render. This is a bigger deal if you are passing function references to children elements (which you will do often). I also find this version easier to understand than the inline one.