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.
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.
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.
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 newCounterDisplay
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?
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 toCounterManager
, the code for the newincrementCounter
action function is the exact same code we had before in theButton
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 therender
method and you can also wrap the elements in the specialReact.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 toButton
and named itonClick
on that level. TheButton
component does not need to be aware of the nature of the click. It will just execute the instructions it receives from its boss, theCounterManager
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:

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
. TheButton
component does not need to be aware of the meaning of its click event. It just needs to pass thisclickValue
along when a click event is triggered. For example, naming this new propertycounterValue
would not be the best choice because now, in theButton
component, we read the code to understand that aButton
element is related to a counter. This makes theButton
component less reusable. For example, if I want to use the sameButton
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 aButton
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.