CHAPTER 4
The first and most important concept you need to learn about React is the concept of the component.
In React, we describe user interfaces using components that are reusable, composable, and stateful as well.
You define small components and then put them together to form bigger ones. All components small or big are reusable, even across different projects.
You can think of components as simple functions (in any programming language). We call functions with some input, and they give us some output. We can reuse functions as needed and compose bigger functions from smaller ones.
React components are exactly the same; their input is a set of "props," and their output is a description of a user interface. We can reuse a single component in multiple UIs, and components can contain other components. The basic form of a React component is actually a plain-old JavaScript function.
A React component can also have a private state to hold data that may change over the lifecycle of the component. This private state is an implicit part of the input that drives the component’s output, and that’s actually what gives React its name!
When the state of a React component (which is part of its input) changes, the user interface it represents (its output) changes as well. This change in the description of the UI has to be reflected in the device we are working with. In a browser, we need to update the HTML DOM tree. In a React application, we don’t do that manually. React will simply react to the state changes and automatically (and efficiently) update the DOM when needed.
In its simplest form, a React component is a JavaScript function:
Code Listing 51: Code available at jscomplete.com/playground/rs2.1
function Button (props) { // Returns a DOM/React element here. For example: return <button type="submit">{props.label}</button>; } // To render a Button element in the browser ReactDOM.render(<Button label="Save" />, mountNode); |
Note how I wrote what looks like HTML in the returned output of the Button function in Code Listing 51. This is neither JavaScript nor HTML, and it is not even React. However, it is so popular that it became the default in React applications. It is called JSX, and it is a JavaScript extension that allows you to write JavaScript function calls in an XML-like syntax (the X in JSX refers to XML).
We’ve seen the ReactDOM.render method in Chapter 2; the only difference is that now we’re feeding it JSX instead of React.createElement calls.
Go ahead and try to return any other HTML element inside the Button function in Code Listing 51, and see how they are all supported (for example, return an input element or a textarea element).
JSX is not understood by browsers. If you try to execute the Button function in a regular browser console, it’ll complain about the first character in the JSX part:

Figure 4: Browsers do not understand JSX
What browsers understand (given the React library is included) is the React.createElementAPI calls that we used in Chapter 2. The same Button example can be written without JSX as follows:
Code Listing 52: Code available at jscomplete.com/playground/rs2.2
function Button (props) { return React.createElement( "button", { type: "submit" }, props.label ); } ReactDOM.render( React.createElement(Button, { label: "Save"}), mountNode ); |
You can use React like this—you can execute the Button function in a browser directly (after loading the React library), and things will work just fine. However, we like to see and work with HTML instead of dealing with function calls. When was the last time you built a website with just JavaScript and not used HTML? You can if you want to, but no one does that. That’s why JSX exists.
JSX is basically a compromise. Instead of writing React components using the React.createElement syntax, we use a syntax very similar to HTML, and then use a compiler to translate it into React.createElement calls.
A compiler that translates one form of syntax into another is known as a transpiler. To translate JSX, we can use transpilers like Babel or TypeScript. For example, the jsComplete playground uses TypeScript to transpile any JSX you put into it. When you use create-react-app, the generated app will internally use Babel to transpile your JSX.
Tip: You can use babeljs.io/repl/ to see what any JSX syntax gets converted to for React, but JSX can also be used on its own. It is not a React-only thing.
So, a React component is a JavaScript function that returns a React element (usually with JSX). When JSX is used, the <tag></tag> syntax becomes a call to React.createElement("tag"). It’s critically important for you to keep this in mind while building React components. You are not writing HTML— you are using a JavaScript extension to return function calls that create React elements (which are essentially JavaScript objects).
Note how I named the component Button. The first letter being a capital one is actually a requirement since we will be dealing with a mix of HTML elements and React elements. JSX will consider all names that start with a lowercase letter as names of HTML elements. This is important because HTML elements are passed as strings to React.createElement calls, while React elements need to be passed as variables:

Figure 5: HTML tags vs. component names in JSX
Go ahead and try naming the React component button instead of Button, and see how ReactDOM will totally ignore the function and render a regular, empty HTML button.
Code Listing 53: Code available at jscomplete.com/playground/rs2.3
// Wrong: const button = () => ( <div>My Fancy Button</div> ); // The following will render an HTML button // (and ignore the fancy button function) ReactDOM.render(<button />, mountNode); |
Just like HTML elements can be assigned attributes like id or title, a React element can also receive a list of attributes when it gets rendered. The Button element in Code Listing 51 received a label attribute. In React, the list of attributes received by a React element is known as props. A React function component receives this list as its first argument. The list is passed as an object with keys representing the attribute names, and values representing the values assigned to them.
When using a function component, you don’t have to name the object holding the list of attributes as props, but that is the standard practice. When using class components, which we will do in the following example, the same list of attributes is always presented with a special instance property named props.
Note: Receiving props is optional. Some components will not have any props. However, a component’s return value is not optional. A React component cannot return "undefined" (either explicitly or implicitly)—it has to return a value. It can return "null" to tell the renderer to ignore its output.
I like to use object destructuring whenever I use component props (or state, really). For example, the Button component function can be written like this with props destructuring:
Code Listing 54: Destructuring component props
const Button = ({ label }) => ( <button type="submit">{label}</button> ); |
This approach has many benefits, but the most important one is to visually inspect what props are used in a component and make sure a component does not receive any extra props that are not needed.
Note: Note how I used an arrow function in Code Listing 54 instead of a regular one. This is just a style preference for me personally. Some people prefer the regular function style, and there is nothing wrong with that. What’s important is to be consistent with the style that you pick. I’ll use arrow functions in this book’s examples, but don’t interpret that as a requirement.
In Chapter 3, we saw how you can include a JavaScript expression anywhere within the JSX syntax using a pair of curly brackets.
Code Listing 55: Code available at jscomplete.com/playground/rs2.4
const RandomValue = () => ( <div> { Math.floor(Math.random() * 100) } </div> ); ReactDOM.render(<RandomValue />, mountNode); |
Only expressions can be included inside these curly brackets. For example, you cannot include a regular if statement, but a ternary expression is okay. Anything that returns a value is okay. You can always put any code in a function, make it return something, and call that function within the curly brackets. However, keep the logic you put in these curly brackets to a minimum. There are better places for long or complex logic, which we will see in the next chapter.
JavaScript variables are also expressions, so when the component receives a list of props you can use these props inside curly brackets. That’s how we used {props.label} (and {label}) in the Button example.
JavaScript object literals are also expressions. Sometimes we use a JavaScript object inside curly brackets, which makes it look like double curly brackets: {{a:42}}. This is not a different syntax; it is just an object literal defined inside the regular JSX curly brackets.
For example, one use case for using an object literal in these curly brackets is to pass a CSS-style object to the special style attribute in React:
Code Listing 56: Code available at jscomplete.com/playground/rs2.5
const ErrorDisplay = ({ message }) => ( <div style={ { color:'red', backgroundColor:'yellow' } }> {message} </div> ); ReactDOM.render( <ErrorDisplay message="These aren't the droids you're looking for" />, mountNode ); |
The style attribute is a special one in React. We use an object as its value, and that object defines the styles as if we are setting them through the JavaScript DOM API (camel-case property names and string values). React translates these style objects into inline CSS style attributes. This is generally not the best way to style a React component, but I find it extremely convenient to use when applying conditional styles to elements. For example, here is a component that will randomly output its text in either green or red about half the time:
Code Listing 57: Code available at jscomplete.com/playground/rs2.6
class ConditionalStyle extends React.Component { render() { return ( <div style={{ color: Math.random() < 0.5 ? 'green': 'red' }}> How do you like this? </div> ); } } ReactDOM.render( <ConditionalStyle />, mountNode, ); |
The logic for this styling is right there in the component. I like that! This is easier to work with than conditionally using a class name and then tracking what that class name is doing in a CSS stylesheet.
Some JavaScript libraries that deal with HTML provide a template language for it. You write your dynamic views with an "enhanced" HTML syntax that has loops and conditionals. These libraries will then use JavaScript to convert the templates into DOM operations. The DOM operations can then be used in the browser to generate the DOM tree described by the enhanced HTML.
React (and ReactDOM) eliminated that step. We do not send the browser a template at all in a React application. We send it a tree of objects described with the React API. React uses these objects to generate the DOM operations needed to display the desired HTML tree.
Note: With an HTML template, the library parses your application as a string. A React application is parsed as a tree of objects.
While JSX might look like a template language, it really isn’t. It’s just a JavaScript extension that allows us to represent React’s tree of objects with a syntax that looks like an HTML template. Browsers don’t have to deal with JSX at all, and React does not have to deal with it either—only the compiler deals with it. What we send to the browser is template-free and JSX-free code.
Take, for example, the todos array in Code Listing 1. If we’re to display that array in a UI using a template language, we’ll need to do something like:
Code Listing 58
<ul> <% FOR each todo in the list of todos %> <li><%= todo.body %></li> <% END FOR %> </ul> |
Note: The <% %> is one syntax to represent the dynamic enhanced parts. You might also see the {{ }} syntax. Some template languages use special attributes for their enhanced logic, and some template languages make use of whitespace indentation (off-side rule).
When changes happen to the todos array (and we need to update what’s rendered in the DOM with a template language), we’ll have to either re-render that template or compute where in the DOM tree we need to reflect the changes to the todos array.
In a React application, there is no template language at all. In Code Listing 2, we described our desired HTML tree for the todos array using JSX:
Code Listing 59
<ul> {todos.map(todo => <li>{todo.body}</li> )} </ul> |
Which, before being used in the browser, gets translated to:
Code Listing 60
React.createElement( "ul", null, todos.map(todo => React.createElement("li", null, todo.body) ), ); |
React takes this tree of objects and makes it into a tree of DOM elements. From our point of view, we’re done with this tree. We don’t manage any transactions on it. We just manage transactions in the todos array itself.
React supports creating components through the JavaScript class syntax as well. Here is the same Button component example written with the class syntax:
Code Listing 61: Code available at jscomplete.com/playground/rs2.7
class Button extends React.Component { render() { return ( <button>{this.props.label}</button> ); } } // Use it (same syntax) ReactDOM.render(<Button label="Save" />, mountNode); |
In this syntax, you define a class that extends React.Component, which is one of the main classes in the React top-level API. A class-based React component has to at least define an instance method named render. This render method returns the element that represents the output of an object instantiated from the component. Every time we use the Button class-based component (by rendering a <Button … />), React will instantiate an object from this class-based component and use that object’s representation to create a DOM element. It’ll also associate the DOM-rendered element with the instance it created from the class.
Note how we used this.props.label inside the rendered JSX. Every component gets a special instance property named props that holds all values passed to that component’s element when it was instantiated. Unlike function components, the render function in class-based components does not receive any arguments.
Components created with functions used to be limited in React. The only way to make a component "stateful" was to use the class syntax. This has changed with the release of React Hooks, beginning with React version 16.8, which was released in early 2019. The React Hooks release introduced a new API to make a function component stateful (and give it many other features).
With this new API, most of what is usually done with React can be done with functions. The class-based syntax is only needed for advanced and rare cases.
In this book, I’ll use the new Hooks-based API instead of the old class-based one. I believe the new API will slowly replace the old one, but that’s not the only reason I want to encourage you to use it (exclusively if you can).
I’ve used both APIs in large applications, and I can tell you that the new API is far superior to the old one for many reasons, but here are the ones that I personally think are the most important:
While class-based components will continue to be part of React for the foreseeable future, as a newcomer to the ecosystem, it makes sense for you to start purely with just functions (and Hooks) and focus on learning the new API (unless you have to work with a codebase that already uses classes).
The term "component" is used by many frameworks and libraries. We can even write web components natively using HTML5 features like custom elements and HTML imports. Components, whether we are working with them natively or through a library like React, have many advantages.
First, components make your code more readable and easier to work with. Consider this UI:
Code Listing 62: HTML-Based UI
<a href="http://facebook.com"> <img src="facebook.png" /> </a> |
What does this UI represent? If you speak HTML, you can parse it quickly here and say, “it’s a clickable image.” If we’re to convert this UI into a component, we can just name it ClickableImage.
<ClickableImage /> |
When things get more complex, this parsing of HTML becomes harder so components allow us to quickly understand what a UI represents using the language that we’re comfortable with. Here’s a bigger example:
<TweetBox> <TextAreaWithLimit limit="280" /> <RemainingCharacters /> <TweetButton /> </TweetBox> |
Without looking at the actual HTML code, we know exactly what this UI represents. Furthermore, if we need to modify the output of the RemainingCharacters section, we know exactly where to go.
React components can also be reused in the same application, and across multiple applications. For example, here’s a possible implementation of the ClickableImage component:
Code Listing 63: ClickableImage Render Function
const ClickableImage = ({ href, src }) => { return ( <a href={href}> <img src={src} /> </a> ); }; |
Having variables for both the href and the src props is what makes this component reusable. For example, to use this component we can render it with a set of props:
<ClickableImage href="http://google.com" src="google.png" /> |
And we can reuse it by using a different set of props:
<ClickableImage href="http://bing.com" src="bing.png" /> |
Tip: In functional programming, we have the concept of pure functions (which do not have any observable side effects). They are basically protected against any outside state; if we give them the same input, we’ll always get the same output. If a React component does not depend on (or modify) anything outside of its definition (for example, if it does not use a global variable), we can label that component pure as well. Pure components have a better chance at being reused without any problems.
We create components to represent views. For ReactDOM, the React components we define will represent HTML DOM nodes. The ClickableImage component in Code Listing 63 was composed of two HTML elements.
We can think of HTML elements as built-in components in the browser. We can also use our own custom components to compose bigger ones. For example, let’s write a component that displays a list of search engines.
Code Listing 64: SearchEngines Mockup
const SearchEngines = () => { return ( <div className="search-engines"> <ClickableImage href="http://google.com" src="google.png" /> <ClickableImage href="http://bing.com" src="bing.png" /> </div> ); }; |
Note how I used the ClickableImage component to compose the SearchEngines component!
We can also make the SearchEngines component reusable as well, by extracting its data into a variable and designing it to work with that variable.
For example, we can introduce a data array in a format like:
Code Listing 65: SearchEngines Data
const data = [ { href: "http://google.com", src: "google.png" }, { href: "http://bing.com", src: "bing.png" }, { href: "http://yahoo.com", src: "yahoo.png" } ]; |
Then, to make <SearchEngines data={data} /> work, we just map the data array from a list of objects into a list of ClickableImage components:
Code Listing 66: SearchEngines Render Function
const SearchEngines = ({ engines }) => { return ( <List> {engines.map(engine => <ClickableImage {...engine} />)} </List> ); }; ReactDOM.render( <SearchEngines engines={data} />, document.getElementById("mountNode") ); |
This SearchEngines component can now work with any list of search engines we give to it.
A Hook in a React component is a call to a special function. All Hook functions begin with the word use. The most widely-used Hook function in React is the useState one. You can use it to give a component stateful elements.
To see an example of that, let’s make the Button component (from Code Listing 51) respond to a click event. Let’s maintain the number of times it gets clicked in a count variable and display the value of that variable as the label of the button it renders.
const Button = () => { let count = 0; return ( <button>{count}</button> ); }; ReactDOM.render(<Button />, mountNode); |
This count variable will be the state element that we need to introduce to the example. It’s a piece of data that the UI will depend on (because we’re displaying it), and it is a state element because it is going to change over time.
Tip: Every time you define a variable in your code, you will be introducing a state, and every time you change the value of that variable, you are mutating that state. Keep that in mind.
Before we can change the value of the count state, we need to learn about events.
You can add an event handler with an onEvent property (to the button element in this case). This could be an onClick, onMouseOver, onScroll, or onSubmit, among others.
What we need here is an onClick event, and we just define it as an attribute on the target element. For example, to make the program log a message to the console every time the button is clicked, we can do something like:
Code Listing 67: Using onClick in React
const Button = () => { let count = 0; return ( <button onClick={() => console.log('Button clicked')}> {count} </button> ); }; ReactDOM.render(<Button />, mountNode); |
Unlike the DOM version of the onClick attribute (which uses a string), React’s onClick attribute uses a function reference. You specify that inside curly brackets.
Code Listing 68
function func() {} <button onClick={func} /> |
Note how we passed the func reference (name) as the onClick handler. We did not invoke func in there. React will invoke func when the button gets clicked.
In Code Listing 67, we inlined a function definition that when invoked will output a message to the console. Each time we click on the button, the onClick handler (the inline arrow function) will be invoked and we’ll see that message.
Note: Note how the event name is in camel case. All DOM-related attributes (which are handled by React) need to be in camel case, and React will display an error if they are not. React also supports using custom HTML attributes, and those have to be in all-lowercase format.
Some DOM attributes in React are slightly different from what they do in the regular DOM API. An example of that is the onChange event. In a regular browser, it’s usually fired when you click outside a form field (or tab out of it). In React, onChange fires whenever the value of a form field is changed (on every character added or removed).
Some attributes in React are named differently from their HTML equivalent. An example of that is the className attribute in React, which is equivalent to using the class attribute in HTML. You can see a complete list of the differences between React attributes and DOM attributes in the React docs.
To track state updates and trigger virtual DOM diffing and real DOM reconciliation, React needs to be aware of any changes that happen to any state elements that are used within components. To do this in an efficient way, React requires the use of special getters and setters for each state element you introduce in a component. This is where the useState Hook comes into play. It defines a state element and gives us a getter and setter for it.
Here’s what we need for the count state element we’re trying to implement:
const [count, setCount] = React.useState(0); |
The useState function returns an array with two items. The first item is a value (getter), and the second item is a function (setter). I used array destructuring to give these items names. You can give them any names you want, but [name, setName] is the convention.
The first item "value" can be a string, number, array, or another type. In this case, we needed a number, and we needed to initialize that number with 0. The argument to React.useState is used as the initial value of the state element.
The second item "function" will change the value of the state element (and trigger DOM processing if needed). Each time the setCount function is invoked, React will re-render the Button component, which will refresh all variables defined in the component (including the count value). The argument we pass to setCount becomes the new value for count.
To make the button increment its label, we need to invoke the setCount function within the onClick event and pass to it the current count value incremented by 1. Here’s the full code of the label-incrementing button example:
Code Listing 69
const Button = () => { const [count, setCount] = useState(0); return ( <button onClick={() => setCount(count + 1)}> {count} </button> ); }; ReactDOM.render(<Button />, mountNode); |
Go ahead and test that. The button will now increment its label on each click.
Note how we did not implement any actions to change the UI itself. We implemented an action to change a JavaScript object (in memory). Our UI implementation was basically telling React that we want the label of the button to always reflect the value of the count object. Our code didn’t do any DOM updates—React did.
Note also how I used the const keyword to define count, although it’s a value that gets changed. Our code will not change that value. React will when it uses a fresh call of the Button function to render the UI of its new state. In that fresh call, the useState function will give us a new fresh count value.
Tip: The useState function is available globally in the playground. This is just an alias to React.useState. In your code, you can use named imports to have useState available directly in the scope of a module. In this book’s examples, I’ll use the useState (and other React use* functions) directly for brevity. All examples will work in the playground, but remember to name-import these Hook functions when you start using them in a different environment.
import React, { useState } from 'react'; |
You’ll need a few more examples to appreciate this power. So, let’s add some more features to this basic example. Let’s make the UI show many buttons, and make them increment a single count value.
Let’s split the Button component that we have so far into two components:
The new Display component will be a purely presentational one, with no state or interactions of its own. That’s normal. Not every React component has to have stateful Hooks or be interactive.
Code Listing 70
const Display = (props) => ( <pre>COUNT VALUE HERE...</pre> ); |
The responsibility of the Display component is to simply display a value that it will receive as a prop. For example, the fact that a pre element was used to host the value is part of that responsibility. Other components in this application have no say about that!
We now have two elements to render: Button and Display. We can’t render them directly next to each other like this:
// This will not work ReactDOM.render(<Button /><Display />, mountNode); |
Adjacent elements can’t be rendered like this in React because each of them gets translated into a function call when JSX is converted. You have a few options to fix this problem.
First, you can pass an array of elements to ReactDOM.render and insert into that array as many React elements as you wish.
Code Listing 71: Option #1
ReactDOM.render([<Button />, <Display />], mountNode); |
This is usually a good solution when all the elements you’re rendering are coming from a dynamic source. It’s not ideal for the case we’re doing here.
Another option is to make the sibling React elements the children of another React element. For example, we can just enclose them in a div element.
Code Listing 72: Option #2
ReactDOM.render( <div> <Button /> <Display /> </div>, mountNode ); |
React API supports this nesting. In fact, React has a special object if you need to enclose multiple adjacent elements like this without introducing a new DOM parent node. You can use React.Fragment:
Code Listing 73: Option #3
ReactDOM.render( <React.Fragment> <Button /> <Display /> </React.Fragment>, mountNode ); |
This case is so common in React that the JSX extension has a shortcut for it. Instead of typing React.Fragment, you can just use an empty tag, <>.
Code Listing 74: Option #3+
ReactDOM.render( <> <Button /> <Display /> </>, mountNode ); |
The empty tag will get transpiled into the React.Fragment syntax. I’ll use this syntax to continue with the example.
However, you should always try to make the first argument to ReactDOM.render a single component call instead of the nested tree that we just did. This is essentially a code quality preference. It forces you into thinking about your components’ hierarchy, names, and relations. Let’s do that next.
Let’s introduce a top-level component to host both the Button and Display components. The question now is: what should we name this new parent component?
Note: 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 in your React apps.
Since this new parent component will host a Display with a Button that increments the displayed count, we can think of it as the count value manager. Let’s name it CountManager.
const CountManager = () => { return ( <> <Button /> <Display /> </> ); }; ReactDOM.render(<CountManager />, mountNode); |
Since we’re going to display the count’s value in the new Display component, we no longer need to show the count’s value as the label of the button. Instead, we can change the label to something like +1.
Code Listing 75
const Button = () => { return ( <button onClick={() => console.log('TODO: Increment counter')}> +1 </button> ); }; |
Note how I’ve also removed the state element from the Button component because we can’t have it there anymore. With the new requirement, both the Button and Display components need access to the count state element. The Display component will display it, and the Button component will update it. When a component needs to access a state element that’s owned by its sibling component, one solution is to "lift" that state element one level up and define it inside its parent component, which, in this case, is the CountManager component that we just introduced.
By moving the state to CountManager, we can now "flow" data from parent to child using component props. That’s what we should do to display the count value in the Display component:
Code Listing 76
const Display = ({ content }) => ( <pre>{content}</pre> ); const CountManager = () => { const [count, setCount] = useState(0); return ( <> <Button /> <Display content={count} /> </> ); }; ReactDOM.render(<CountManager />, mountNode); |
Note how in CountManager I used the exact same useState line that was in the Button component. We are lifting the same state element. Note also how when I flowed the count value down to the Display component via a prop, I used a different name for it (content). That’s normal. You don’t have to use the exact same name. In fact, in some cases, introducing new generic names are better for children components because they make them more reusable. The Display component could be reused to display other numeric values besides count.
Parent components can also flow down behavior to their children, which is what we need to do next.
Since the count state element is now in the CountManager component, we need a function on that level to handle updating it. Let’s name this function incrementCounter. The logic for this function is actually the exact same logic we had before in the handleClick function in the Button component. The new incrementCounter function is going to update the CountManager component count state to increment the value using the previous value:
Code Listing 77
const CountManager = () => { // .... const incrementCounter = () => setCount(count + 1); // ... } |
The onClick handler in the Button component has to change now. We want it to execute the incrementCounter function that’s in the CountManager component, but a component can only access its own functions. So, to make the Button component able to invoke its parent’s incrementCounter function, we can pass a reference to incrementCounter to the Button component as a prop. Yes, props can hold functions as well, not just data. Functions are just objects in JavaScript, and just like objects, you can pass them around.
We can name this new prop anything. I’ll name it clickAction and pass it a value of incrementCounter, which is the reference to the function we defined in the CountManager component. We can use this new passed-down behavior directly as the onClick handler value. It will be a prop for the Button component:
Code Listing 78
const Button = ({ clickAction }) => { return ( <button onClick={clickAction}> +1 </button> ); }; // ... const CountManager = () => { // ... return ( <div> <Button clickAction={incrementCounter} /> <Display content={count} /> </div> ); } |
Something very powerful is happening here. This clickAction property allowed the Button component to invoke the CountManager component’s incrementCounter function. It’s like when we click that button, the Button component reaches out to the CountManager component and says, “Hey parent, go ahead and invoke that increment counter behavior now.”
In reality, the CountManager component is the one in control here, and the Button component is just following generic rules. If you analyze the code as it is now, you’ll realize how the Button component has no clue about what happens when it gets clicked. It just follows the rules defined by the parent and invokes a generic clickAction. The parent controls what goes into that generic behavior. That’s an example of the concept of responsibility isolation. Each component here has certain responsibilities, and they get to focus on that.
Look at the Display component for another example. From its point of view, the count value is not a state; it is just a prop that the CountManager component is passing to it. The Display component will always display that prop. This is also a separation of responsibilities.
As the designer of these components, you get to choose their level of responsibilities. For example, if we want to, we can make the responsibility of displaying the count value part of the CountManager component itself and not use a new Display component for that, but I like it this way. The CountManager component has the responsibility of managing the count state. That’s an important design decision that we made, and it’s one you’re going to have to make a lot in a React application: where to define the state?
The practice I follow is to define a state element in a shared parent node that’s as close as possible to all the children who need to access that state element. For a small application like this one, that usually means the top-level component itself. In bigger applications, a sub-tree might have its own state "branch." In the next chapter, we’ll see an example of the value of having some state managed in a sub-tree rather than defining all state elements on the top level.
Tip: The top-level component is usually the one used to manage shared application state and actions because it’s a parent to all other components. However, be careful about this design because updating a state element on the top-level component means that the whole tree of components will be re-rendered (in memory), which can affect performance.
Here’s the full code for this example so far:
Code Listing 79: Code available at jscomplete.com/playground/rs2.8
const Button = ({ clickAction }) => { return ( <button onClick={clickAction}> +1 </button> ); }; const Display = ({ content }) => ( <pre>{content}</pre> ); const CountManager = () => { const [count, setCount] = useState(0); const incrementCounter = () => setCount(count + 1); return ( <div> <Button clickAction={incrementCounter} /> <Display content={count} /> </div> ); } |
Components are all about reusability. Let’s make the Button component reusable by changing it so that it can increment the global count with any value, not just 1.
Let’s start by adding more Button elements in the CountManager component so that we can test this new feature:
Code Listing 80: Adding more Button elements
const CountManager = () => { // .. return ( <> <Button clickAction={this.incrementCounter} /> {/* +1 */} <Button clickAction={this.incrementCounter} /> {/* +5 */} <Button clickAction={this.incrementCounter} /> {/* +10 */} <Display count={this.state.count} /> </> ); }; |
All the Button elements rendered in Code Listing 80 will have a +1 label, and they will increment the count with 1. We want to make them display different labels that are specific to each button and make them perform a different action based on a value that is specific to each one. Remember that you can pass any value to a React element as a prop.
Here’s the UI I have in mind after clicking each button once:

Figure 6: The count value started with 0. We added 1, then 5, and then 10 to get to 16
Tip: Before we go through this exercise, take some time and think about it and try to implement it yourself. It is mostly straightforward. Hint: you’ll need to introduce one new prop for Button. Give it a shot and come back when you are ready to compare your solution with mine.
The first thing we need do is make the +1 label in the Button component a custom one.
To make something customizable in a React component, we introduce a new prop (which the parent component can control) and make the component use its value. For our example, we can make the Button component receive the amount to increment (1, 5, 10) as a new prop. I’ll name this prop clickValue. We can change the render method in CountManager to pass the values we want to test to this new clickValue prop.
Code Listing 81: The clickValue prop
return ( <> <Button clickAction={incrementCounter} clickValue={1} /> <Button clickAction={incrementCounter} clickValue={5} /> <Button clickAction={incrementCounter} clickValue={10} /> <Display content={count} /> </> ); |
Note a couple of things about this code so far. First, I did not name the new property with anything related to count. 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 its click event is triggered. For example, naming this new property countValue would not be the best choice because we would read the code to understand that a Button element is related to a count. 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.
Also note that 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 CountManager component is the incrementCounter action function. It cannot have a hardcoded count + 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:
Code Listing 82
incrementCounter = (incrementValue) => setCount(count + incrementValue+); |
Now all we need to do is make the Button component use its clickValue prop as its label, and make it invoke its onClick action with its clickValue as an argument.
Code Listing 83
const Button = ({ clickValue, clickAction }) => { return ( <button onClick={() => clickAction(clickValue)}> +{clickValue} </button> ); }; |
Note how I had to wrap the onClick prop with an inline arrow function in order to bind it to the button’s clickValue. The JavaScript closure for this new arrow function will take care of that.
The three buttons should now increment the shared count state with their three different click values. You can see the code for this example at jscomplete.com/playground/rs2.9.
Imagine we need to count the characters a user types in a text area, just like Twitter’s tweet form. With each character the user types, we need to update the UI with the new character count.
Here’s a component that displays a textarea input element with a placeholder div for the character count:
Code Listing 84: Code available at jscomplete.com/playground/rs2.10
const CharacterCounter = () => { return ( <div> <textarea cols={80} rows={10} /> <div>Count: X</div> </div> ); }; ReactDOM.render(<CharacterCounter />, mountNode); |
To update the count as the user types in the textarea, we need to customize the event that fires when the user types. That event in React is onChange. Note that it is different than the DOM’s onchange event, which is not fired when the user types in a textarea element. React has a few improvements to the way events are fired.
We’ll also need to use a state element for the count of characters and fire its updater function within the onChange event.
In the new onChange event handler that we need to come up with, we’ll need access to the text that was typed in the textarea element. We’ll need to read it somehow because React by default is not aware of it. As the user types, the rendered UI changes through the browser’s own state management. We did not instruct React to change the UI based on the value of the textarea element.
We can read the value using two main methods. First, we can read it by using the DOM API itself directly. We’ll need to "select" the element with a DOM selection API, and once we do that we can read its value using an element.value call. To select the element, we can simply give it an ID and use the document.getElementById DOM API to select it.
Because React renders the textarea element, we can actually do the element selection through React itself. React has a special ref attribute that we can assign to each DOM element and use to access it later.
We can also access the element through the onChange event’s target object itself. Each event exposes its target, and in the case of an onChange event on a textarea, the target is the textarea element.
That means all we need to do is:
Code Listing 85: Code available at jscomplete.com/playground/rs2.11
const CharacterCounter = () => { const [count, setCount] = useState(0); const handleChange = (event) => { const element = event.target; setCount(element.value.length); }; return ( <div> <textarea cols={80} rows={10} onChange={handleChange} /> <div>Count: {count}</div> </div> ); }; |
This is the simplest solution, and it actually works fine. What’s not ideal about this solution is that we’re mixing concerns. The handleChange event has the side effect of calling the setCount function and computing the length of the text. This is really not the concern of an event handler.
The reason we needed to mix these concerns is that React is not aware of what is being typed. It’s a DOM change, not a React change.
We can make it a React change by overriding the value of textarea and updating it through React as a state change. In the onChange handler, instead of counting the characters, we just set the value of what has been typed on the state of the component. Then the concern of what to do with that value becomes part of the React UI render logic. Here’s a version of the solution that uses this strategy:
Code Listing 86: Code available at jscomplete.com/playground/rs2.12
const CharacterCounter = () => { const [inputValue, setInputValue] = useState(''); const handleChange = (event) => { const element = event.target; setInputValue(element.value); }; return ( <div> <textarea cols={80} rows={10} value={inputValue} onChange={handleChange} /> <div>Count: {inputValue.length}</div> </div> ); }; |
Although this is a bit more code, it has clear separation of concerns. React is now aware of the input element state. It controls it. This pattern is known as the controlled component pattern in React.
This version is also easier to extend. If we’re to compute the number of words as the user types, this becomes another UI computed value. No need to add anything else on the state.