Sebastian Springer
React
The Comprehensive Guide
Imprint
This e-book is a publication many contributed to,
specifically:
Editor Rachel Gibson
German Edition Editor Patricia Schiewald
Copyeditor Melinda Rankin
Translation Winema Language Services, Inc.
Cover Design Graham Geary
Shutterstock: 2265417157/© Oleksandr Antonov;
iStockphoto: 1385385993/© D3Damon
Production E-Book Hannah Lane
Typesetting E-Book III-satz, Germany
We hope that you liked this e-book. Please share your
feedback with us and read the Service Pages to find out how
to contact us.
The Library of Congress Cataloging-in-Publication
Control Number for the printed edition is as follows:
2023945633
ISBN 978-1-4932-2440-1 (print)
ISBN 978-1-4932-2441-8 (e-book)
ISBN 978-1-4932-2442-5 (print and e-book)
© 2024 by Rheinwerk Publishing Inc., Boston (MA)
1st edition 2024
Notes on Usage
This e-book is protected by copyright. By purchasing this
e-book, you have agreed to accept and adhere to the
copyrights. You are entitled to use this e-book for personal
purposes. You may print and copy it, too, but also only for
personal use. Sharing an electronic or printed copy with
others, however, is not permitted, neither as a whole nor in
parts. Of course, making them available on the internet or in
a company network is illegal as well.
For detailed and legally binding usage conditions, please
refer to the section Legal Notes.
This e-book copy contains a digital watermark, a
signature that indicates which person may use this copy:
Notes on the Screen
Presentation
You are reading this e-book in a file format (EPUB or Mobi)
that makes the book content adaptable to the display
options of your reading device and to your personal needs.
That’s a great thing; but unfortunately not every device
displays the content in the same way and the rendering of
features such as pictures and tables or hyphenation can
lead to difficulties. This e-book was optimized for the
presentation on as many common reading devices as
possible.
If you want to zoom in on a figure (especially in iBooks on
the iPad), tap the respective figure once. By tapping once
again, you return to the previous screen. You can find more
recommendations on the customization of the screen layout
on the Service Pages.
Table of Contents
Notes on Usage
Table of Contents
Foreword
Preface
1 Getting Started with React
1.1 What Is React?
1.1.1 Single-Page Applications
1.1.2 The Story of React
1.2 Why React?
1.3 The Most Important Terms and
Concepts of the React World
1.3.1 Components and Elements
1.3.2 Data Flow
1.3.3 The Renderer
1.3.4 The Reconciler
1.4 A Look into the React Universe
1.4.1 State Management
1.4.2 The Router
1.4.3 Material UI
1.4.4 Jest
1.5 Thinking in React
1.5.1 Decomposing the UI into a Component
Hierarchy
1.5.2 Implementing a Static Version in React
1.5.3 Defining the Minimum UI State
1.5.4 Defining the Location of the State
1.5.5 Modeling the Inverse Data Flow
1.6 Code Examples
1.7 Summary
2 The First Steps in the
Development Process
2.1 Quick Start
2.1.1 Initialization
2.1.2 TypeScript Support
2.2 Playgrounds for React
2.2.1 CodePen: A Playground for Web
Development
2.2.2 A React Project on CodePen
2.3 Local Development
2.4 Getting Started with Developing in
React
2.4.1 Requirements
2.4.2 Installing Create React App
2.4.3 Alternatives to Create React App
2.4.4 React Scripts
2.4.5 Server Communication in Development
Mode
2.4.6 Encrypted Communication during
Development
2.5 The Structure of the Application
2.6 Troubleshooting in a React
Application
2.7 Building the Application
2.8 Summary
3 Basic Principles of React
3.1 Preparation
3.1.1 Tidying Up the Application
3.2 Getting Started with the Application
3.2.1 The index.jsx File: Renderingthe Application
3.2.2 The App.jsx File: The Root Component
3.3 Function Components
3.3.1 One Component per File
3.4 JSX: Defining Structures in React
3.4.1 Expressions in JSX
3.4.2 Iterations: Loops in Components
3.4.3 Conditions in JSX
3.5 Props: Information Flow in an
Application
3.5.1 Props and Child Components
3.5.2 Type Safety with PropTypes
3.6 Local State
3.7 Event Binding: Responding to User
Interactions
3.7.1 Responding to Events
3.7.2 Using Event Objects
3.8 Immutability
3.8.1 Immer in a React Application
3.9 Summary
4 A Look Behind the Scenes:
Further Topics
4.1 The Lifecycle of a Component
4.2 The Lifecycle of a Function
Component with the Effect Hook
4.2.1 Mount: The Mounting of a Component
4.2.2 Update: Updating the Component
4.2.3 Unmount: Tidying Up at the End of the
Lifecycle
4.3 Server Communication
4.3.1 Server Implementation
4.3.2 Server Communication via the Fetch API
4.3.3 Things to Know about Server
Communication
4.3.4 Server Communication with Axios
4.4 Container Components
4.4.1 Swapping Out Logic to a Container
Component
4.4.2 Integrating the Container Component
4.4.3 Implementing the Presentational Component
4.5 Higher-Order Components
4.5.1 A Simple Higher-Order Component
4.5.2 Integrating a Higher-Order Component in the
BooksList Component
4.5.3 Integrating the Higher-Order Component
4.6 Render Props
4.6.1 Alternative Names for Render Props
4.6.2 Integrating the Render Props into the
Application
4.7 Context
4.7.1 The Context API
4.7.2 Using the Context API in the Sample
Application
4.8 Fragments
4.9 Summary
5 Class Components
5.1 Class Components in React
5.2 Basic Structure of a Class Component
5.3 Props in a Class Component
5.3.1 Defining Prop Structures Using PropTypes
5.3.2 Default Values for Props
5.4 State: The State of the Class
Component
5.4.1 Initializing the State via the State Property of
the Class
5.4.2 Initializing the State in the Constructor
5.5 The Component Lifecycle
5.5.1 Constructor
5.5.2 “getDerivedStateFromProps”
5.5.3 “render”
5.5.4 “componentDidMount”
5.5.5 “shouldComponentUpdate”
5.5.6 “getSnapshotBeforeUpdate”
5.5.7 “componentDidUpdate”
5.5.8 “componentWillUnmount”
5.5.9 Unsafe Hooks
5.6 Error Boundaries
5.6.1 Logging Errors Using “componentDidCatch”
5.6.2 Alternative Representation in Case of an
Error with “getDerivedStateFromError”
5.7 Using the Context API in a Class
Component
5.8 Differences between Function and
Class Components
5.8.1 State
5.8.2 Lifecycle
5.9 Summary
6 The Hooks API of React
6.1 A First Overview
6.1.1 The Three Basic Hooks
6.1.2 Other Components of the Hooks API
6.2 “useReducer”: The Reducer Hook
6.2.1 The Reducer Function
6.2.2 Actions and Dispatching
6.2.3 Asynchronicity in the Reducer Hook
6.3 “useCallback”: Memoizing Functions
6.4 “useMemo”: Memoizing Objects
6.5 “useRef”: References and Immutable
Values
6.5.1 Form Handling Using the Ref Hook
6.5.2 Caching Values Using the Ref Hook
6.6 “useImperativeHandle”: Controlling
Forward Refs
6.6.1 Forward Refs
6.6.2 The Imperative Handle Hook
6.7 “useLayoutEffect”: The Synchronous
Alternative to “useEffect”
6.8 “useDebugValue”: Debugging
Information in React Developer Tools
6.9 “useDeferredValue”: Performing
Updates According to Priority
6.10 “useTransition”: Lowering the
Priority of Operations
6.11 “useId”: Creating Unique Identifiers
6.12 Library Hooks
6.12.1 “useSyncExternalStore”
6.12.2 “useInsertionEffect”
6.13 Custom Hooks
6.14 Rules of Hooks: Things to Consider
6.14.1 Rule #1: Execute Hooks Only at the Top
Level
6.14.2 Rule #2: Hooks May Only Be Used in
Function Components or Custom Hooks
6.15 Changing over to Hooks
6.16 Summary
7 Type Safety in React
Applications with TypeScript
7.1 What Is the Benefit of a Type
System?
7.2 The Different Type Systems
7.3 Type Safety in a React Application
with Flow
7.3.1 Integration into a React Application
7.3.2 The Major Features of Flow
7.3.3 Flow in React Components
7.4 Using TypeScript in a React
Application
7.4.1 Integrating TypeScript in a React Application
7.4.2 Configuration of TypeScript
7.4.3 The Major Features of TypeScript
7.4.4 Type Definitions: Information about Third-
Party Software
7.5 TypeScript and React
7.5.1 Adding TypeScript to an Existing Application
7.5.2 Basic Features
7.5.3 Function Components
7.5.4 Context
7.5.5 Class Components
7.6 Summary
8 Styling React Components
8.1 CSS Import
8.1.1 Advantages and Disadvantages of CSS
Import
8.1.2 Dealing with Class Names
8.1.3 Improved Handling of Class Names via the
“classnames” Library
8.1.4 Using Sass as CSS Preprocessor
8.2 Inline Styling
8.3 CSS Modules
8.4 CSS in JavaScript Using Emotion
8.4.1 Installing Emotion
8.4.2 Using the “css” Prop
8.4.3 The Styled Approach of Emotion
8.4.4 Pseudoselectors in Styled Components
8.4.5 Dynamic Styling
8.4.6 Other Features of Styled Components
8.5 Tailwind
8.6 Summary
9 Securing a React
Application through Testing
9.1 Getting Started with Jest
9.1.1 Installation and Execution
9.1.2 Organization of the Tests
9.1.3 Jest: The Basic Principles
9.1.4 Structure of a Test: Triple A
9.1.5 The Matchers of Jest
9.1.6 Grouping Tests: Test Suites
9.1.7 Setup and Teardown Routines
9.1.8 Skipping Tests and Running Them
Exclusively
9.1.9 Handling Exceptions
9.1.10 Testing Asynchronous Operations
9.2 Testing Helper Functions
9.3 Snapshot Testing
9.4 Testing Components
9.4.1 Testing the “BooksListItem” Component
9.4.2 Testing the Interaction
9.5 Dealing with Server Dependencies
9.5.1 Simulating Errors during Communication
9.6 Summary
10 Forms in React
10.1 Uncontrolled Components
10.1.1 Handling References in React
10.2 Controlled Components
10.3 File Uploads
10.4 Form Validation Using React Hook
Form
10.4.1 Form Validation Using React Hook Form
10.4.2 Form Validation Using a Schema
10.4.3 Styling the Form
10.4.4 Testing the Form Validation Automatically
10.5 Summary
11 Component Libraries in a
React Application
11.1 Installing and Integrating Material UI
11.2 List Display with the “Table”
Component
11.2.1 Filtering the List in the Table
11.2.2 Sorting the Table
11.3 Grids and Breakpoints
11.4 Icons
11.5 Deleting Data Records
11.5.1 Preparing a Delete Operation
11.5.2 Implementing a Confirmation Dialog
11.5.3 Deleting Data Records
11.6 Creating New Data Records
11.6.1 Preparing the Creation of Data Records
11.6.2 Implementation of the “Form” Component
11.6.3 Integration of the Form Dialog
11.7 Editing Data Records
11.8 Summary
12 Navigating Within an
Application: The Router
12.1 Installation and Integration
12.2 Navigating in the Application
12.2.1 The Best Route Is Always Activated
12.2.2 A Navigation Bar for the Application
12.2.3 Integrating the Navigation Bar
12.3 “NotFound” Component
12.4 Testing the Routing
12.5 Conditional Redirects
12.6 Dynamic Routes
12.6.1 Defining Subroutes
12.7 Summary
13 Creating Custom React
Libraries
13.1 Creating a Custom Component
Library
13.1.1 Initializing the Library
13.1.2 The Structure of the Library
13.1.3 Hooks in the Library
13.1.4 Building the Library
13.2 Integrating the Library
13.2.1 Regular Installation of the Package
13.3 Testing the Library
13.3.1 Preparing the Testing Environment
13.3.2 Unit Test for the Library Component
13.3.3 Unit Test for the Custom Hook of the
Library
13.4 Storybook
13.4.1 Installing and Configuring Storybook
13.4.2 Button Story in Storybook
13.5 Summary
14 Central State
Management Using Redux
14.1 The Flux Architecture
14.1.1 The Central Data Store: The Store
14.1.2 Displaying the Data in the Views
14.1.3 Actions: The Description of Changes
14.1.4 The Dispatcher: The Interface between
Actions and the Store
14.2 Installing Redux
14.3 Configuring the Central Store
14.3.1 Debugging Using the Redux Dev Tools
14.4 Handling Changes to the Store
Using Reducers
14.4.1 The Books Slice
14.4.2 Integration of “BooksSlice”
14.5 Linking Components and the Store
14.5.1 Displaying the Data from the Store
14.5.2 Selectors
14.5.3 Implementing Selectors Using Reselect
14.6 Describing Changes with Actions
14.7 Creating and Editing Data Records
14.8 Summary
15 Handling Asynchronicity
and Side Effects in Redux
15.1 Middleware in Redux
15.2 Redux with Redux Thunk
15.2.1 Manual Integration of Redux Thunk
15.2.2 Reading Data from the Server
15.2.3 Deleting Data Records
15.2.4 Creating and Modifying Data Records
15.3 Generators: Redux Saga
15.3.1 Installation and Integration of Redux Saga
15.3.2 Loading Data from the Server
15.3.3 Deleting Existing Data
15.3.4 Creating and Modifying Data Records Using
Redux Saga
15.4 State Management Using RxJS:
Redux Observable
15.4.1 Installing and integrating Redux
Observable
15.4.2 Read Access to the Server Using Redux
Observable
15.4.3 Deleting Using Redux Observable
15.4.4 Creating and Editing Data Records Using
Redux Observable
15.5 JSON Web Token for Authentication
15.6 Summary
16 Server Communication
Using GraphQL and the Apollo
Client
16.1 Introduction to GraphQL
16.1.1 The Characteristics of GraphQL
16.1.2 The Disadvantages of GraphQL
16.1.3 The Principles of GraphQL
16.2 Apollo: A GraphQL Client for React
16.2.1 Installation and Integration into the
Application
16.2.2 Read Access to the GraphQL Server
16.2.3 States of a Request
16.2.4 Type Support in the Apollo Client
16.2.5 Deleting Data Records
16.3 Apollo Client Devtools
16.4 Local State Management Using
Apollo
16.4.1 Initializing the Local State
16.4.2 Using the Local State
16.5 Authentication
16.6 Summary
17 Internationalization
17.1 Using react-i18next
17.1.1 Loading Language Files from the Backend
17.1.2 Using the Language of the Browser
17.1.3 Extending the Navigation with Language
Switching
17.2 Using Placeholders
17.3 Formatting Values
17.3.1 Formatting Numbers and Currencies
17.3.2 Formatting Date Values
17.4 Singular and Plural
17.5 Summary
18 Universal React Apps
with Server-Side Rendering
18.1 How Does Server-Side Rendering
Work?
18.2 Implementing Server-Side
Rendering
18.2.1 Initializing and Configuring the Server
Application
18.2.2 Implementing the Client-Side Application
18.2.3 Dynamics in Server-Side Rendering
18.3 Server-Side Rendering Using Next.js
18.3.1 Initializing a Next.js Application
18.3.2 Implementing the Page Component
18.3.3 Implementing the Server Side
18.3.4 API Routes in Next.js
18.4 Summary
19 Performance
19.1 The Callback Hook
19.2 Pure Components
19.3 “React.memo”
19.4 “React.lazy”: “Suspense” for Code
Splitting
19.4.1 Lazy Loading in an Application
19.4.2 Lazy Loading with React Router
19.5 Suspense for Data Fetching
19.5.1 Installing and Using React Query
19.5.2 React Query and Suspense
19.5.3 Concurrency Patterns
19.6 Virtual Tables
19.7 Summary
20 Progressive Web Apps
20.1 Features of a Progressive Web App
20.2 Initializing the Application
20.3 Installability
20.3.1 Secure Delivery of an Application
20.3.2 The Web App Manifest
20.3.3 Service Worker in the React Application
20.3.4 Installing the Application
20.3.5 Asking the Users
20.4 Offline Capability
20.4.1 Integrating Workbox
20.4.2 Handling Dynamic Data
20.5 Tools for Development
20.6 Summary
21 Native Apps with React
Native
21.1 The Structure of React Native
21.2 Installing React Native
21.2.1 Project Structure
21.2.2 Starting the Application
21.3 Displaying an Overview List
21.3.1 Static List View
21.3.2 Styling in React Native
21.3.3 Search Field for the “List” Component
21.3.4 Server Communication
21.4 Debugging in the Simulated React
Native Environment
21.5 Editing Data Records
21.5.1 Implementing the “Form” Component
21.6 Publishing
21.7 Summary
The Author
Index
Service Pages
Legal Notes
Foreword
Web development is advancing continuously, which is both
a curse and a blessing for web developers. Hardly a week
goes by without another new library or framework being
hyped in the community and celebrated as the supposed
savior. Developers with experience know that such hype
should be treated with caution. But don't misunderstand
me: there is often something to hype, but as with
everything, you should use your common sense and not
jump onto every new bandwagon without being asked. One
bandwagon that has been running for a long time and can
be jumped onto without hesitation is React, a library
developed by Facebook.
The year 2023 marks the 10th anniversary of React, which
means it’s already a prehistoric rock if you consider the fast
pace of software. It's both nice and reassuring to know both
that React is now firmly established and that developers are
regularly adding new aspects and features. In addition,
React is very well thought-out in terms of its underlying
concepts, which in turn makes it extraordinarily performant
and—compared to other large frameworks—lets it move in
an extremely attractive cosmos—be it the alternative
package manager Yarn, which is also developed by Meta,
the company behind Facebook, and which has already
inspired a couple of features for the official Node.js package
manager, npm; or the Jest testing tool, which is particularly
suitable for testing React applications thanks to snapshot
testing. GraphQL, the more flexible alternative to REST, also
comes from Meta and integrates seamlessly with React
applications. The total of all this is why React remains my
personal preference over other comparable libraries and
frameworks.
For newcomers, there are of course a few hurdles to
overcome with React. Concepts like Redux, JSX, and Hooks
need to be understood before they can be used effectively.
And this is exactly where this completely revised second
edition by Sebastian Springer will help. You’ll learn how to
best set up, organize, and plan React applications, how to
structure components, and best practices to follow.
Particularly noteworthy is that Sebastian manages to
explain topics in individual, self-contained chapters, which
nevertheless follow a common thread and build on each
other in a didactically outstanding manner.
As a reviewer, I had the pleasure of reading both German
editions (or the respective manuscripts) in advance and
have to say: in this English edition, Sebastian achieves
perfection. Not only are newer features presented and
described so that you are brought up to date with React, but
Sebastian has also taken the trouble to revise the entire
book, which has an extremely positive effect on your
reading experience. My respect, Sebastian! And to you, dear
readers, enjoy the book and learning React.
Philip Ackermann
Chief Technology Officer at Cedalo GmbH
Preface
React has established itself as a force to be reckoned with in
the frontend world over the past few years, and it's hard to
imagine the world without it as one of the big three
solutions alongside Angular and Vue. To me, React stands
for a lightweight and flexible way of development that is
nevertheless highly professional. It gives you a high degree
of freedom when building and designing your application.
This is both a curse and a blessing. Especially for beginners,
that’s the point where it gets difficult: Where should I start?
How do I structure my application? How do I solve specific
problems? What libraries and tools do I need to develop my
application? These are just a few questions to ask yourself
as you begin your journey with React, and that's where this
book comes in. Together we’ll implement a complete
application that covers numerous problems from everyday
practice. In the process, you'll learn about not only React
itself, but also parts of the ecosystem around the library.
Whether you're just getting started with React or already
have experience with it, I’d like to invite you to use this
book as an opportunity to actively work with React. Use the
code samples, extend them, or build your own applications.
Try to implement different requirements and be inspired by
the code examples and approaches for your own solutions.
There is hardly a better strategy for delving deeper into a
topic than using the technology or tool yourself, even
making a mistake once in a while and learning from it.
To work with this book, you should have a solid basic
knowledge of HTML, CSS, and JavaScript. If you are unsure
at one point or another, or are wondering what a particular
language element does exactly, I recommend the Mozilla
Developer Network at https://developer.mozilla.org. This is a
comprehensive up-to-date reference for all web
technologies. If you prefer to work with books, I can
recommend JavaScript: The Comprehensive Guide by Philip
Ackermann. Otherwise, I recommend that you should be
curious and try things out. Ask yourself: What happens if I
do this or that at this specific point? Try it out, open your
browser's developer tools, and see the effects of your
experiment. The advantage of frontend web development is
that you can't break anything else in your application except
the frontend, and you can also keep that to a minimum by
using a version control system like Git as you can always
revert back to a working state. In addition to these
experiments, you should also try to develop the sample
application independently or build your own application.
This book is designed both to get you started with React and
as a reference for everyday use. You can either follow the
examples by writing the source code yourself or download
the code and customize it as you like. All that really matters
to me is that you start using React yourself, get to know the
library and its capabilities, and have a lot of fun doing it.
One of the most common questions related to JavaScript
libraries and frameworks is which one is the best. Of course,
React is a good choice when it comes to implementing a
web frontend. However, other solutions like Angular, Vue,
Svelte, and many more are not worse at all. Try to make up
your own mind at this point and give the different
approaches a chance. To me, React has proven itself in
practice in both small and large projects.
This second edition reflects the evolution of React and the
ecosystem around the library. In addition, a great deal of
feedback from readers of the first edition has been
incorporated. Thus, the book no longer relies on one
continuous and, toward the end of the book, very extensive
example, but on smaller sample applications. Although all of
them deal with the topic of book management, they do not
build on each other in a linear fashion.
Before I briefly introduce you to the structure of this book, I
would like to give you a hint along the way: the world of
JavaScript frameworks and libraries is extremely fast-paced,
and so you will find that the version numbers and, to some
extent, the features of the libraries presented here will
change. That's why in this book I also provide you with
background information on concepts and architecture
patterns that will allow you to adapt to changes and get up
to speed very quickly on new features and libraries.
Structure of the Book
The book consists of two parts and is divided into a total of
21 chapters. The first part of the book deals with React
itself, while the second part highlights the ecosystem of the
library with various problems from everyday project work.
The first part starts with an introduction to React, explaining
the basic principles and the most important terms and
concepts (Chapter 1), followed by an explanation of how to
install and use React (Chapter 2). You will then learn how to
use React components in Chapter 3 and Chapter 4. This
involves building the component tree of your application,
the flow of data in this tree, and the way the lifecycle of a
component works. More advanced concepts such as higher-
order components and render props complement these
basics. In Chapter 5, I’ll introduce the now obsolete class
components. The main reason for this is that you will always
encounter this type of component in existing applications.
Chapter 6 is dedicated to the Hooks API, an extension to
React that significantly affects the way an application is
built. In Chapter 7, you will learn about TypeScript, a tool
that supports you in implementation and provides additional
security in the development process.
React supports different approaches when it comes to
styling components. Chapter 8 introduces you to some of
these approaches and shows you how you can integrate
them into your application. Another tool related to the
quality assurance of an application is covered in Chapter 9,
which deals with formulating unit tests. Chapter 10
concludes the first part of the book with a look at forms.
Using forms, you can give your users the opportunity to
actively interact with an application and produce data.
The second part of the book begins with a chapter about the
integration of external component libraries (Chapter 11).
Using Material UI as an example, you’ll see how you can
integrate existing components into your application. In
Chapter 12, you'll learn how to use the React router to
navigate within your single-page application and use it to
render different subtrees of your application. Chapter 13
deals with custom component libraries. This is an important
topic when it comes to reusing components across different
applications. Chapter 14 and Chapter 15 deal with
centralized state management in an application with the
Redux library and with handling side effects with various
asynchronous middleware implementations such as Redux-
Thunk.
In Chapter 16, you’ll learn how to address a GraphQL
interface in your React application. This query language
provides a flexible alternative to the traditional RESTful
interfaces commonly used in web development.
Chapter 17 integrates react-i18next, another external
library, into the application to support internationalization.
Here you will learn more about the handling of numbers and
date values in addition to the pure translation of strings.
The last three chapters deal with different environments
touched by a React application. Chapter 18 begins with an
introduction to server-side rendering to improve the
performance of an application. In this chapter, you’ll also
get to know the popular React framework, Next.js.
Chapter 19 is dedicated to various performance aspects of
React applications. You’ll then learn how to use React to
implement a progressive web app that can be installed on
user systems and run on as many systems as possible
(Chapter 20). The concept of React on different systems is
taken a step further in the final chapter, Chapter 21, with a
focus on React Native. You’ll learn how to use this library to
implement native apps via React.
Download the Code Samples
All code samples in this book are available for download
from the publisher's website at https://www.rheinwerk-
computing.com/5705. Should you have any problems with
the implementation, or if I have overlooked an error despite
careful checking, please feel free to contact me at
[email protected].
Acknowledgments
I would like to thank all the people who helped me write this
book. First and foremost, there’s Philip, who once again took
care of the review of one of my books and contributed many
comments and tips.
A big thank you also goes to Friederike Daenecke for the
linguistic polish.
I would also like to thank the entire team at Rheinwerk
Publishing and especially Patricia Schiewald for her support.
Finally, I am very grateful to my wife Alexandra for all her
patience and support.
Sebastian Springer
Aßling, Germany
1 Getting Started with React
Surely you know Facebook, which is currently the largest
social network in the world. Meta, the company behind this
network, has developed a JavaScript library for the
implementation of the platform to deal with the
requirements of such a product. The biggest problem that
arises when implementing a social network is the large
amount of data, its display in the browser, and its updating.
Because the frameworks and libraries available on the
market in the past didn’t meet Facebook's requirements, or
did so only insufficiently, the company decided to write its
own library—and this was the birth of React.
1.1 What Is React?
React is a library for implementing web frontends. However,
React only covers the view layer—that is, the representation
of the user interface. Regarding the structuring of the
business logic and other architectural relationships, the
library does not make any specifications, which results in a
higher degree of freedom in the design of frontends
compared to other frameworks and libraries. This freedom
has the disadvantage that it is difficult, especially for
beginners, to learn how to use React and to structure larger
applications well. This comparatively high entry hurdle,
combined with a steep learning curve, is also one of the
biggest points of criticism of React. However, the initial
extra effort in learning quickly pays off in development. The
official website of the project, with a lot of further
information and a tutorial, can be found at https://react.dev.
Getting started in the world of React is made much easier
with tools like Create React App. Like many other JavaScript
libraries and frameworks, React is an open-source project.
Its developers have chosen to use the open-source MIT
license, which is fairly open as it allows private and
commercial use, requires the license and copyright to be
stated, and excludes any liability. The source code of the
project is maintained on GitHub and is available in the form
of NPM packages. NPM is currently the largest package
manager worldwide. It is used for almost all JavaScript
projects and provides a public repository and a command
line tool for package management.
React was developed with the ulterior motive to have the
library able to be used on multiple platforms. This approach
also explains why the library is divided into several
packages. Thus, platform-specific components such as the
renderer, which is responsible for rendering the application,
can be replaced with little effort, while the rest of the
structure remains intact. In this chapter, you will get to
know two essential parts of React in more detail, the
renderer and the reconciler, which take care of finding the
differences between two application states in order to
render the application in a manner that is as performant as
possible.
1.1.1 Single-Page Applications
Typically, you use React to implement single-page
applications. The key concept of this type of application is
that the browser does not have to be completely reloaded
when a new state of the application is to be displayed.
Hence the name single page as the application basically
consists of only one HTML page. This page is customized by
using JavaScript to reflect the current state of the
application. However, this type of web application has the
disadvantage that the initial loading process takes much
longer than with a traditional website. The reason is that
you need to load all the resources required to render the
application. However, there are established solutions to this
problem, namely lazy loading and server-side rendering,
which you will get to know later in this book.
For the users of a web application, the single-page approach
is much more convenient than a multipage approach, where
the individual views of the application have to be reloaded
each time the page is changed, as the transitions between
the views can be designed much more smoothly. For a React
application, a multipage architecture is only an option in
rare, exceptional cases, because the entire source code of
React must be loaded for each loading process. This
problem can be minimized by using the browser cache so
that only the first loading process takes more time and all
subsequent ones run much faster. However, after loading
the source code, React must be executed and must build
the visible structure of the application.
In addition, a reload in the browser results in the current
state of the application being discarded in the frontend and
then having to be rebuilt. For a React application, this would
mean that the state of all components would need to be
stored on the server side or in the web storage of the
browser. In a single-page application, the state of the
application is preserved during a browser session, and you
can access the contents of the memory and place objects
there.
React exploits the characteristics of a single-page
application to gain performance, which makes you feel that
web applications are pretty much like native applications.
However, by the time React reached the level of
optimization it has today, the library had received numerous
improvements and enhancements.
1.1.2 The Story of React
Compared to its competition, React is in the middle if you
look at the appearance of the initial releases. The first
version of Angular was released in 2009; Vue.js was
released in 2014. React has been used at Facebook since
2011 and has had a colorful history since then. Originally, it
was used for the central element of the social platform, the
newsfeed. Then in 2012, React came to Instagram, which
was acquired by Meta. The decision to use React also
contributed significantly to the abstraction of the library.
The Emergence of React
The history of React begins in 2011 under the name FaxJS.
Jordan Walke developed a prototype of React using this
library. One of the core elements of FaxJS was seamless
rendering, whether server-side or client-side; even that early
version was already environment-independent. In addition,
responsiveness played a prominent role. When changes are
made to an application's data, the frontend should
automatically adapt. Fast content rendering and low load
times were another feature of FaxJS. Similar to React, the
library took a component-oriented and declarative approach
to building applications graphically. FaxJS, in turn, was
inspired by XHP, an HTML component framework.
In 2015, another platform besides the browser with native
apps was supported by React: React Native.
The Jump from Version 0.x to Version 15
React follows the semantic versioning approach, where the
version of a piece of software consists of three version
numbers: major version, minor version, and patch level. For
a long time, React was developed in version 0.x as part of
minor updates. This practice is quite common in open-
source projects and is meant to indicate that the project is
in its early stages. During this time, breaking changes are
also to be expected for minor releases, which, according to
semantic versioning, may only be the case when the major
version is increased. Overall, a 0.x version stands for limited
stability. This approach has proven successful in other
projects, such as Node.js. Again, the project was in this
development phase for several years. The jump to a major
version, as in the case of React to version 15, is intended to
signal to users that the project has reached full production
maturity and stability.
React has always been used live on the Facebook platform.
There, the new versions are often used before the actual
release so that they have already been extensively tested in
practice at this point. This and the many years of use in
production systems up to this point were to be further
underscored by the leap to version 15.
The move to version 15 in April 2016 resulted in some
significant changes to React itself. For example, support for
Internet Explorer 8 has been removed. The development
process became more transparent with the introduction of
Requests for Comments (RFCs). Since the introduction of
this development process, a change or change request for
React has been published as an RFC and made available for
discussion. These RFCs can be found on GitHub at
https://github.com/reactjs/rfcs. In addition, and to improve
transparency, the meeting notes of the core React team are
published.
The most important technical innovation in React 15 was
the significantly improved handling of the browser's DOM. In
this context, the createElement method was used instead of
the previously used innerHTML method. Also, the need to
wrap text in span elements was removed, and Scalable
Vector Graphics (SVG) support was completed.
React 16: The Next Big Leap
For a long time, the React community was eagerly awaiting
the release of this version. The heart of this version was the
new Fiber reconciler. A reconciler is the algorithm that
calculates the differences between the current and the next
version of the graphical interface. Fiber was supposed bring
a significant performance boost and, more importantly,
pave the way for future developments and new features.
This became necessary because the stack reconciler
reached its limits, especially with animations. Websites like
https://isfiberreadyyet.com illustrate the mood at the time.
shows how the website looked on March 26, 2017.
Figure 1.1 https://isfiberreadyyet.com/ on March 26, 2017
The website answers whether the new release is already
available with a prominently visible No. You can also see the
status of the library's unit tests.
On September 26, 2017, the time had finally come: React
16 was released to the public as the new stable React
version.
With the new reconciliation algorithm, whose functionality
we’ll describe in more detail in this chapter, and subsequent
minor releases, numerous improvements and
enhancements have been added to the core of React:
React 16.2
The lifecycle of components up to this version was not
oriented to the fact that the creation of the component
tree could be canceled. New lifecycle methods were
supposed to make this possible. A possible reason for
such an abort is, for example, a higher-priority change, as
is used in animations. A second feature worth mentioning
in this release is the finalization of the Context API, which
is used to exchange information between components
across the tree structure. This interface was rebuilt with
some adjustments to make it more convenient to use.
React 16.4
One of the most important features of React is that the
library is platform-independent, so it can be used on
mobile devices as well as desktop systems. To
accommodate this, support for pointer events was added
to the library. Thus, touch surfaces and pens are now also
natively supported as input devices.
React 16.6
An important topic in React development is the
performance of the library. With features like the lazy
function, the first part of the suspense feature was
implemented. This feature enables asynchronous content
reloading. In the case of lazy, components can be loaded
asynchronously and a placeholder can be displayed in the
meantime. The static contextType property of a component
makes accessing the context API even more convenient.
React 16.8
In this release, the Hooks API was introduced. However,
behind this feature, which at first seems inconspicuous,
lies a significant upgrade of React's functional
components. Using Hooks, it’s possible to implement both
the lifecycle of the component and its own local state in a
function component, which before this feature was
reserved only for class components. You can learn more
about Hooks in Chapter 6. This feature takes the
decoupling of logic and state from the representation one
step further. Despite the enhanced capabilities, the
introduction of the Hooks API did not bring any breaking
changes, so this extension also joined a series of minor
updates that follow the roadmap of becoming a more
performant and usable library.
Version 16.x
Another feature aimed at improving performance is the
concurrent mode, also known as async mode. This feature
allows you to calculate the component hierarchy of the
application in a separate process without blocking the
main browser process. The lazy function implemented the
first part of the suspense feature, which allowed reloading
components. The Suspense for Data Fetching feature
ensures that other server requests, such as asynchronous
fetch calls to read data, can be wildcarded.
During the development of the new features, especially
the concurrent mode, the development team had to
realize that the original roadmap was planned too
optimistically. The team wanted to thoroughly test the
new features first before including them in the stable
release. For this reason, the concurrent mode was in an
experimental stage for a long time, where it had to be
activated separately.
React 17: The Featureless Update
The semantic versioning approach provides that a major
update may include breaking changes. The release of
version 17 of React was particularly surprising to the
community, as it was announced with the headline “No New
Features.” The focus of this release was to facilitate
upgrades to new version numbers in existing applications.
With this release, the development team introduced gradual
upgrades, a feature that allows you to upgrade your
application to a new version bit by bit.
Another change under the hood was that for event handling,
React no longer registers its event listeners at the document
level, but at the root node of the app.
With server components, React moves to bring the client
and server closer together. This feature is intended to
combine the interactive nature of React components as they
are used in the browser with the performance of the server
side. Like most other major new functionalities, it was
initially added to the library as an experimental feature.
React 18: Concurrent React
The most important feature of the 18th version of React is
the concurrent renderer. This feature allows React to
prepare multiple versions of the graphical interface. This
renderer changes the principles of the previous process and
allows the library to interrupt the rendering process,
continue it later, or start a new process. For the users of an
application, this results in a more responsive UI, as the
application can react more immediately to user interactions.
In addition to the concurrent renderer, React 18 also brings
Suspense for Data Fetching for external frameworks, such
as Next.js.
If you look at the development of React, it quickly becomes
clear that the focus is strongly on performance, but also on
good usability of the interfaces. The development is driven
not only by Facebook itself, where React is strategically
used to implement web frontends, but also by a very large
and active community that has formed around the library.
1.2 Why React?
In client-side web development, there has long been an
emotional discussion about which framework is the best. In
addition to the three major solutions, Angular, Vue, and
React, there are also numerous smaller solutions to choose
from, such as Svelte. As in other areas of software
development, there is no clear winner, there is no right or
wrong, but only alternatives. And so React is “only” an
alternative to its competitors.
However, React has a very specific goal with its features,
architecture, and philosophy. The focus of React is on user
interface design, so when developing, the library gives you
the freedom to design all other aspects of frontend
development yourself. Like the other frameworks, React is
built on a component architecture, but React components
are very lightweight: in the simplest case, components are
functions with a specific return type.
Another feature of React is that there is no predefined
architecture like there is with Angular. Concepts like
dependency injection or services are foreign to React. You
have the freedom to design your architecture as you need it
for your application, and React supports you in this but does
not impose strict requirements. However, this fact is not
always an advantage as it makes it difficult for beginners to
find their way in React. For this reason, both the React team
and the community strive to make it as easy as possible for
new developers to learn React and use the library in
production systems.
React follows the semantic versioning approach to
versioning. Unlike other open-source projects, however,
React does not provide for a regular release cycle in which,
for example, new major releases are published every six
months.
Semantic Versioning
In the semantic versioning approach, the version number
of a project is divided into three numbers separated by a
period. This results in a version number in the format
X.Y.Z. The versioning usually starts with 0 and has no
upper limit.
The first number of the version number is called the major
version. This number is increased in the case of breaking
changes, changes that ensure that the application can
continue to run only with adjustments to the source code.
One example in React is version 16, which introduced the
Fiber reconciler, among other things.
The second number of the version number, which is also
referred to as the minor version, is incremented for new
features. For example, in version 16.8, Hooks were
introduced as an extension to the function components.
The important aspect about a minor release is that the
application must still be able to run without changes in
this case.
The last number, the patch level, is increased for bug
fixes. Such releases occur comparatively often and usually
do not cause any problems.
React developers are careful to keep breaking changes,
meaning major updates, to a minimum. This is also reflected
in the frequency of recent major releases: React 15 was
released in April 2016, version 16 was released in
September 2017, React 17 in October 2020, and finally
React 18 in March 2022.
If changes to the interface are pending, such as in the
lifecycle of the class components, then they are not
introduced directly in the next version and the previous
version is no longer supported. Instead, in such a case, the
previous methods get renamed and are still available for a
transition period. For such customizations, the developers of
React provide Codemods, which is a tool for the automated
customization of the source code. We’ll present this tool in
more detail in Chapter 2. In addition, the React team uses
deprecations. This means that the team marks parts of the
library that will be removed in the future, so that everyone
who develops applications is warned and can schedule
appropriate refactorings.
1.3 The Most Important Terms and
Concepts of the React World
Development of a React application is sometimes very
different from that of traditional web applications. For
example, React follows a component-oriented approach to
building an application. This means that an application is
composed of numerous small building blocks that are
loosely connected to each other. In addition, React has
some optimizations that allow changes in the appearance of
the application to be displayed in a very performant way.
The goal of React is to be able to handle very large amounts
of data in the frontend.
1.3.1 Components and Elements
As mentioned, the central elements of React are
components. They are the building blocks of an application
and provide the structure of the interface. A component
should be as small as possible in its scope and independent
of its environment. This makes it possible to use it in
multiple places in the application or even in multiple
applications. The core of a component is a function that
returns the structure of the component. This function is
referred to as the render function.
You can define the structure of the display in two different
ways:
You can use the React.createElement method to create new
elements and build a hierarchy with those elements.
It’s also possible to use JavaScript XML (JSX)—a syntax
extension for JavaScript—and thus use HTML-like notation
directly in the JavaScript source code of your component
to describe the structure.
The more common variant, which is used throughout this
book, is the second one using JSX.
A component is not the smallest unit available to you in
React. At the lowest level, there are the React elements.
These are translated into native elements, depending on the
environment. For the browser environment, this means that
the React div element is translated into an HTML div
element. In a component, you can use both components
and elements to describe the structure of your application.
To distinguish between elements and components, React
has introduced the convention that elements always start
with a lowercase letter and components with an uppercase
letter.
Components can be parameterized for improved reusability.
If you use JSX, you can pass attributes to a component that
you write as a JSX tag. These are referred to as props in
React and allow objects to be passed in the component tree.
In general, React components have a defined lifecycle that
you can intervene in at various points to influence the
behavior of a component. Components can also have their
own state. This state is a data structure that contains
information for representing the component. If the value of
the state changes, React makes sure that the change is
reflected in the browser and the component will be rendered
anew.
When it comes to components, React again distinguishes
between two categories: class and function components.
Class Components
The class components of React are in some ways relics from
a bygone era. They represented the standard when it came
to components prior to the introduction of the Hooks API in
version 16.8 because only class components had their own
state and lifecycle methods. However, the introduction of
the Hooks API has caused class components to become less
important. In modern React applications, class components
are hardly ever found.
The reason for the decline of class components is that the
second type of components, function components, are more
in line with the character of React. They are more
lightweight and flexible. In addition, the Hooks API solves
some problems of the class components. However, we’ll go
into these aspects in more detail in Chapter 6.
The name class component is based on the fact that a class
component is a JavaScript class that derives from the
React.Component base class. In previous versions of React,
class components were created using the React.createClass
method, although this method is now no longer available
and you should only use the class-based variant.
Alternatively, you can use the create-react-class add-on
package, which is an interface that behaves very similarly to
React.createClass.
The core of the class component is the render method, which
makes sure that the component can be displayed. The
method returns a React element or component as a return
value that React uses for rendering. React manages the
state of a class component in the form of a property and
maps the lifecycle through various methods, such as
componentDidMount.
When you start implementing a new React application, you
should avoid using class components and instead draw on
the more modern function components.
Function Components
As the name suggests, a function component consists of a
single function. This function is nothing more than the render
method of a class component and takes care of rendering
the component. By default, such a function component
originally did not have its own state and lifecycle methods.
Function components were used in their original form as
simple display components. However, with the introduction
of the Hooks API, this changed and function components
became full-fledged React components with state and
lifecycle and have now almost completely replaced class
components. With a few exceptions, this book focuses
exclusively on function components for developing
applications.
1.3.2 Data Flow
A React application lives by its dynamics. As a rule, users
interact with your application and use it to produce and
consume information. A core element of building a React
application is modeling data streams. In the component tree
of the application, information always flows from the parent
elements toward their children.
If you imagine a typical React application that is used to
manage information, typically one of the first requirements
is to implement a list representation of the information. In
the component tree, the list itself is represented by a
component. The individual entries are in turn components
that are in a parent-child relationship with the list. The list
entries are the child elements of the list (see Figure 1.2). To
display the information, you read the data into a central
point, such as the list component, and distribute the
information to the individual child elements. This solution
has the decisive advantage that only a summarized request
is required, and you receive an overview immediately.
Figure 1.2 Data flow in React Application
The list component contains the state; that is, it is
responsible for data management. It downloads the data for
display from the server and takes care of sending changes
to the server. For each data record, a child component is
created, which receives the information to be represented
as props. This method makes sure that a directed data flow
can be implemented, through which React is able to make
the rendering of the interface very performant. A side effect
of this component partitioning is that you can use the
components in multiple places in your application.
However, with this type of data flow, the child components
have no way to pass information back to their parent
components. But that becomes necessary when information
has been modified and the parent component should be
notified about it. One solution to this is to pass callback
functions from the parent to the child component in addition
to the rendering information. These functions, known as
event handlers, are executed by the child component in
certain cases—for example, when data is changed. The
function works in the scope of the parent component, so it
has access to its internal structures and can, for example,
modify the state. This gives you the option to return the
information to the parent component.
The performance benefits that are caused by the directed
data flow come from the fact that React knows exactly
where to change the application's display.
1.3.3 The Renderer
In the default configuration of a React application, the react
and react-dom packages are installed. The react package
contains the library itself. The second package, react-dom,
contains the renderer. This component is used to translate
React elements into concrete HTML elements that can then
be rendered in the browser.
Another renderer for React is React Native, which ensures
that the elements of a React app are translated into their
native equivalents in an iOS or Android app. If you use
different renderers, you should note that the different
environments use their own elements. For example, you can
use div elements with react-dom. In React Native, you use the
view element as the container element instead.
Not only can a React app be rendered in the browser or on a
mobile device, but there are many other renderers available
as NPM packages. For example, Ink is a renderer for
interactive command-line applications using React.
1.3.4 The Reconciler
Among other things, the main React package contains the
reconciler. This algorithm is responsible for detecting
changes in an application. An adjustment to a component
can be made either by changing the props or the state. For
a change to take effect in the browser, the DOM tree must
be at least partially rebuilt in the case of the browser
environment. The rendering of HTML structures is still one of
the biggest weak points for the performance of web
applications. React provides a solution to this problem in the
form of the virtual DOM, an image of the application's
structure in the form of JavaScript objects.
If a modification of the DOM structure is required, React
generates a new version of the virtual DOM. This structure
serves as a blueprint for the new version of the application.
React then attempts to customize the existing DOM using a
series of optimized actions.
For the optimization of the reconciler algorithm, two
assumptions are made to reduce complexity:
Two elements with different types produce different trees.
The key prop can be used to specify which child elements
remain stable between two render steps.
When comparing the original state and the target state,
React proceeds from the root toward the child elements and
performs the comparisons.
As already mentioned, the reconciler first checks whether
the types of two elements match. If the types are different,
the first optimization of the algorithm takes effect, which
states, as mentioned earlier, that two different types lead to
two different trees (see Figure 1.3).
Figure 1.3 Different Tree Structures
React can abort the check in this case and build a
completely new subtree. This means that all previously built
structures are discarded and all components are unhooked.
This terminates the component lifecycle and, if present,
executes the appropriate unmount logic used to clean up
the environment. The new components are then built and
the appropriate lifecycle functions are executed.
If the types of two elements match, then the attributes of
the elements are checked and only the values are adjusted
accordingly. For example, if the element's class name or
style attribute changes, the underlying DOM element is
adjusted accordingly and the subtree is not discarded (see
Figure 1.4).
Figure 1.4 Modification of Attributes in Tree Structures
With components, the situation is similar to elements with
changed attributes: The component instance remains the
same, so the state is preserved and the corresponding
lifecycle functions are executed. Then the child elements
and components are processed.
Multiple Child Elements and the “key” Prop
In the case of a list of several elements, it may happen
that only the order is changed, but not the elements
themselves. This becomes relevant, among other things,
when a new element is inserted at a certain position in the
list. As a result, the index of all subsequent elements
changes, which would mean for React that they have to
be rebuilt. This can be prevented by means of the key
prop. The key prop must contain a unique and stable value
in a list. If the render method of the parent component is
executed again, the value of the key prop is used to check
whether the elements have changed.
Typically, the unique IDs of data records are used as the
value for the key prop. The value does not have to be
numeric. However, it is mandatory that the value is
unique within the list, not across the entire application,
and does not change between the different render calls;
otherwise the assignment cannot be done correctly.
As an aid during development, React issues a warning at
each iteration about a list of elements without a key prop.
This warning states that each child element of a list
should have a unique key prop. Especially for large lists,
using the key prop can significantly improve the rendering
performance.
These concepts have given you a first glimpse into the
world of React. The following chapters of this book build on
these terms and provide additional detailed explanations. In
the next section, you’ll learn about some other libraries that
are often used in conjunction with React.
1.4 A Look into the React Universe
React is a specialized library for creating graphical
interfaces in web frontends. Unlike full-featured frameworks
(such as Angular), such libraries cover only a specific aspect
of an application. The more extensive an application
becomes, the more additional libraries must be included to
speed up development and keep the source code
manageable. In the following sections, we'll briefly introduce
some of the most important libraries in the React universe.
In the course of this book, you’ll get to know these tools in
greater detail.
1.4.1 State Management
One of the most popular architectural forms of large React
applications is the flux architecture, which provides for the
decoupling of the presentation from the state and the
business logic. A concrete implementation of the flux
architecture is the redux library. At its core, redux provides a
central store for storing the application's information. The
data of the store can be read, but not directly written. Here
you have to take a detour via so-called actions. These are
simple JavaScript objects that describe the changes. They
are received by reducer functions, which in turn can modify
the store. To learn more about centralized state
management and redux, see Chapter 14.
1.4.2 The Router
If the application has multiple views, switching between
them can be quite time-consuming and result in cluttered
code in the application. A solution that is also available in
most other frontend frameworks is routing. This feature
refers to an extension that can be used to insert component
trees depending on the selected URL. In the case of the
React router, the history API of the browser can be used.
In addition to just navigating between component trees, the
router supports other features, such as variables in the URL
that you can access in the components, or nested routes.
1.4.3 Material UI
You can find numerous design recommendations on the
web. One of the most widely used is Google's Material
Design. So that you don't have to implement the individual
elements yourself, the Material UI package is a collection of
components that implements the recommendations of
Material Design. The component collection includes not only
standard components such as buttons or input fields, but
also more extensive components such as dialogs, data
tables, or menus.
1.4.4 Jest
Meta has developed Jest, a testing framework that, while
not directly tied to React, is ideally suited for use with the
library. By default, Jest does not require any additional
configuration and runs tests in a simulated environment
rather than in the browser. This fact also ensures that the
tests are executed much faster compared to other
frameworks (such as Jasmine in combination with Karma).
Another notable feature of Jest is snapshot testing, which
allows you to create a static representation of a component
and use it as a basis for comparison.
1.5 Thinking in React
In the React documentation, you’ll find a section called
“Thinking in React” (https://react.dev/learn/thinking-in-
react). It describes how you should proceed when creating
an application. Here, React's component-oriented approach
plays a prominent role, resulting in an application consisting
of a tree of components. You can take advantage of this fact
during the building phase and follow an overall five-step
process.
The development process always begins with a concrete
idea: a simple mock will suffice here. As a concrete
example, examine the form in Figure 1.5.
Figure 1.5 Draft Form
1.5.1 Decomposing the UI into a Component
Hierarchy
A component is a building block of an application. In the
simplest case, you can represent the boundaries of the
components in your mock by rectangles. In doing so, you
should proceed from the larger components to the smaller
ones.
A component should only perform one task. For example,
you can divide the form into its individual sections and
these in turn into the description and the form field.
1.5.2 Implementing a Static Version in React
Then you can statically implement the components
identified in the previous step. This creates a collection of
components, which you can use in the further development
of your application.
The individual components can be nested and the
information passed on via props within the component
hierarchy.
1.5.3 Defining the Minimum UI State
The state of the component hierarchy contains both the
data to be displayed and the state of individual visible parts
of the component. When modeling the state, make sure you
don’t produce any duplicates. These must be synchronized
during the runtime of the application and carry the risk of
errors and inconsistencies.
1.5.4 Defining the Location of the State
Now that you know what data you need, you still need to
determine the appropriate location for it. As a rule, you
should place the state where it is directly needed. An
exception to this is when you place the state at a parent
location to pass the information to multiple child
components.
1.5.5 Modeling the Inverse Data Flow
The information from the state is passed to the child
components via props. If it should be possible for the child
components to modify parts of the state, you must
implement this by means of functions. In this case, you pass
functions from the parent to the child components in which
you modify the state of the parent components.
You can even build your entire application according to this
scheme. Over time, you can build your own library of
components that you can reuse, which speeds up your
development work as a whole.
1.6 Code Examples
In this book, you will become more familiar with each area
of React and the ecosystem that has formed around the
library. Reading the theory is one thing, but applying what
you've read in real life is completely different. Only when
you work with React and its numerous extensions yourself
will you really get to know the library. The examples in each
chapter are self-contained, so they do not build on each
other and function independently.
You can implement almost any application in React, from
classic CRUD applications for managing data records to
complex interactive applications that you can use to map
business processes to browser games. The individual
chapters, while independent, share a topic that will run
throughout the book. The examples represent the parts of a
library—that is, an administrative interface for books, which
enables you to view, create, modify, and delete data
records.
1.7 Summary
This chapter presented an introduction to the world of
React. The main topics you learned in this chapter are as
follows:
React is usually used in the context of single-page
applications.
The development of React spans from the initial
introduction to Facebook to the current development and
integration of the concurrent renderer and server
components.
React follows the semantic versioning approach, where
breaking changes only occur in major versions. However,
the development team tries to avoid such breaking
changes if possible.
Components are the building blocks of a React
application. They come in two flavors in React: as the old
class components and the more modern function
components in combination with the Hooks API.
You can model the data flow using props by passing
objects and functions to child components. The functions
allow child components to notify parent components.
In React, the renderer takes care of the rendering in the
respective target platform.
The reconciler is the algorithm used to calculate the
differences between the current component tree and the
future component tree.
You also learned about some libraries that are used
together with React in an application.
Finally, you learned how to proceed when building an
application.
This chapter was mostly concerned with theoretical
concepts behind React. In the next chapter, you’ll learn how
to start developing a React application.
2 The First Steps in the
Development Process
In this chapter, you’ll learn about the development process:
from initializing the application, configuring the
development environment, and debugging the application
to building the application.
The first steps in developing a single-page application
usually involves a lot of effort: you need to download and
install dependencies, create structures—that is, files and
directories—and either launch the application directly from
the file system or deliver it via a web server. In this chapter,
we’ll look at the lifecycle of a React application. You’ll also
learn about the different ways to start developing such an
application.
However, you’ll usually use an established and very widely
used tool called Create React App to start your development
work. This command line tool takes care of the most
important steps and creates a new React application for
you, which enables you to start developing immediately.
2.1 Quick Start
Do you want to start developing your application
immediately and deal with the background and the various
alternatives later? You’ll learn how you can do that in the
following sections. For explanations and details, just
continue reading the chapter to the end.
2.1.1 Initialization
To initialize a new React application, first switch to the
command line of your system and enter the following
command:
npm init react-app library
Listing 2.1 Initializing an Application Using NPM
A prerequisite for the successful execution of this command
is a local installation of Node.js including NPM. The npm init
command creates a new directory named library where your
application is located. All dependencies and structures are
prepared to the point that you can start your application
with the commands from Listing 2.2 and start developing.
cd library
npm start
Listing 2.2 Starting the Application
More information about Node.js and NPM, the Node package
manager, will be provided in Section 2.4.2. At this point, all
you need to know is that NPM is part of the Node.js
platform, which you can get from http://nodejs.org.
2.1.2 TypeScript Support
I recommend that you always initialize your React
application, no matter how small, with TypeScript. For this
purpose, Create React App provides application templates.
For example, the command
npm init react-app library --template typescript
Listing 2.3 Initialization with TypeScript
integrates TypeScript and all necessary tools into the
application during the initialization. Then you can switch to
the directory and start developing.
2.2 Playgrounds for React
To quickly try something with React or get an initial feel for
the library, you don't necessarily need to install anything on
your system. Numerous platforms exist that provide you
with a basic version of React in the browser. For example,
you can create small applications and test them directly in
the same window. This approach is only suitable for a very
small scale and for smaller experiments.
Warning!
As soon as you start working on a larger application, you
should write the source code locally on your system and
stop using such a playground.
Ahead, we’ll introduce one of these platforms: CodePen.
2.2.1 CodePen: A Playground for Web
Development
You can use the CodePen platform both after a free
registration and anonymously without registration. The
address of CodePen is https://codepen.io. As with other
similar platforms, you have three editors, one each for
HTML, CSS, and JavaScript. Also, the platform will show you
the result of executing your code in another section of the
window. You can vary the size of the individual sections
using drag and drop, and you can also customize the layout
of the entire platform.
Users that are logged in can save their experiments and
have an overview of the saved projects through a
dashboard. The registration can be done either directly via a
CodePen account or via a Twitter, GitHub, or Facebook login.
Figure 2.1 shows the default view of CodePen. In the next
section, you'll learn the steps to get to a React playground
where you can run your experiments.
Figure 2.1 CodePen View
2.2.2 A React Project on CodePen
In the default configuration, CodePen doesn’t include any
libraries and just provides you with a combination of HTML,
CSS, and JavaScript. You can activate React in this
environment by first clicking the Settings icon in the
JavaScript editor. There, under the JS item, select Babel as
the JavaScript preprocessor and then add the two
libraries, react and react-dom, under Add External
Scripts/Pens. This configuration allows you to start
developing your React application.
Babel
Babel is a JavaScript compiler that accepts JavaScript
source code and in turn also produces JavaScript source
code. The purpose of Babel is to simulate features that
aren’t yet implemented in certain browsers. Babel itself
only provides the compiler infrastructure and can be
extended by plugins. Each of these plugins is responsible
for a specific aspect, such as supporting arrow functions.
Several plugins can be combined into presets. This is a
convenient feature that should save you from having to
include many plugins. For example, the React preset
groups some JSX plugins for you.
A React application requires an HTML element in which to
insert the application. For this purpose, you need to insert a
div element in the HTML editor with an id attribute and the
value, root (see Listing 2.4). The value of this attribute is
freely selectable and may differ. In this case, you must
adjust the reference when you bootstrap the application.
<div id="root"></div>
Listing 2.4 Container for the React Application
In Listing 2.5, you can see the JavaScript source code of the
sample application. It consists of defining a component and
rendering the React application.
const Greet = ({ name }) => <h1>Hello {name}!</h1>;
const rootElement = document.getElementById('root');
const root = ReactDOM.createRoot(rootElement);
root.render(<Greet name="Reader" />);
Listing 2.5 Source Code of the React Application
In the JavaScript editor, you first create a simple component
called Greet. Make sure that the name of the component
starts with an uppercase letter. The component is an arrow
function that returns a JSX element. The function receives a
props object as an argument, which you can use to access
passed values. You can access the name value, which you
extract via a destructuring statement, in the JSX structure
with curly brackets. The details about the structure of a
component can be found in the following chapters.
After defining the component, you get down to the actual
setup of the application. First, you need a reference to the
container element where you are inserting the application—
that is, the div element you defined earlier. Then you use
the createRoot method to create a root object from the
ReactDOM object and the reference to the div element. In the
final step, you call the render method of the root object and
pass it another JSX structure, which contains the Greet
component as an HTML tag with the name attribute. This way
you can make sure that React renders the component and
produces output like that shown in Figure 2.2.
The CodePen platform has a pretty simple structure. As a
consequence, in the default setup, you have no way to
divide your application into multiple JavaScript files. Other
platforms, such as CodeSandbox, for example, offer this
feature, but local development of applications is still much
more convenient and flexible. In the following section, you’ll
learn how to start developing your React application on your
own system.
Figure 2.2 View of the React application in CodePen
2.3 Local Development
The advantage of a platform like CodePen that is available
anytime and anyplace and in which you can share your code
with other developers worldwide is somewhat marred by the
disadvantage that debugging in such an environment is only
possible in a pretty awkward manner. You also need an
existing internet connection and have no way to keep your
source code in a local repository. In most cases, you will
develop your application locally on your computer. Once
again, there are numerous options. The easiest way is to
integrate the library directly into a web page.
Compared to libraries like Vue.js, React is often criticized for
being comparatively difficult to get started with. Whereas
Vue.js only requires you to include a JavaScript file, React
seems to require you to go through an extensive installation
process first. However, this is only partially true.
To use React in a small local application, you should follow a
similar procedure to configuring a playground such as
CodePen. First, you want to create an HTML file named
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hello React!</title>
<script
src="https://unpkg.com/react@18/umd/react.development.js"
crossorigin
></script>
<script
src="https://unpkg.com/react-dom@18/umd/react-dom.development.js"
crossorigin
></script>
<script src="https://unpkg.com/babel-standalone@6/babel.min.js"></script>
<script src="index.jsx" type="text/babel"></script>
</head>
<body>
<div id="root"></div>
</body>
</html>
Listing 2.6 Initial index.html File with Direct React Integration
Listing 2.6 shows the structure of this initial file. First, you
need to make sure that the react and react-dom libraries are
loaded. You can do this via the unpkg.com content delivery
network (CDN). To use the more comfortable JSX syntax to
build your components, you also need Babel, which you can
also get from unpkg.com. With this setup, you can finally
include the index.jsx file that contains your custom
application. At this point, it’s important that you specify the
type attribute with the value text/babel. This way Babel can
ensure that the file is translated so that the browser can
interpret the JSX code. In the body of your HTML file, you
define the container in which your application will be
mounted. The .jsx file extension signals that this file is not
an ordinary JavaScript file, but that JSX is used and therefore
a tool like Babel is needed for translation.
The contents of the index.jsx file correspond to the source
code that you executed as JavaScript in CodePen in the first
example. Listing 2.7 summarizes it again for you:
const Greet = ({ name }) => <h1>Hello {name}!</h1>;
const rootElement = document.getElementById('root');
const root = ReactDOM.createRoot(rootElement);
root.render(<Greet name="Reader" />);
Listing 2.7 JavaScript Source Code of the Local React Application
When you load the index.html file locally in your browser,
you will only see a blank page, and when you look at the
JavaScript console of your browser, you’ll see an error
message telling you that the index.jsx file could not be
loaded. This is because Babel tries to load this file by means
of an asynchronous request, and for security reasons such
requests cannot be executed via file://-, but only via the
http:// protocol.
The easiest way to fix this problem is to test your
application with a local web server.
Using a Local Web Server
Some browser features require that an HTML document be
read from a web server rather than from the local hard
disk. To simplify this, numerous projects exist that provide
you with a lightweight web server. One of the best-known
projects is called http-server and is available as an NPM
package.
To use the http-server web server, you have several
options. You can install it locally, globally, or on demand:
Local installation
For the local installation, you need to switch to your
system’s console, navigate to the directory where your
React application is located, and run the following
commands:
npm init -y
npm install http-server
The first command creates a package.json file for your
project, which contains its description and configuration,
such as installed dependencies. The second command
installs the http-server in the current directory in the
node_modules subdirectory. After that, you can start the
http-server using the node_modules/.bin/http-server .
command. The server then delivers the contents of the
current directory to all available network interfaces on
your system, so you can access your application via
http://localhost:8080.
Global installation
You can start the global installation on the command
line via the following command:
npm install -g http-server
This makes sure that the package is installed into a
directory that is in the system's search path so that you
can run the http-server on the console using the http-
server . command. Again, the program returns the
contents of the current directory in all interfaces on TCP
port 8080.
On-demand execution
The third and last variant consists of using the npx
command, which has been part of the NPM since
version 5.2. Using npx http-server ., you can first search
for a local version of the package; if that can’t be found,
it will be downloaded and executed directly. Again, the
contents of the current directory are made available.
Once the package has been run, it will be discarded.
Which option should you use for which purpose? In
general, you should avoid global installations of packages
as this creates an implicit dependency and locks you into
one version for your entire system. The local installation
of a package is worthwhile whenever you need the
functionality more than once, whereas the on-demand
installation can be used when you need a functionality
once or only a few times.
If you now run http-server as described, you’ll reach your
application via http://localhost:8080, where it should run
without errors and you should get a view like the one shown
in Figure 2.3.
After these two digressions into the execution of React, in
the following sections we’ll focus on the variant most
commonly used for working with a React application: the
Create React App project.
Figure 2.3 Local Execution of the React Application
2.4 Getting Started with Developing
in React
Almost all major JavaScript frameworks have command line
tools, which relieve you of routine tasks that arise in the
course of development. The most time-consuming task in
the development process is setting up the environment. This
is mainly because numerous tools have to be put together
for the development and build process.
Typically, tools such as package managers, bundlers like
Webpack, Babel as a transpiler, and numerous others
collaborate in the development of a web application. A
command-line tool like Create React App handles the
coordination of these tools and creates a basic structure for
your application.
2.4.1 Requirements
To implement a React application, you need some tools on
your computer.
Node.js
For example, you should have an up-to-date version of
Node.js. You can get it either directly from
https://nodejs.org/ as an installer package or via the
package manager of your operating system.
Node.js
Although React is a frontend library that can be used
independently of Node.js, the server-side JavaScript
platform nevertheless plays an important role in the
development process. Many tools you use during the
implementation are based on Node.js. Node.js is based on
the V8 engine, which is also used in the Chrome browser.
The platform has a set of core modules that allow you to
access operating system resources (such as the network
or disk) during development.
In addition to the JavaScript platform itself, the Node.js
package also includes NPM, a package manager that can
be used to manage the dependencies of your application.
In this chapter, you’ll also get to know Yarn, a largely API-
compatible alternative to NPM.
On the command line, you can use the following
commands to check that the two tools are installed
correctly:
$ node -v
V19.2.0
$ npm -v
8.19.3
Editor
In addition to Node.js, you should install an editor on your
system that you can use to write the source code of your
application. I recommend using either the free Visual Studio
Code editor from Microsoft or the paid development
environment WebStorm from JetBrains, but you can use
most any editor. However, you should make sure that it has
convenient features such as syntax highlighting for
JavaScript, TypeScript, and, in the best case, React and JSX,
and that it offers you code completion—that is, the correct
completion of variable and method names when you have
started typing them.
Browsers
React can be run in various environments. However, React
applications are usually run in the browser. So for
development purposes, you should have at least a recent
version of one of the four main browsers: Chrome, Firefox,
Edge, or Safari. The examples in this book, insofar as they
concern specific browser features, refer to Chrome because
it’s so widely used. Functionalities such as the developer
tools can also be found in the other browsers in a similar
form and to a similar extent.
React supports all modern browsers. Since version 18 of
React, Internet Explorer is no longer supported. Microsoft
discontinued support for this browser on June 15, 2022,
anyway. If you still want to support older browser versions or
even Internet Explorer, you need to install various polyfills.
These are combined in the react-app-polyfill package and
can be installed via a package manager. After that you only
need to integrate the version suitable for your browser.
Listing 2.8 demonstrates this using Internet Explorer 11 as
an example.
import 'react-app-polyfill/ie11';
Listing 2.8 Loading Polyfills for Internet Explorer 11
Polyfill
A polyfill is a piece of JavaScript source code that is used
to emulate a feature that isn’t present in the browser.
Polyfills are usually much less powerful than the actual
browser features. However, the main issue here is not to
exclude any users, and less about performance.
2.4.2 Installing Create React App
The variants presented so far for starting development with
React have the disadvantage that they allow a quick start in
development, but the development comfort falls by the
wayside. This problem is solved by the Create React App
project. This is a command-line tool developed by Facebook.
The project's website can be found at https://create-react-
app.dev/. Unlike other command line interface (CLI) tools
(such as Angular CLI) that guide you through the entire app
lifecycle, Create React App only assists you in the app
creation process.
Create React App takes care of almost all the work involved
in initializing an application. The tool creates the basic
structure of the application, downloads all the required
dependencies, and prepares everything to the point where
you can start developing directly. The generated application
uses packages established in the community for standard
tasks, such as Webpack as a bundler and Babel as a
transpiler. However, the configuration of the tools used is
handled by Create React App by default and hidden from
you, so you don't come into contact with it. In most cases,
the default configuration is also sufficient. If that isn’t the
case for your application, you have the option of exporting
the configuration and adapting it accordingly. To learn
exactly how this works, Section 2.4.4.
Another convenient feature Webpack offers is the Webpack
dev server, a local web server through which you can test
your application directly. During development, each time a
change is made to the source code, the affected files are
reprocessed and inserted into the running application. The
infrastructure ensures that the browser is automatically
reloaded so that the changes take effect immediately. Under
no circumstances should you use the dev server for
production operations as reloading the application results in
resetting the state of the application in the browser.
The primary goal of Create React App is to get you started
quickly with development while keeping complexity to a
minimum. This is noticeable on the one hand by the default
configuration mentioned earlier and on the other hand by
the handling of dependencies. If you look at the list of
directly installed dependencies in the package.json file of a
fresh React application created using Create React App,
you’ll find a total of three installed libraries: react, react-dom,
and react-scripts. All other required dependencies are
dependencies of these three libraries, which also greatly
simplifies the update process of an application.
But enough of the introductory words; now let's start the
development of an application! You have several options for
installing Create React App, depending on which package
manager and strategy you choose.
Initialization Using “npm init” and “npx”
While several package managers exist for JavaScript, NPM is
by far the most widely used. The simplest way to initialize
your React application is to use the npm init command. Using
npm init react-app library
Listing 2.9 Initializing a React application using “npm init”
you also create a local directory named library where Create
React App builds the structure for your application. If Create
React App isn’t installed on your system, NPM will download
it temporarily, run it, and then discard it. Typically, you use
the npm init command to create a new package.json file in
an interactive process. In our case, you also pass the react-
app initializer. NPM then uses the npx tool to download the
package named create- and the name of the initializer and
run it. Alternatively, you can also use npx directly. In this
case, the relevant command is as follows:
npx create-react-app library
Listing 2.10 Creation of a New React Application Using “npx”
The effects of both commands are equivalent, and you can
start working on your application directly afterward.
The Node Package Manager
The name of the NPM tool is somewhat misleading. NPM is
a package manager for JavaScript that you can use both
for server-side development with Node.js and for
managing dependencies in the frontend. NPM is
developed as an independent open-source project and is
part of most Node.js installations. On the command line,
NPM is available across the entire system via the npm
command.
Besides the npm command, there are other important
components of NPM in your project: The package.json file,
which is usually located in the root directory of your
application, contains the description of your application.
The node_modules directory stores the installed packages.
Usually, this directory does not get versioned along with
the application. Instead, if you want to rebuild an existing
application from the version control system, you need to
run the npm install command to install all dependencies
listed in the package.json file.
In addition to the package.json file, there is also the
package-lock.json file. This file lists all installed
dependencies of your application and their dependencies.
It ensures that each installation of your application is
similar to all other installations. In package-lock.json, you
can find the package names, their locations in the NPM
registry, and an integrity hash that protects against
tampering with the package. You should version both the
package.json and the package-lock.json files of your
project.
You can install, update, and remove packages from your
system using NPM. The following list introduces the most
important commands:
npm install <package name>
Downloads the specified package from the NPM
repository and installs it. The package is automatically
entered as a dependency in the package.json file of
your project. You can use the -save-dev option to specify
that this is a dependency which is only needed during
development.
npm update <package name>
Updates the specified package within the allowed
version range specified in the package.json file.
npm remove <package name>
Removes the package.
npm list
Lists the currently installed packages.
npm init
Creates a package.json file.
Note
Throughout the rest of this book, I will use NPM as a
package manager. However, all examples can be
reproduced with minor adjustments using Yarn or any
other package manager, such as pnpm.
Do Not Use the Global Installation via NPM
A previously very common way to use Create React App was
to install the tool globally via the -g option, which you can
see in the following command:
npm install -g create-react-app
Listing 2.11 Installing Create React App Globally Using NPM
The global installation ensures that the tool is available to
you system-wide on the command line. The drawback is
that you then have only one fixed version of Create React
App in your entire system and you have to take care of
updating the tool yourself. Because you only need Create
React App once for an application—namely, during the
initialization—the risk of the tool becoming obsolete is very
high. Today, Create React App even prohibits a global
installation. If you try to use the tool in this mode, you’ll get
the following error message: “We no longer support global
installation of Create React App.” In this case, you have no
choice but to uninstall the global installation and use one of
the variants described previously.
In addition to NPM, you can use Yarn as a full-featured
alternative to initialize your application.
Yarn
Yarn is a package manager for JavaScript developed by
Facebook. The tool is open source and free to use.
You can install Yarn either via an installer package or via
your system's package manager, depending on your
operating system. For more information on the
installation, you should visit https://yarnpkg.com/getting-
started/install.
Yarn is largely API-compatible with NPM and has
approximately the same feature set. At this point, you
may legitimately wonder why you should bother with
another package manager besides the established NPM.
The reason can be found in the history of Yarn's
development. If you look at the website for Yarn at
https://yarnpkg.com, the three words fast, reliable, and
secure will catch your eye. These are the three main areas
where the earlier versions of NPM were rather weak. To
address these issues, some Facebook developers created
the Yarn package manager, with the following features:
Fast
Yarn has a caching mechanism that ensures that once
installed packages do not need to be downloaded again,
but can be delivered directly from the local cache. In
addition, Yarn can parallelize downloads of packages,
further reducing the time of installation.
Reliable
The yarn.lock file ensures that multiple installations of
your application are similar, even on different systems.
In older versions of NPM, only the versions of the
directly installed packages were kept, but not those of
their dependencies, which sometimes resulted in
serious problems. In the yarn.lock file, all packages to
be installed, including their complete dependency trees,
are specified and provided with integrity hashes.
Secure
The integrity hashes in the yarn.lock file make sure that
the packages cannot be manipulated afterward. If even
a small part of the package is modified, the hash value
will no longer match and the installation will fail.
The statement that competition stimulates business also
applies to the package manager market. After the release
of Yarn, the developers of NPM quickly followed suit and
fixed the biggest weaknesses in NPM, so both tools are
now on par with each other.
The big advantage for you is that NPM and Yarn use the
same infrastructure. If a package is available under NPM,
you can also install it using Yarn and vice versa. In
addition, both package managers use the node_modules
directory to store dependencies and the package.json file
to store the most important configurations of your
application. There are only a few commands in which Yarn
and NPM differ. If you install your packages via NPM using
npm install react, you can achieve this in Yarn using the
yarn add react command.
Initialization Using “yarn create”
Yarn is an alternative to NPM. Similar to NPM, you don't
install Create React App on your system using Yarn. The
following command allows you to use a temporary variant of
the tool to initialize your application, after which the
installation of Create React App will be discarded again:
yarn create react-app library
Listing 2.12 Initializing a React application using Yarn
No matter which variant of the installation you have chosen,
after running the respective command you’ll have a fully
functional React application to work with. Before we get into
the structure of the app in the next step, here's some more
in-depth information about Create React App.
Command Line Options of Create React App
It’s usually sufficient to call Create React App with only the
directory name of the application you want to create. In
addition, the tool provides some useful options, which we’ll
briefly introduce here:
-h, --help
This option displays a list of available options.
-V, --version
You can use this option to display the version of Create
React App.
--verbose
This option activates more extensive output. It is mainly
used for troubleshooting.
--info
This option is also often used in debugging. It outputs
environment information such as the operating system
and browser versions.
--use-npm
If Yarn is installed on the system where you are running
Create React App, it will be used as a package manager
by default. You can use the -use-npm option to force the
use of NPM.
--use-pnp
Plug and play is a relatively new feature of Yarn. It enables
you to significantly speed up the installation of
dependencies of a React application because the
packages are not copied to the node_modules directory.
You can use this option to enable the plug-and-play
feature for your React application.
--scripts-version
The react-scripts package represents the core of an
application that you create with Create React App. This
option allows you to specify the version of react-scripts.
Not only do you have the choice between different
variants of concrete version numbers and alternative
packages available through NPM, but you can also use
local directories or archives.
--template
The --template option allows you to specify a custom
template for your application. Here you have several
options—for example, an official Create React App
template, a local directory, or an archive file. Examples of
templates Create React App provides are typescript or cra-
template-pwa.
Especially the newer options like -use-pnp or -template show
the direction in which Create React App is evolving: from a
mere tool for initializing an application to a flexible tool that
offers modern development and multiple choices.
Update of an Existing Application
If you develop an application for a long time, sooner or later
you will face the situation in which a new version of the
react-scripts package is released and you should update
your application to benefit from improvements. You can find
out if such an update is pending by running the npm outdated
command in your application's directory.
For example, to upgrade from version 5.0.0 to version 5.0.1,
use the following command in the command line in the
directory of your application:
npm install
[email protected]Listing 2.13 Updating the “react-scripts” Package
However, you should not perform such an update carelessly;
especially with major releases, like an update of the major
version number, there may be breaking changes that may
limit the functionality of your application. To avoid this, you
should first check the changelog at http://s-prs.co/v570501
before each update (see Figure 2.4). There, in addition to
various categories such as Bug Fix or Enhancement, you’ll
also find information about migrating to the new version
and any breaking changes you need to be aware of.
Once you’ve performed the update, you must restart your
application for the changes to take effect.
You must update the react and react-dom libraries separately
from react-scripts. Again, running npm outdated on a regular
basis will help to keep you informed about new releases.
The React development team also maintains a changelog
with the changes that are introduced by a new release of
the library. The changelog can be found at http://s-
prs.co/v570502.
Figure 2.4 Excerpt from the Create React App Changelog
Automatic Update of the Source Code via “react-
codemod”
Some updates to React will require changes to the existing
code of the application. To avoid having to adapt every
place in your application manually, you can use the react-
codemod project. The combination of JSCodeshift and the
codemod scripts allows you to have certain changes to your
source code automated. Although it sounds a bit risky at
first, Facebook performs it regularly on a large scale on
production code.
When making changes to central interfaces, adapting the
source code manually is not an option, and manipulation via
regular expressions also quickly reaches its limits. For this
reason, Facebook's developers implemented the JSCodeshift
tool. It uses recast to transform an abstract syntax tree
(AST) into a modified AST. These changes can include, for
example, the aforementioned changes to the interfaces. The
coding style is maintained as much as possible.
2.4.3 Alternatives to Create React App
Using Create React App is just one way you can start
developing a React app. There are numerous other options
that don’t require setting up all the structures yourself right
away. Popular alternatives include the following:
Vite
Vite is a build tool, so it’s an alternative to Webpack. It’s
modular and provides templates for different setups,
similar to Create React App. The templates that are
relevant in the context of React are react and react-ts. The
react template builds a React application based on
JavaScript. The react-ts template also integrates
TypeScript into the development process. You can use the
npm create vite library -- --template react-ts command to
create a new React application via the react-ts template.
For more information on the topic of setting up a React
application, you should visit https://vitejs.dev/guide/.
Parcel
Parcel also joins the ranks of web build tools. Its scope of
features is similar to that of Webpack and Vite. Only the
setup requires a bit more work than with the elegant
template solutions in Create React App and Vite. The
Parcel documentation includes step-by-step instructions
for building a React application, which can be found at
https://parceljs.org/recipes/react/.
Snowpack
You can use Snowpack like the other tools for the web
application build process. Compared to its competitors
Parcel and Webpack, Snowpack is kept very lightweight
and requires almost no configuration. Like Vite, it also
provides a template system through which you can create
an executable React application with just one command.
On the command line, you can use the npx create-snowpack-
app react-snowpack - template @snowpack/app-template-minimal
command to set up your application. For more
information, see the documentation at
www.snowpack.dev/tutorials/react.
In addition, there are frameworks based on React, such as
RedwoodJS or Next.js. These frameworks include their own
command line tools to help you initialize and build your
application.
2.4.4 React Scripts
Besides the dependencies you need for development, the
react-scripts package provides you with another tool: React
Scripts. This is an executable program you can use to start
your application or build it for production use. The various
scripts are available as entries in the package.json file under
the scripts section. In total, there are four different scripts
with different meanings:
start
The start script starts the Webpack dev server and runs
your application. In the default configuration, you can
reach your application at the following URL:
http://localhost:3000. If the port is already in use by
another application, you will receive a corresponding error
message on the console. You can use the PORT
environment variable to select an alternative port.
Listing 2.14 shows how to use port 8181 as an alternative
to port 3000:
PORT=8181 npm start
Listing 2.14 Running the Application on an Alternative Port
For example, in an application that you create using
Create React App, you can use the ECMAScript module
system with the import and export keywords. To make this
work in all browsers, a combination of Babel and Webpack
is used. A positive side effect of the tools used is that the
application is automatically reloaded when changes are
made.
It’s also worth taking a look at the console from time to
time, as error and warning messages are also issued here
if there are any problems in the build process. In general,
it’s recommended to follow a zero-warning policy—that is,
console output without warnings.
build
During development, you should divide your application
into as many small components as possible. Each of these
components has exactly one purpose and is located in a
separate file. The build script is used to pack the many
component and helper files into an application bundle and
optimize it to use as little memory as possible, which has
a positive effect on the bandwidth required when the
application is delivered to the client. If you run this script,
you will find the result in the build directory of your
application.
test
The initial application is already prepared for unit testing.
All necessary libraries are installed and the configuration
is prepared. For the App component, the root component
that Create React App generates for you by default, a
simple test is already prepared as well. You can run this
using the test script.
eject
Create React App configures the app so that you can't
access the configuration. However, there are situations
where you may want to edit the Webpack configuration.
Especially if you include additional Webpack plugins, you
need to configure them. The eject script extracts the
Webpack configuration from your application so that you
can modify it yourself. But you have to be careful with the
eject script. If you run it once, the configuration will be
exported. However, this process is no longer reversible.
So once you have “ejected” the configuration, you cannot
bring it back.
The four scripts are stored in the package.json file in the
scripts section and can be executed via the package
manager. For example, to start your application in
development mode, you want to execute the following
command in the root directory of your application:
npm start
Listing 2.15 Starting the Application in Development Mode
The script prepares the application for deployment and
starts the dev server. As output, you will receive a
corresponding success message (see Figure 2.5).
Running npm start also ensures that your application opens in
your system's default browser. As a result, you’ll get the
view shown in Figure 2.6 in the browser.
Figure 2.5 Console Output of “npm start”
Figure 2.6 Initial React Application
The React scripts have a few more useful extensions,
including server communication in development mode and
encrypted communicated during development. We’ll cover
those next.
Warning
NPM has a special feature when it deals with scripts. There
are the so-called standard scripts like start or test, which
you can execute directly via npm start and npm test
respectively. Besides these, you can define other scripts.
To run these custom scripts, you must use the run
command. The build and eject scripts are such user-
defined scripts. Thus, for a build of your application, you
need to run the npm run build command on the command
line.
2.4.5 Server Communication in Development
Mode
In single-page applications, the separation between the
frontend and backend is usually very strict. On the one
hand, there is the React application in the browser and on
the other hand, there is a server interface that can be
implemented with any programming language. During
development, you can use the Webpack dev server to
deliver your frontend application. You can also take
advantage of convenient features such as automatic
reloading when changes are made to the code. In such an
application, the server-side backend takes care of data
persistence and other aspects such as user authentication.
The dev server does not deliver the backend. It runs as a
separate server process. This means that the frontend and
the backend are accessible via two different URLs. Security
mechanisms in the browser prevent you from easily
communicating between different servers. Figure 2.7 shows
the error message you get when trying to access a remote
backend (http://google.de in this case).
Figure 2.7 Error Message when Accessing a Backend Interface
To avoid such error messages, the Webpack dev server has
a proxy extension, which ensures that all requests are sent
from the frontend to the dev server, which then forwards
them to the appropriate systems. All you need to do for this
is to add the following entry to your package.json file:
"proxy": "http://google.de"
Listing 2.16 Proxy Configuration in the package.json File
After this adjustment, you must restart your application
once for the changes to take effect. Now, as soon as you
start a request to the backend, the dev server redirects it
accordingly. However, for the proxy to work, the requests
must be directed to the same host that delivers the
application. This is usually the case in production operation,
so there should be no further problems. You can learn more
about server communication in Chapter 3.
2.4.6 Encrypted Communication during
Development
Normally, your application is delivered from the Webpack
dev server over HTTP, so it is not encrypted. In most cases,
this is not a problem either, as the browser interfaces that
require HTTPS make an exception to the delivery of
localhost. The use of HTTPS becomes particularly relevant if
you want to use the proxy feature already presented and
redirect it to an HTTPS backend. In this case, you need to
set the HTTPS environment variable before starting the dev
server. The server then uses a self-signed certificate to
encrypt the connection. Listing 2.17 shows how this works
on a Unix system:
HTTPS=true npm start
Listing 2.17 Launching the Application with HTTPS Support
Note that you will then access your application via
https://localhost:3000, so you’ll have to adjust the protocol.
Now that you’ve launched your application and have a first
impression of the development environment, we’ll introduce
the structure of the application in the next step.
2.5 The Structure of the Application
Create React App prepares the application for you up to the
point where you can start developing right away. To help you
find your way around the generated structure, this section
provides a brief explanation of the various files and
directories.
The base directory of your application contains several files.
They contain global settings and configurations for the
application:
.gitignore
This file lists the files and directories that should not be
checked into the Git repository. This includes, for
example, the node_modules directory.
package.json
In the package.json file, you can find the configuration of
the application—for example, the start scripts and the
installed dependencies.
README.md
The readme file usually contains the documentation of the
application. In the initial version of this file, you’ll find the
description of the different start scripts and a link to the
online documentation. In your application, you should try
to document in this file everything that developers need
when working on the development of the application. This
includes the setup of the development environment, the
build and release process, and special features of the
application. The goal should be that new team members
only need to read the readme file and can then join the
actual development process.
package-lock.json
The package-lock.json file stores the exact versions and
integrity hashes of the installed dependencies and their
subdependencies. This ensures that your application
installations are the same on all systems at all times. In
addition, by checking the checksums of the packages, the
package manager makes sure that no manipulated
packages can be injected.
Besides the files, there are also some directories in the root
directory of the application:
node_modules
This directory stores all installed NPM packages in a flat
hierarchy. The node_modules directory is reserved for the
package manager, so you shouldn’t manipulate its
contents, lest these changes be distributed to other
developers and thus lost.
public
The public directory contains the entry file for your
application: the index.html file. It gets delivered upon a
client request and ensures that a basic framework is
available. In this directory, you can store static assets
such as HTML, CSS, JavaScript, and media files. In most
cases, however, it’s recommended to include this data
dynamically via the module system, as this gives
Webpack the opportunity to optimize the content.
Placing assets in the public directory is recommended if
the names of the resources must not be changed by the
build process, as is the case with the manifest.json file, for
example, because the browser expects the file with
exactly that name. In addition, it may be useful to store
files here that you do not want to be part of your
application package.
Directly from within the index.html file, you can only
reference content in the public directory. To reference
contents of the public directory, you want to use the
PUBLIC_URL variable. Within the index.html file, this variable
is enclosed by percentage signs, as you can see in
Listing 2.18 for the example of the favicon:
<link rel="icon" href="%PUBLIC_URL%/favicon.ico">
Listing 2.18 Including the Favicon in index.html
To create a reference to the contents of this directory from
your application (e.g., from a component), you can also
use the PUBLIC_URL variable. This variable must get the
object structure process.env as a prefix when accessing it,
as shown in Listing 2.19:
<img src={process.env.PUBLIC_URL + /cat.jpeg'} />
Listing 2.19 Referencing Contents of the “public” Directory from the
Application
src
The src directory is where you will spend most of your
time during development. This is where you store the files
that contain the components and all the helper constructs
for your application.
Create React App requires a lightweight structure here,
which consists of a series of files. The index.js file is the
entry point to the file and renders the root component,
which in turn is located in the App.js file. App.css contains
style definitions, which are loaded in App.js. With
App.test.js, you have a first unit test for your application,
which you can execute via the npm test command. In the
index.css file, you’ll find general style definitions, such as
for the body element of your application. Finally, the
logo.svg file represents a static asset that is referenced
from the JavaScript code of your application.
2.6 Troubleshooting in a React
Application
A tool that is often underestimated in development is the
web browser. You can use it not only to display your
application, but also to actively integrate it into the
development process. Modern web browsers have several
helpful tools for analyzing and debugging an application.
First and foremost, there is the debugger. You can use the
shortcut (Cmd)+(Alt)+(i) on a Mac or (Ctrl)+(Alt)+(i) or
(F12) on a Windows or Linux system to open the browser's
developer tools. Figure 2.8 shows the debugger view of a
browser that has stopped at a breakpoint in the application.
To debug your application, open the developer tools, go to
the Sources tab, and set a breakpoint by clicking on the
line number. Right-clicking on the line number also gives
you the option to set conditional breakpoints, where the
browser stops only when a previously defined condition is
met. Another way to set a breakpoint is to use the debugger
statement. To do this, type the keyword “debugger” in the
respective place in the source code of your application. If
your browser's developer tools are open, execution will stop
at this point.
You can navigate in the debugger using the toolbar in the
upper-right part of the developer tools. The icons have the
following meanings:
Resume
If the debugger pauses at a breakpoint, the script can be
continued with this button.
Step Over
Skips the next function call.
Step Into
Jumps into the function to be called. This action adds an
entry to the call stack.
Step Out
Jumps out of the current function. This action removes the
current entry from the call stack.
Step
This action jumps to the next line.
Deactivate Breakpoints
Disables all currently set breakpoints so that execution
continues without interruption.
Pause on Exception
By clicking on this button, you can make sure that the
execution is paused as soon as an exception is thrown.
Figure 2.8 Stopped Debugger in a React Application
If the execution of your application is paused at a
breakpoint, you will get additional information about the
current state of your application. This way you can view the
currently valid variable assignments or the call stack or
define watch expressions, expressions that are reevaluated
at each step in the debugger. The result will be displayed in
the corresponding section of the debugger.
Besides using the browser debugger, there is also the option
to debug your application directly in your development
environment. This has the advantage that you can work
directly in your source code and also set breakpoints there.
For information on how to set up the debugger for your
development environment, refer to the development
environment’s documentation. In the case of Visual Studio
Code, a debugger is integrated. You can find the related
documentation at http://s-prs.co/v570509. The
documentation for WebStorm also contains a separate
section on this at http://s-prs.co/v570503.
However, the debugger is not the only tool you can access
when developing a React application. For both Firefox and
Chrome, there are browser extensions that can show you
the structure and dynamic data of your application. For
Chrome, you can find React Developer Tools in the Chrome
Web Store at http://s-prs.co/v570504. For Firefox, the add-on
is available at http://s-prs.co/v570505.
After installation, you will find two tabs in your browser's
developer tools labeled Profiler and Components. The
Profiler allows you to record performance data at runtime
of your application and evaluate it afterward. In the
Components tab, you can see the current component
structure of your application and inspect the individual
components as well as check their props and state.
Figure 2.9 shows what the React dev tools look like in the
browser.
Figure 2.9 React Dev Tools in Chrome
This brings us to the final step in the development process:
building the application for production use.
2.7 Building the Application
In development mode, the source code is indeed translated
by the Webpack dev server so that the browser can execute
it without any problem. However, the server does not
optimize the code. To prepare your application for
production use, you need to run the npm run build command.
As a result, a new directory named build will be created.
This directory contains all the resources you need to deliver
the application to your users. You can copy the contents of
this directory to the document root directory of a web server
to deploy your application. You can customize this behavior
by adding an entry to your package.json file with the
homepage key and your application URL as the value.
To test if the build worked, you can also install a local web
server and deliver your application through it. One of the
simplest solutions for this is provided by the http-server NPM
package. You can run this package using the npx http-server
build command in the root directory of your application.
2.8 Summary
The purpose of this chapter was to introduce the React
development process and the tools available to you in the
process:
You can integrate React into existing websites as well as
implement a React application from scratch.
Playgrounds like CodePen allow you to run simple
experiments with React by directly including the library.
However, this variant is not suitable for extensive
projects.
React can be integrated in a very lightweight way by
directly including the library files in a static HTML page.
However, in doing so, you forego the comfort of a
prefabricated structure and configured tools.
Create React App is the tool of choice when it comes to
setting up larger applications. You can also implement
extensive applications on the generated structure.
The react-scripts package, which gets installed along with
Create React App, provides four scripts that allow you to
launch, test, build, and export the Webpack configuration.
You can influence the behavior of Create React App as
well as react-scripts via command line options and
environment variables.
All modern browsers have powerful developer tools to
help you troubleshoot issues. In addition, the React
Developer Tools extensions can help you to analyze a
React application.
In the next chapter, you’ll get to know the basic building
blocks of React and start developing your application.
3 Basic Principles of React
All popular single-page frameworks, such as Angular, Vue,
and React, follow a similar concept when it comes to
building an application: the user interface is broken down
into smaller units, called components. React has an
advantage over the other two frameworks in that
components in React are very lightweight, making it easy
for you to create many small and self-contained
components. In this chapter, we’ll look at the different
aspects of components in React. You’ll learn how to organize
the state of your application and how the information flows
through the component tree.
3.1 Preparation
If you don't have a React application at this point, you
should take care of initializing one now so you can try out
the concepts yourself. To do this, switch to your system's
console and run the commands in Listing 3.1. This way,
you’ll create a new application named library, go to the
directory, and start the application.
npx create-react-app library
cd library
npm start
Listing 3.1 Creating and Launching an Application
Once you have started the application, the default browser
of your system will open it automatically and display its
current state with its initial component. For more
information on setup and initialization, see Chapter 2.
By the end of this chapter, you’ll have implemented an
application with a simple component hierarchy. As a
template, we’ll use the mockup shown in Figure 3.1.
Figure 3.1 Application Mockup
3.1.1 Tidying Up the Application
In your application, you won't need some of the structures
that Create React App has created for you, and you should
always make sure that you don’t have unnecessary
structures in your application. So the first step is to tidy up
the application. You can delete the src/logo.svg file. It
contains the logo that will be displayed in the demo
application. Likewise, you can delete the App.test.js file as
you won't have anything to do with testing until Chapter 9.
You can delete the styles of the demo application in the
src/App.css file by clearing the file.
Next, let's look at the existing structures. The two most
important files in the src directory are index.js and App.js. A
convention often used in projects is that files containing JSX
structures are given a .jsx extension instead of .js. To follow
this convention, you should change the file extension of
both files from .js to .jsx.
3.2 Getting Started with the
Application
The two most important files in a React application are the
index.jsx and App.jsx files in the src directory. The index.jsx
file marks the starting point of the application and is
responsible for rendering. In the App.jsx file, you will find
the root component of the application—that is, the entry
point to the component tree.
3.2.1 The index.jsx File: Renderingthe
Application
The source code of the index.jsx file ensures that the
application gets displayed.
Quick start
The index.jsx file is the entry point to a React application.
React renders the application with a combination using
the render method of the root object you create via the
createRoot function. When you call createRoot, you pass the
HTML element where you want React to insert the
application and you pass the root component of your
application to the render method. Listing 3.2 shows a
minimal version of this file, which really contains only
what is most necessary:
import ReactDOM from 'react-dom/client';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Listing 3.2 Minimal Version of index.jsx File (src/index.jsx)
The source code of the index.jsx file Create React App
generated for you is shown in Listing 3.3:
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (e.g., reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
Listing 3.3 Entry Point to the Application (src/index.jsx)
The first block in the file consists of the import statements.
From the React package, you use the StrictMode component,
and you need the import of ReactDOM to create the root object
via createRoot. The index.css file provides the styling for the
current file. The App.jsx file exports the root component that
you use as the basis for rendering the application, and
importing reportWebVitals allows you to analyze certain
metrics, which you will find explained in more detail ahead.
Import Statements Placed at the Beginning
The general structure of JavaScript files in a React
application is that all required import statements are listed
first. If you get the idea to distribute import statements in
your files, the build process of the application prevents
this with the following error message: "Import in body of
module; reorder to top import/first". This is a so-called linting
rule. It comes from ESLint, a static code analysis tool for
finding bugs and antipatterns that integrates Create React
App into the build process.
You can use the createRoot function from the react-dom/client
package to create the root object. When you call the
function, you pass the element into which you want to
render your application. The element with ID root is defined
in the index.html file in the public directory. This is an empty
div element, which means that your React app is initially
delivered to users as a blank page, and then React builds
and displays the app. The setup usually only takes a few
milliseconds, so this circumstance is hardly annoying.
However, there are solutions, especially with server-side
rendering, to solve the problem with the initial blank page.
We’ll deal with the topic of server-side rendering in
Chapter 18.
The root object provides the render method, which you use
to let React render the application. This method accepts the
root component as an argument. At this point, you can see a
special feature of React. If you execute this line directly in
the browser, it acknowledges this with a syntax error. The
notation with HTML tags directly in the JavaScript source
code is called JSX. This is a syntax extension of JavaScript
that is translated into regular JavaScript code by the build
process. In this chapter, you’ll learn much more about JSX
and its special features. For now, all you need to know is
that JSX is a convenience feature of React to keep the
source code uncluttered. <React.StrictMode> and <App> enable
you to reference React components.
StrictMode
The StrictMode component is a tool for finding potential
issues in the application. It’s only active in development
mode and does not affect the production build.
The checks include the following:
Finding components with unsafe lifecycle methods
Use of the deprecated String Ref API
Use of the deprecated findDOMNode method
Finding unexpected side effects
Use of the deprecated version of the Context API
Recognition of insecure effects in the lifecycle
It’s basically a good idea to activate these checks. An
exception may be the use of older components or libraries
over which you have no direct control. However, warnings
and error messages here also indicate potential issues
you’ll have to take care of sooner or later.
In the final step, you call the reportWebVitals function. This
step is optional.
reportWebVitals
Simply calling the reportWebVitals function doesn’t do
anything, except that a set of key figures is collected. To
see the values, you must pass a callback function to the
function when you call it. In the simplest case, this is a
reference to console.log. However, you can also pass a
function that sends the data to a server for further
evaluation. The key figures the function provides you with
are as follows:
Cumulative Layout Shift (CLS)
This is the unintended movement in the layout of a
page when, for example, asynchronous elements are
inserted.
First Input Delay (FID)
This metric denotes the time between the first user
input and the browser's response to it.
Largest Contentful Paint (LCP)
Specifies the rendering time of the largest image or text
block within the visible area.
First Contentful Paint (FCP)
This is the time between a page loading and the first
content being displayed to users.
Time to First Byte (TTFB)
Represents the time interval between sending a request
and receiving the first byte of the response.
These figures are analyzed to assess the performance of a
website. They try to include not only purely technical
aspects, but also impact on users. Metrics play a big role
not only for user experience, but also for search engine
ranking.
3.2.2 The App.jsx File: The Root Component
The application itself currently consists of only one
component, located in the App.jsx file. You can simplify the
content of this component for the first step. The source code
of the application component is shown in Listing 3.4:
import './App.css';
function App() {
return (
<h1>Hello React!</h1>
);
}
export default App;
Listing 3.4 Entry Component into the Application (src/App.jsx)
Once you’ve adjusted the source code of the application
accordingly and made sure that the Webpack dev server has
been started via the npm start command, you can view the
result in the browser, which should look as shown in
Figure 3.2.
Figure 3.2 View of the Root Component of the Application
In the following section, you'll learn more about the basics
of React components. You’ll also implement the first
component of the application and include it in the entry
component.
3.3 Function Components
Quick Start
Function components are ordinary JavaScript functions
that return a JSX structure. Listing 3.5 shows a simple
example of such a component:
function MyComponent() {
return <div>My Component works!</div>;
}
export default MyComponent;
Listing 3.5 Function Component
Be sure to define only one component per file. If this
condition is met, you can export the component via a
default export as there can be only one of this type per
file. Elsewhere in your application, you can then import
the component and integrate it as a tag in your JSX
structure.
Components form the units that make up your application.
You’ve already seen how to proceed with the concept in
Chapter 1. At this point, we want to explain the
implementation of a concrete component as a functional
component.
Code Style
When you develop a React application, whether as part of
a team or alone, you should make sure that the source
code is consistent and always follows the same formatting
rules. Beginners and newcomers to development already
face numerous questions about the code style, such as
the naming of components or files. Numerous style guides
exist to clarify these issues. One of the most popular is the
Airbnb JavaScript Style Guide, which you can find at
https://github.com/airbnb/javascript. In addition, there is
the JavaScript Standard Style (https://standardjs.com) and
the Google JavaScript Style Guide
(https://google.github.io/styleguide/jsguide.html).
If you look at the descriptions of these style guides, you’ll
quickly realize that they are quite extensive, making it
difficult to follow all the rules right away during
development. ESLint is a tool that can help you in this
context: it analyzes your source code and points out
errors.
If you start developing your app using Create React App,
ESLint will be preinstalled and will check the code both at
development time and when you build the app. ESLint
provides two error levels, warn and error. Note that errors
of the error level will cause the build to abort and you will
need to fix the error before the process can finish
successfully.
Create React App defines its own rule set called react-app
and enables it by default. So you don't need to install
additional packages or create a configuration for the
default configuration.
But with Create React App, you aren’t limited to following
the given rules. You can modify the existing rules or define
your very own rule set. You can do this both in a separate
ESLint configuration file named .eslintrc.json and via an
additional field named eslintConfig in your package.json
file. Listing 3.6 shows a sample excerpt from the
package.json file:
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"rules": {
"semi": "error"
}
},
Listing 3.6 ESLint Configuration in the package.json File
The configuration extends the react-app and react-app/jest
rulesets predefined by Create React App. As an additional
rule, you want to define semi. This rule states that
semicolons must not be omitted. If you still try to do so,
ESLint acknowledges it with an error, and this error in turn
causes the build to abort. You can test the whole thing by
deleting a semicolon at the end of a line in the App.jsx
file, for example. You can see the feedback in the console
in Listing 3.7:
Failed to compile.
src/App.jsx
Line 6:4: Missing semicolon semi
Search for the keywords to learn more about each error.
ERROR in src/App.jsx
Line 6:4: Missing semicolon semi
Search for the keywords to learn more about each error.
webpack compiled with 1 error
Listing 3.7 Error Due to a Violation of the ESLint Rules
With the appropriate ESLint plugin for your development
environment, it will also show you the errors directly in the
code, as shown in Figure 3.3.
Figure 3.3 Error Message in Visual Studio Code
3.3.1 One Component per File
As a convention, it has become established in React that
you create a separate file for each component. The name of
this file should reflect the purpose of the component so that
you can keep track of it in the file system. For example, for
naming files that contain components, Airbnb's React style
guide provides some guidelines:
The file extension for a component is .jsx to reflect the
use of JSX within the component.
The file name is written in PascalCase, which means that
the first letter is capitalized. If the name consists of more
than one word, each additional word also begins with a
capital letter. Aside from that, the words are not
separated.
The file name corresponds to the name of the contained
component.
The component you implement in the next step represents a
list of books. Consequently, a suitable name is BooksList.
This results in the file name: BooksList.jsx. In a small
application, you can store your components directly in the
src directory. However, as soon as the number of files
increases, you should turn to creating subdirectories. Most
often in this case, the components are grouped by subject.
So, for example, if your application has a user management
feature, all components related to this feature will be
located in the user directory.
But for the current example, you want to create the
BooksList.jsx file in the src directory. In the first step, you
develop a purely static version of this component:
function BooksList() {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
<tr>
<td>JavaScript—The Comprehensive Guide</td>
<td>Philip Ackermann</td>
<td>978-3836286299</td>
<td>*****</td>
</tr>
<tr>
<td>Clean Code</td>
<td>Robert Martin</td>
<td>978-0132350884</td>
<td>****</td>
</tr>
<tr>
<td>Design Patterns</td>
<td>Erich Gamma</td>
<td>978-0201633610</td>
<td>*****</td>
</tr>
</tbody>
</table>
);
}
export default BooksList;
Listing 3.8 Static Version of the “BooksList” Component (src/BooksList.jsx)
There are a few things to consider regarding the component
source code. In previous versions of React, when you used
JSX, which is HTML syntax in JavaScript, you had to import
React so that the build process could translate the
component correctly. In React version 17, the development
team introduced a new JSX transform that eliminates the
need for this import from React.
Import and Export
So far in this book, you have already come into contact
with the ECMAScript module system. It works with the
import and export keywords. Browser support for this
JavaScript feature has improved significantly, but in most
cases a bundler like Webpack is used in combination with
a loader like Babel.
If you implement a component, you must export it so that
you can include it elsewhere in your application. For these
exports, you can use two variants: named and default
exports. Default exports are widely used for implementing
React applications.
A file can have only one default export, but multiple
named exports. When importing, you can use the default
import directly. You can access the named exports using
curly brackets.
The BooksList component is a function component. This
means that it consists of a JavaScript function. This is the
simplest type of a React component. At this point, the
Airbnb style guide recommends using named functions
instead of arrow functions because you are relying on the
interference of function names here. This is a feature where
the JavaScript engine tries to find out the name of a function
to display it in the call stack, for example.
You can think of a React component as a function that you
can include, which is equivalent to a call. The result of the
component is a React element. You can pass information to
a component, but we’ll come to that later. The structure you
define and return in the component is written in JSX. Before
we get into JSX in the next section, you first need to
integrate the component in your application, which you can
do by including it in another component.
Go to the App.jsx file, import the BooksList component, and
integrate it into the structure that returns the App function,
as shown in Listing 3.9:
import './App.css';
import BooksList from './BooksList';
function App() {
return (
<div>
<h1>Books management</h1>
<BooksList />
</div>
);
}
export default App;
Listing 3.9 Integration of the “BooksList” Component (src/App.jsx)
As a convention for structuring the imports in a component,
it has become established that you group the imports. The
first group contains imports of NPM packages; the second
contains imports of custom files.
When including a component, you should note that a
component must always return exactly one root element.
These customizations allow you to view the static version of
your component in the browser. The result should look like
the example shown in Figure 3.4.
Figure 3.4 Static Version of the “BooksList” Component
At this point, the component still doesn’t look particularly
appealing. But with the help of a little CSS, you can easily
fix this issue. You have already learned one method to
include CSS using the index.jsx and App.jsx files as
examples: you need to create a CSS file and import it into
the component. So create the BooksList.css file in the src
directory, and paste the contents of Listing 3.10 there:
table {
border-collapse: collapse;
}
th {
border-bottom: 3px solid black;
}
tr:nth-child(2n) {
background-color: #ddd;
}
td {
padding: 5px 10px;
}
Listing 3.10 Styling of the “BooksList” Component (src/BooksList.css)
For the stylesheet to take effect, you want to include it as an
import in the component file. Note that the style
specifications in the CSS file affect the application globally.
import './BooksList.css';
function BooksList() {…}
export default BooksList;
Listing 3.11 Integrating the Stylesheet in the Component (src/BooksList.jsx)
Due to this adjustment, the book management now looks a
bit more acceptable, as you can see in Figure 3.5.
Figure 3.5 Styled View of the Books List
If you’re wondering what the JSX syntax in the components
is all about, we’ll address this topic in a bit more detail in
the next section.
3.4 JSX: Defining Structures in
React
React introduces JSX, a language extension for JavaScript, to
make it easier for you to develop components.
Quick Start
JSX is a syntax extension for JavaScript that allows you to
create components and elements without much effort. JSX
uses a notation similar to the tags in HTML. Within JSX,
you can insert JavaScript expressions in curly brackets to
display values or the result of a function call, for example.
You usually create loops by converting a data structure,
predominantly an array, into a JSX structure using the map
method.
Formulating conditions with if statements within JSX
structures is not possible. You can either swap this out or
use the ternary or the logical AND operator, for example.
In Listing 3.12, you can see the different aspects of JSX in
use:
function MyComponent() {
const name = 'React';
const items = ['book', 'calculator'];
const boolVal = true;
return (
<div>
{/* Expression */}
<h1>Hello {name}</h1>
{/* Loop */}
<ul>
{items.map((item) => (
<li>{item}</li>
))}
</ul>
{/* Conditions */}
{boolVal ? 'true' : 'false'}
{boolVal && 'true'}
</div>
);
}
export default MyComponent;
Listing 3.12 Overview of the JSX Syntax
For years, web developers were taught to keep HTML, CSS,
and JavaScript neatly separated. However, React breaks
with this principle. There's a good reason for that: React
abstracts the DOM, so you don't have to deal directly with
the HTML structure. The tags you write in JSX are not
ordinary HTML elements, but React elements.
React Elements
React elements are the smallest building blocks of a React
application. They are simple objects that you use to
describe the appearance of your application. React
elements always start with a lowercase letter. An element
can have attributes that are similar to the attributes of
DOM elements. These are written in lowerCamelCase,
such as tabIndex. You can enclose the values of the
attributes in quotes, and then they will be interpreted as
string values. For dynamic values, you use curly brackets.
In this case, you can use JavaScript expressions. A
peculiarity of React elements is that you are not allowed
to use the class and for attributes. Because JSX is
JavaScript at its core, the class and for keywords are
already reserved by the language itself. In React
elements, you can use the className and htmlFor attributes
instead.
The difference between React elements and components
is that, following the convention, elements start with a
lowercase letter and components start with an uppercase
letter. If one of your components starts with a lowercase
letter, you should either rename it directly or assign it to a
variable with an uppercase letter and use that instead.
In addition, an element may have one or more child
elements. Elements are immutable, which means they are
created once and cannot be modified. You can only
recreate them.
When the user interface is updated, React redraws the
affected regions. React optimizes rendering so that only
certain parts are modified.
A component returns a structure of React elements and
components, which in turn are translated into React
elements. The returned structure may have only exactly one
root element. As soon as you try to return multiple elements
arranged in parallel in one component, you’ll receive an
error message.
JSX is a syntax extension for JavaScript, and it is compiled
during the build process. Note that you don't have to use
JSX to implement a React application. Alternatively, you can
use the createElement method of React to create the building
blocks of your application:
import React from 'react';
function MyComponent() {
const Hello = React.createElement('span', null, 'Hello React');
return React.createElement('div', null, Hello);
}
export default MyComponent;
Listing 3.13 Using React.createElement
The createElement method accepts the name of the element
to be created, the inputs—also referred to as props of the
element—and finally the child elements. This notation
alternative to JSX is rarely found in React applications, as it’s
very inconvenient.
In JSX, you can not only model static structures, but also
display dynamic content. You can insert any JavaScript
expressions by enclosing them in curly brackets.
3.4.1 Expressions in JSX
In the current implementation of your component, all values
are static. However, especially for more extensive content—
for example, if you want to display 100 or more books—it
doesn’t make much sense to build your application in this
way. Before you start working on the component, you should
define the data structure independently of the component.
When doing this, you can choose between defining the
individual books as simple objects in an array and defining a
data class for a book and creating an array of instances of
that class.
In React, it’s common to work in a lightweight manner.
Using a data class doesn’t provide any advantages at this
point because it doesn’t contain any additional logic. For
this reason, here we’ve used the simple array and object
structure. The objects have the following properties: id,
title, author, isbn,
and rating. You can easily store the data
structure in the BooksList.jsx file outside the component
function, as shown in Listing 3.14:
import './BooksList.css';
const books = [
{
id: 1,
title: 'JavaScript—The Comprehensive Guide,'
author: 'Philip Ackermann',
isbn: '978-3836286299',
rating: 5,
},
{
id: 2,
title: 'Clean Code',
author: 'Robert Martin',
isbn: '978-0132350884',
rating: 2
},
{
id: 3
title: 'Design Patterns',
author: 'Erich Gamma',
isbn: '978-0201633610',
rating: 5,
},
];
function BooksList() {…}
export default BooksList;
Listing 3.14 Implementing the Data Structure (src/BooksList.jsx)
The data structure is only temporary, and you will later
replace it with dynamic data that you obtain from a web
interface. But first you need to make sure that the data is
displayed in the BooksList component. Listing 3.15 shows
how to use a combination of curly brackets and the
reference to books[0].title, for example, to display the title
of the first book. In this case, you only want to display the
first book; all other data records would simply be copies of
this structure, which you can solve much more elegantly
with a loop, thus saving yourself some error-prone typing.
import './BooksList.css';
const books = […];
function BooksList() {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
<tr>
<td>{books[0].title}</td>
<td>{books[0].author}</td>
<td>{books[0].isbn}</td>
<td>{books[0].rating}</td>
</tr>
</tbody>
</table>
);
}
export default BooksList;
Listing 3.15 Extension of the “BooksList” Component (src/BooksList.jsx)
If one of the expressions returns the undefined value—for
example, because you mistyped it—nothing will be
displayed at the corresponding position. This makes your
application more stable and fault-tolerant. The same goes
for the null or false values: these values won’t be displayed
either. However, this fault tolerance is only sufficient to
handle simple values; if you write an expression that causes
an exception (such as accessing a variable that doesn’t
exist), this will cause an exception in the browser, and your
application will terminate.
Comments in JSX
If you need a comment in your JSX structure, either to
comment out a functionality or to insert a note, you can
do this using the {/* ... */} string, where ... stands for
your comment. So you can use an ordinary JavaScript
multiline comment in a JSX expression for this purpose.
XSS Protection
Inserting dynamic content creates security risks,
especially when it comes to displaying user-generated
content. React has a built-in protection mechanism, which
prohibits the insertion of HTML. In Listing 3.16, you can
see how to insert custom HTML anyway:
function createHTML() {
return {
__html: '<button onclick=\'alert("hello world");\'>hello</button>',
};
}
function MyComponent() {
return <div dangerouslySetInnerHTML={createHTML()} />;
}
export default MyComponent;
Listing 3.16 Inserting HTML
You cannot paste the HTML code directly from a variable.
In that case, React would ensure that the HTML code is
escaped correctly and display the HTML code directly.
Instead, you need to set the dangerouslySetInnerHTML
attribute and call a function there; in the example, that's
createHTML. This function must return an object that has the
__html property. The result of the sample code is a button.
If you click the button, an Alert window will display, so the
embedded JavaScript will be executed. Be careful when
pasting HTML as it can cause serious security issues!
Currently, the list is still very tightly bound to the data
structure. We’ll change that in the next section.
3.4.2 Iterations: Loops in Components
In the current implementation, you display only the first
record from the books array in the list. This severely limits
the extensibility of the application. Another disadvantage of
such static structures is that you have to adjust your source
code in several places as soon as you want to insert
additional records, make minor changes, or insert a
completely different type of record.
You can solve the list display problem by means of a loop.
This means that you define the structure to be displayed
only once and display it individually for each data record
within the loop body. JSX doesn’t provide its own feature for
iteration, so instead you’ll want to use JavaScript
functionality. In this case, when displaying information from
an array, the map method is the one to use. You transform
the input array into an array of React elements and display
them:
import './BooksList.css';
const books = […];
function BooksList() {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>{book.rating}</td>
</tr>
))}
</tbody>
</table>
);
}
export default BooksList;
Listing 3.17 Iteration over the Book Records (src/BooksList.jsx)
The extension of the BooksList component is limited to the
source code inside the tbody tag. The books array you defined
earlier is the basis for your loop. You can use the map method
to transform the individual records into React elements (tr
elements in this case). Inside the callback function you pass
to the map method, you have access to the data record in the
form of the book object. In each iteration, you create td child
elements for title, author, ISBN, and rating within the tr
element. As a final extension, you add the key attribute to
the tr tag. This is required by React for internal referencing.
Iterations and Keys
If you omit the key attribute from such an iteration, you’ll
receive the following warning in the browser console:
Warning: Each child in an array or iterator should have a unique "key" prop.
Listing 3.18 Iteration without the “key” Attribute
React optimizes the processing of changes by redisplaying
only certain parts of the user interface. In the case of
iterations, unique keys are needed for this process as they
allow React to determine which elements have actually
changed. The value of the key attribute must be unique
within the iteration between elements on the same level.
The assignment of the key values should not change for
the elements.
The array of elements created via the map method is
automatically displayed correctly by React. If you switch to
the browser, you’ll now see all three data records instead of
just one.
In addition to iterations, conditions are a second important
control element for designing user interfaces.
3.4.3 Conditions in JSX
You can use conditions to render certain structures
depending on certain conditions. In the list display, one
possible condition you can check is whether the array you
want to iterate over contains any entries at all:
import './BooksList.css';
const books = […];
function BooksList() {
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>…</table>
);
}
}
export default BooksList;
Listing 3.19 Conditional Rendering of JSX Structures (src/Card.jsx)
In Listing 3.19, you take advantage of the fact that you can
treat JSX elements like JavaScript objects and keep them as
values of variables. Depending on whether the books array is
empty or not, you can either display the information that no
books were found or display the list of found books.
If, instead of an array with three elements, you assign only
an empty array to the books variable and then switch to the
browser, you’ll see the text No books found, as shown in
Figure 3.6.
Figure 3.6 Displaying an Empty Book List
In addition to the if statements for conditional rendering,
there are other ways to display structures under certain
conditions.
Condition Shortcut 1: Logical AND operator
Instead of the if condition, you can use the logical &&
operator to display elements. Whereas if allows you to
implement multiple branches via else if and else, the logical
&& permits only one branch. You use this branch in
Listing 3.20 to display a span element with the corresponding
number of stars when a rating is present:
import './BooksList.css';
const books = […];
function BooksList() {
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>
{book.rating && <span>{'*'.repeat(book.rating)}</span>}
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 3.20 Condition with the Logical AND Operator (src/BooksList.jsx)
As you can see in Listing 3.20, the condition is enclosed by
curly brackets. The first expression is cast to a Boolean
value. This ensures that the span element is displayed only if
the evaluation can be converted to a truth value.
Condition Shortcut 2: Ternary Operator
The disadvantage of using the AND operator is that it allows
you to use only one value and no alternatives. This problem
can be solved by the ternary operator. However, you should
only use this operator sparingly, and never in a nested form.
To disallow this antipattern, you can enable the ESLint rule
named no-nested-ternary. You typically use this operator less
for controlling structures, and more for displaying values
and their alternatives. In Listing 3.21, you can see that the
author is displayed if the operator is set; otherwise, the
Unknown string will display.
import './BooksList.css';
const books = […];
function BooksList() {
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author ? book.author : 'Unknown'}</td>
<td>{book.isbn}</td>
<td>
{book.rating && <span>{'*'.repeat(book.rating)}</span>}
</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 3.21 Displaying Values with the Ternary Operator (src/BooksList.jsx)
3.5 Props: Information Flow in an
Application
One of the most important concepts of React components is
the reusability of these structures. However, if a component
is static, as is currently the case with the map component,
reusability is not possible, or at least hardly possible. For
this reason, components can be parameterized. These
parameters are called props and are passed to a component
as attributes.
Quick Start
If you include a component as a tag in your JSX structure,
you can define attributes. The component can access
these attributes via a props object, which it receives as its
first argument. Often the individual props are extracted
directly via a destructuring statement. Listing 3.22 shows
a component that accepts a prop named person. The
component assumes that this is an object with the
displayName property and uses this value for the output.
function MyComponent({ person }) {
return <h1>Hello {person.displayName}!</h1>;
}
export default MyComponent;
Listing 3.22 Component with Props
When you include it, you must make sure (as in
Listing 3.23) that you provide the component with the
correct value:
<MyComponent person={{displayName: 'Jane Doe'}}/>
Listing 3.23 Transferring Props to a Component
As you can see, you are not limited to primitive data types
such as strings or numbers, but can pass any data
structures such as objects, arrays, or functions to a
component. Just make sure you don't forget the curly
brackets here.
You can access props via the argument of the function
component. Props are passed as an object whose properties
you can access. In the books list, the display of individual
data records is a very good candidate for a child component
that receives the information for display as a prop.
3.5.1 Props and Child Components
First, you extract the block you want to swap out to the child
component—that is, the tr element and its child elements—
into a separate component named BooksListItem which is
located in a file of the same name with the .jsx extension.
This component needs a book data record for display, which
you pass to it as a prop. You can either name the parameter
props, for example, and then access the information via
props.book, or you can use the more common variant with a
destructuring statement as in Listing 3.24, where you insert
a property of the object directly into a new variable:
function BooksListItem({ book }) {
return (
<tr>
<td>{book.title}</td>
<td>{book.author ? book.author : 'Unknown'}</td>
<td>{book.isbn}</td>
<td>{book.rating && <span>{'*'.repeat(book.rating)}</span>}</td>
</tr>
);
}
export default BooksListItem;
Listing 3.24 Child Component of the List (src/BooksListItem.jsx)
In the step that follows, you then include the child
component in the BooksList component. You can see what
this should look like for our example in Listing 3.25:
import './BooksList.css';
import BooksListItem from './BooksListItem';
const books = […];
function BooksList() {
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<BooksListItem key={book.id} book={book} />
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 3.25 Integration of the Child Component (src/BooksList.jsx)
The main differences from the original version of the
BooksList component are the import statement you use to
include the BooksListItem component, and its use within the
component's JSX structure as a <BooksListItem> tag. Also in
this case, the component you create in the loop needs a key
prop.
You also pass the book object to the child component. Here
you should make sure to not forget the curly brackets
around the object, as this is a regular JavaScript object and
React must interpret it as such.
Props can either be passed as a JSX string (in which case
they must be enclosed in quotes), or you can pass an
expression, as in our case; in the latter case, you must use
the curly brackets.
As you can see, the source code of the BooksList component
has become a bit more concise after the swap out, and this
is exactly one of the goals when you integrate child
components. A component should have only one purpose,
and you ideally swap out all other aspects to other,
specialized components.
The problem with using props is that they are JavaScript
structures and are not type-safe. This means that in the
example of the BooksListItem component, you can pass not
only an appropriately shaped object via the book prop, but
also the true value, for example. There are several ways to
solve this problem. A very lightweight approach is to use
PropTypes, while another option is to use TypeScript, which
you'll learn about in Chapter 7.
3.5.2 Type Safety with PropTypes
Since version 15.5 of React, PropTypes are no longer part of
React itself. To use this feature, you must first install the
package using the npm install prop-types command. After the
installation, you can define the structure of the props for
each of your components. You define PropTypes by defining a
propTypes property on the component. The value of this
property is an object. The keys are the names of the props,
and the values are the respective structure. Table 3.1
summarizes a selection of PropTypes.
PropType Meaning
PropTypes.array This PropType represents an
array.
PropTypes.bigint This type allows you to indicate
that you expect a BigInt.
PropTypes.bool This type enables you to
specify that a Boolean value
must be passed.
PropTypes.func A function must be passed with
this prop.
PropTypes.number The number type requires you to
pass a number.
PropTypes.object The object type represents any
object structure.
PropTypes.string The string type is used to
specify that a string is to be
passed.
PropType Meaning
PropTypes.symbol This type makes sure that a
JavaScript symbol must be
passed.
PropType.element This type represents anything
that can be rendered:
numbers, strings, elements, or
arrays of these types.
PropTypes.any Here, any stands for any type.
PropTypes.instanceOf(Class) You can use this PropType to
specify that the prop is an
instance of a particular class.
PropTypes.oneOf(['a', 'b']) This PropType allows you to
specify that the prop must
have one of the specified
values.
PropTypes.oneOfType([…]) This type works similar to
oneOf, except that here you
specify other types in an array.
PropTypes.arrayOf(…) Using arrayOf, you can specify
that the prop must consist of
an array with certain types.
Table 3.1 Different PropTypes
These types allow you to extend your BooksListItem
component to define the types of props to be passed:
import PropTypes from 'prop-types';
function BooksListItem({ book }) {…}
BooksListItem.propTypes = {
book: PropTypes.object.isRequired,
};
export default BooksListItem;
Listing 3.26 PropTypes for the “BooksListItem” Component
(src/BooksListItem.jsx)
Listing 3.26 shows how to define PropTypes. After you import
the PropTypes, you define the propTypes property on the
BooksListItem function as an object with the book property.
This is of type PropTypes. object—that is, an object structure.
The prop is mandatory for the component to work, so you
need to add another isRequired. If you omit this property, the
props will be optional. You shouldn’t use the object, array,
and any general types in favor of more specific types.
You’ll learn how to specify the data structure in a more
detailed manner in the next section. But before that, let's
look at the effects of PropTypes if you omit the book prop
completely, for example. In this case, you’ll receive a
warning in the browser in the developer tools. However, the
build process runs through without errors even if the
PropType definitions are violated. Figure 3.7 shows the error
message.
Figure 3.7 Error Message in Case of a PropTypes Violation
Defining Any Structures with Shapes
If the options for defining PropTypes presented so far are not
sufficient for you, you can use shapes to influence the types
in even more detail. To the shape type, you pass an object
that defines the structure and can use the different
PropTypes. Listing 3.27 shows an example of how to recreate
the Book type with PropTypes:
BooksListItem.propTypes = {
book: PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
isbn: PropTypes.string.isRequired,
rating: PropTypes.number.isRequired,
}).isRequired,
};
Listing 3.27 “shape” PropType (src/BooksListItem.jsx)
PropTypes use ordinary JavaScript objects. This means that
you can, for example, swap out the Book type to its own
variable if you want to use it in multiple places. In this way,
you achieve a good deal of reusability of such structures.
Until now, your components have been static and depend
on a fixed data structure. However, this changes in the
following step when you learn how to define the local state
in a component.
3.6 Local State
Quick Start
The state of a component indicates the status of the
component. If the state changes, React renders the
component again. You can create the state via the useState
function of the Hooks API:
const [state, setState] = useState(initialState );
Listing 3.28 Syntax of the State Hook
When you call the useState function, you pass the initial
structure of the state. The return value of the function is
an array whose first element you can use for read access
to the state. The second element, a function, enables you
to manipulate the state.
You can define multiple states within a function
component that work independently of each other.
If you update multiple states at once, React combines
these operations and renders the component only once.
In the current implementation, the data of the BooksList
component resides in an array outside the component. The
display works in this way, but this type of component state
has a crucial problem: if you modify one of the objects in the
array, such as a rating, the change won’t be displayed. The
reason is that React doesn’t know about the data structure
and changes. This design decision is mainly for performance
reasons: if you were to define the state using simple
objects, React would have to monitor them. With the
introduction of the Hooks API, functional components in
React have been provided with a way to manage their own
state. Before the integration of this feature, a separate state
was reserved for class components only.
The advantage of the state hook is that you can use
multiple specialized state fragments in your component and
name them accordingly. React optimizes the state changes
so that they only result in a redesign of the component. So,
for example, if you have three state parts in your
component and update all three, this will result in the
component being rendered once.
import { useState } from 'react';
import './BooksList.css';
import BooksListItem from './BooksListItem';
const initialBooks = […];
function BooksList() {
const [books, setBooks] = useState(initialBooks);
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<BooksListItem key={book.id} book={book} />
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 3.29 Using the “useState” Function in the Component
(src/BooksList.jsx)
The customization of the existing BooksList component
consists of first importing the useState function and
renaming the books variable to initialBooks. The data
structure remains unchanged and represents the initial
value of the component state. In the component, you call
the useState function and pass it this value. As a return
value, you get an array with two elements. At this point, you
use a destructuring statement to assign the two elements to
separate variables named books and setBooks.
The first element enables read access to the state. Because
this variable has the same name as the original variable,
you do not need to adjust anything in the component itself.
You can modify the state via the second element, a function
you have named setBooks here. You can change the data
structure of the first element as well, but this does not result
in any update in the rendering because React doesn’t
register these changes. The only way to change the state
and the display in the browser is to use the setter function.
3.7 Event Binding: Responding to
User Interactions
Quick Start
You can use the on<Eventname> props to respond to events.
React defines such props for all browser events.
<button onClick={(event) => console.log(event)}>click me</button>
Listing 3.30 Click Handler in the JSX Code of a Component
When the button element is clicked, React executes the
specified function and passes an object representation of
the event to the function. For example, because this
function is executed in the context of the component, you
can adjust the state within this callback function using the
setter function.
Besides onClick, there are numerous other event handler
props, such as onMouseOver or onSelect.
Until now, you’ve presented a static application to your
users that is capable of displaying dynamic content and that
contains reusable components; however, no interaction is
possible yet. But in single-page applications, such as the
ones you typically implement using React, a user interaction
with the application is one of the key elements. The most
obvious interaction option for the books list is to adjust the
rating of each data record.
3.7.1 Responding to Events
React provides you with several props that you can use to
respond to events. The most commonly used variant is
probably onClick, which allows you to respond to user clicks
on specific elements.
In the books list, you allow your users to rate the books.
Because the rating is a separate feature, it makes sense to
swap out this functionality to a separate subcomponent. For
this purpose, you want to define a new component named
Rating and create a new file named Rating.jsx in the src
directory for it. The component receives two props. The first
is a data record, which must have at least an id and a rating.
Also, the component expects an onRate prop, which is a
callback function that the component executes when a star
is clicked. Second, the component calls the function with the
id of the data record and the new rating value. With this
structure, you have created a rating component that is
largely independent of your book data structure, so you can
reuse it elsewhere in your application or even in another
application.
Before we get to implementing the rating component, you
need to use the npm install @mui/icons-material @emotion/styled
command to install a set of icons that you can use in your
application. This will make the display of the rating
component more visually appealing.
import PropTypes from 'prop-types';
import { StarBorder, Star } from '@mui/icons-material';
import './Rating.css';
function Rating({ item, onRate }) {
const ratings = [];
for (let i = 1; i <= 5; i++) {
ratings.push(
<button
onClick={() => onRate(item.id, i)}
className="ratingButton"
key={i}
>
{item.rating < i ? <StarBorder /> : <Star />}
</button>
);
}
return ratings;
}
Rating.propTypes = {
item: PropTypes.shape({
id: PropTypes.number.isRequired,
rating: PropTypes.number.isRequired,
}).isRequired,
onRate: PropTypes.func.isRequired,
};
export default Rating;
Listing 3.31 Implementation of the Rating Component (src/Rating.jsx)
Material UI
Material UI is a collection of components for React that
implements Google's Material Design. It provides a variety
of basic components, such as buttons, text fields, or
dropdown fields. However, there are also more extensive
components, such as menus or dialogs. What they all
have in common is that they’re available to you as
components that you can import and integrate into your
application. You usually influence the behavior of the
components via props that you pass to the components.
You can learn more about component libraries and
Material UI in Chapter 11.
The rating component receives as props the item—in this
case, a book data record and a callback function that allows
the parent component to handle a new rating. The
component itself consists of an array of elements that you
fill via a loop and then return. Here you can see an
exception to the rule that a component must return exactly
one root element: you can also return an array of elements.
However, make sure here that each element has a key prop.
The elements you create here are ordinary button elements
whose click handlers call the onRate function with the id of
the record and the rating value. In the display, you decide
whether it should be a filled or empty star based on the
rating value of the data record and the position in the array.
If you were to display the component this way, the stars
would be surrounded by the browser's default style for
buttons. You can solve this problem by defining a stylesheet
for the component, saving it in a file named Rating.css, and
importing that file into the component. However, because
the styles apply to the entire application, you define a
ratingButton CSS class for the buttons, which you assign
using the className prop. In the stylesheet, you can then use
the class selector to remove the background and border for
just those buttons. You can see the corresponding stylesheet
in Listing 3.32:
.ratingButton {
border: none;
background: none;
}
Listing 3.32 Styling for the Rating Component (src/Rating.css)
The next step is to save the change to the rating in the state
of the parent component. For this purpose, the first step is
to customize the BooksListItem component as shown in
Listing 3.33 by passing the onRate function from the BooksList
component to the Rating component:
import PropTypes from 'prop-types';
import Rating from './Rating';
function BooksListItem({ book, onRate }) {
return (
<tr>
<td>{book.title}</td>
<td>{book.author ? book.author : 'Unknown'}</td>
<td>{book.isbn}</td>
<td>
<Rating item={book} onRate={onRate} />
</td>
</tr>
);
}
BooksListItem.propTypes = {
book: PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
isbn: PropTypes.string.isRequired,
rating: PropTypes.number.isRequired,
}).isRequired,
onRate: PropTypes.func.isRequired,
};
export default BooksListItem;
Listing 3.33 Rating Functionality in the “BooksListItem” Component
(src/BooksListItem.jsx)
The BooksList component is responsible for the books and
manages the state, which means that this is also the right
place to modify the data records. So here you make sure
that the rating of a book is adjusted as soon as the users
adjust the rating via the Rating component:
import { useState } from 'react';
import './BooksList.css';
import BooksListItem from './BooksListItem';
const initialBooks = […];
function BooksList() {
const [books, setBooks] = useState(initialBooks);
function handleRate(id, rating) {
setBooks((prevState) => {
return prevState.map((book) => {
if (book.id === id) {
book.rating = rating;
}
return book;
});
});
}
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<BooksListItem key={book.id} book={book} onRate={handleRate} />
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 3.34 Modifying the Component State for an Interaction
(src/BooksList.jsx)
In Listing 3.34, you can see that you’re allowed to define
and use any functions within a component. In this context,
however, you should note that these functions are recreated
with every single rendering process. In this example, that
isn’t a problem because it’s only one function in one central
component. In Chapter 6, you’ll learn about useCallback and
useMemo, features of React that allow you to avoid this
creation of functions under certain conditions.
You pass the handleRate function to the BooksListItem
component in the onRate prop. The naming of the handler
function and the prop follows a common naming convention
that clarifies that the function is a handler function and that
the prop accepts a callback function that is called on a
specific event.
In the handler function, you use the state's setter function,
setBooks, to manipulate the state. You can call this function
in two ways: you can either pass a new state object that
overwrites the state, or you can pass a callback function, as
in this example. In this callback function, you have access to
the current version of the state via the argument and create
a new version on this basis, which you then return. The
variant with the callback function is the safe variant when it
comes to asynchronous operations: here you can be sure
that you will never lose information from the state because
you always work on the current version.
In the rating example, you use the map method of the array
that is in the state and set the new rating value on the
record that is to be modified. With these changes, you can
now switch to the browser and change the ratings of the
three existing data records. The result should look like the
example shown in Figure 3.8.
There’s another variant that allows you to implement the
rating of your records. We’ll come to it in the next section.
Figure 3.8 Books List with Rating Option
3.7.2 Using Event Objects
The event handlers in React allow you to access an object
that represents the event in question. However, this is not
the native browser implementation, but a lightweight
wrapper that eliminates the idiosyncrasies of the different
browsers. With version 17 of React, the team has removed
one optimization in event objects: event pooling.
Event Pooling
React has long had an optimization called event pooling. It
provided that the event objects were not created
completely from scratch in each event handler, but that a
manageable pool of objects was used. This optimization
was supposed to lead to noticeable performance
improvements, but that wasn’t the case. In addition, event
pooling caused confusion for developers because
accessing the event object within the handler function
wasn’t always possible. This is especially true for
asynchronous operations, such as when there is a
response from a web interface and you need information
from the event object for a comparison, for example. The
reason for this is that React reset the event object after
the initial run of the handler function. With React 17, this
feature has been completely removed. Only the so-called
synthetic event has remained, but it’s available to you at
any time in the handler.
For the implementation of the access to the event object,
you can use the example of the book rating. In the Rating
component, you can remove the event handling completely
and add a data-value attribute with the respective index to
the button element. Also remember to remove the prop and
PropTypes structure as well when doing such conversions:
import PropTypes from 'prop-types';
import { StarBorder, Star } from '@mui/icons-material';
import './Rating.css';
function Rating({ item }) {
const ratings = [];
for (let i = 1; i <= 5; i++) {
ratings.push(
<button className="ratingButton" data-value={i} key={i}>
{item.rating < i ? <StarBorder /> : <Star />}
</button>
);
}
return ratings;
}
Rating.propTypes = {
item: PropTypes.shape({
id: PropTypes.number.isRequired,
rating: PropTypes.number.isRequired,
}).isRequired,
};
export default Rating;
Listing 3.35 Moving Event Handling out of the Rating Component
(src/Rating.jsx)
You move the logic for event handling from the Rating
component to the parent BooksListItem component. In
Listing 3.36, you can see the customized source code of the
BooksListItem component:
import PropTypes from 'prop-types';
import Rating from './Rating';
function BooksListItem({ book, onRate }) {
function handleRate(event) {
const rating = event.target.closest('[data-value]')?.dataset.value;
if (rating) {
onRate(book.id, parseInt(rating, 10));
}
}
return (
<tr>
<td>{book.title}</td>
<td>{book.author ? book.author : 'Unknown'}</td>
<td>{book.isbn}</td>
<td onClick={handleRate}>
<Rating item={book} />
</td>
</tr>
);
}
BooksListItem.propTypes = {…};
export default BooksListItem;
Listing 3.36 Event Handling with Access to the Event Object
(src/BooksListItem.jsx)
In the BooksListItem component, you first define the function
that will handle the event. The parameter list consists of the
reference to the event object. This event object provides
access to the target property, which contains the element
that was clicked. The rating value is in the data-value
attribute. However, you don’t know on which element
exactly the click event was triggered: whether on the svg
element of the icon, the button, or the table cell.
You can solve this problem using the closest method. It finds
the closest element to which the passed selector applies.
When you use this call, it can happen that no element is
found. This is the case when you click on the td element. If
that happens, you cannot access dataset.value and your
browser throws an exception. You can use the optional-
chaining operator (.?) to intercept this problem and assign
the undefined value to the rating variable instead. After that,
you need to check if rating has a valid value, and then you
can call the onRate function that was passed via the props.
Note that the value of data-value is interpreted as a string.
So if you need a number, you have to take care of the
conversion—using the parseInt function, for example.
This conversion saves quite a few callback functions by
dragging the event handler in the tree closer to the
component responsible for the state. Whether you choose
this option or the previous one is up to you. The only
important thing is that you remain as consistent as possible
in your implementation and solve similar problems with the
same approach. This is important because there are usually
multiple solution options in React, and you will improve the
readability and understandability of your code.
3.8 Immutability
If you modify the state of a React component, the display in
the browser may not update. This has to do with the fact
that you have modified a deep object structure, but React
detects a change only at the top level. For this reason, in
the BooksList component, you also used the map method in
the handleRate function, which doesn’t modify the original
array, but works on a copy. You can reproduce the issue by
slightly adjusting the handleRating function in the BooksList
component. Listing 3.37 shows the new structure. For
comparison, we’ve also included the original variant in a
comment.
function handleRate(id, rating) {
setBooks((prevState) => {
const index = prevState.findIndex((book) => book.id === id);
prevState[index].rating = rating;
return prevState;
/* original implementation
return prevState.map((book) => {
if (book.id === id) {
book.rating = rating;
}
return book;
});
*/
});
}
Listing 3.37 Direct Modification of the State that Does Not Cause Any Re-
rendering (src/BooksList.jsx)
In this customized variant, you search for the index of the
record you want to modify. You then access the state
structure via this index, change the value, and return the
state object. If you set a breakpoint in the code before the
return, you will see in the debugger that the rating value
has been set correctly. The problem here is that React does
not recognize the change.
But even the solution with the map method is not perfect.
React does recognize the change because you are returning
a copy of the state object and not the original. But during
the operation, you also modified the original. The reason is
that you iterate over objects and you manipulate these
objects, which also affects the entries of the original array.
In general, you should always be careful with nested object
structures. You’ll often find expressions like {...myObj} for
objects or [...myArr] to copy the corresponding structures.
However, these operations only create a flat copy; that is,
they only copy the top object or the top array, not
references at lower levels.
If you are aware of this, it isn’t a problem in most cases. But
such operations can lead to unwanted side effects. For this
reason, you should always use so-called immutable data
structures in such cases. This means that you do not modify
the original data structure, but always create a deep copy of
it. Creating such a deep copy can require some effort. For
this reason, there are several libraries that deal with this.
Examples include immutability-helper, Immer, or
Immutable.js. The libraries take different approaches and
range from a simple solution like immutability-helper that
supports a function and a defined set of operations, to a
flexible library like Immer that uses JavaScript proxies, to
comprehensive solutions that provide custom data types for
immutability. In the following sections, I’ll show you Immer
as a possible solution in use in a React application.
3.8.1 Immer in a React Application
Immer is an independent library that you can use both in
the frontend and in Node.js in the backend. Before you can
use Immer, you must install it using the npm install immer
command. The core of Immer is the produce function. You
pass the object to it on which you want to perform an
operation and a callback function. The return value of
produce is the modified copy of the object. In the callback
function, you have access to the object and can work with it.
For example, you can assign a new value to a property, and
Immer makes sure that this operation doesn’t change the
original object.
In Listing 3.38, you use the produce function of Immer to
repair the code from the previous example, which modified
the state directly and did not change the display.
import { useState } from 'react';
import produce from 'immer';
import './BooksList.css';
import BooksListItem from './BooksListItem';
const initialBooks = […];
function BooksList() {
const [books, setBooks] = useState(initialBooks);
function handleRate(id, rating) {
setBooks((prevState) => {
return produce(prevState, (draftState) => {
const index = draftState.findIndex((book) => book.id === id);
draftState[index].rating = rating;
});
});
}
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<BooksListItem key={book.id} book={book} onRate={handleRate} />
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 3.38 Using Immer in the “BooksList” Component (src/BooksList.jsx)
The handleRate function remains almost unchanged, except
for one small modification. You pass the current state, which
is in the prevState variable, to the produce function. In the
callback function, you look for the index of the record in the
draftState variable and then change the rating value. Again,
you can use the debugger and breakpoints and you will
notice that the original state is not changed. You can use
Immer in such error-prone situations or whenever you
manipulate the state. In Listing 3.39, you can see the
handleRate function in the original version in combination
with Immer:
function handleRate(id, rating) {
setBooks((prevState) => {
return produce(prevState, (draftState) => {
draftState.map((book) => {
if (book.id === id) {
book.rating = rating;
}
return book;
});
});
});
}
Listing 3.39 Using Immer in the Original “handleRate” Function
(src/BooksList.jsx)
You can see in this example that the source code of the
function gets a bit more extensive and also slightly more
difficult to read. However, the advantage of this
implementation is that you can exclude the modification of
the original structure.
3.9 Summary
In this chapter, you got to know the basic principles of
React:
Components form the building blocks of a React
application.
Function components represent the more modern,
lightweight, and flexible variant of components.
A function component returns a JSX structure that takes
care of rendering in the browser.
Conditions allow you to display certain parts of the
structure depending on whether a certain condition is
true. You can implement conditions with if statements,
the ternary operator (?:), or even a logical AND.
Because JSX is a syntax extension of JavaScript, you can
implement loops using the map method of arrays, for
example.
Props let you pass information to a component via its
attributes.
allow you to secure the types of props of a
PropTypes
component.
You can register event handlers with attributes like
onClick.
You can map the state of a component via the state hook
using the useState function. The first element of the return
value allows you to read the state; the second element
allows you to modify the state.
You pass the initial state to the useState function when you
call it.
If you pass a function to the setter function of the state
hook, you can ensure that you are using the state that is
valid at the time of the call.
React relies heavily on immutable data structures for
change detection. For this purpose, you can either
manually create copies of data structures and execute the
changes on them, or use established libraries such as
immutability-helper, Immer, or Immutable.js.
In the next chapter, you’ll learn about other aspects of
components, such as their lifecycle, and you’ll learn how to
communicate with a server interface.
4 A Look Behind the Scenes:
Further Topics
With a component hierarchy and the data flow between the
parent and child components with props and with the help
of communication via functions that you pass from the
parent component to the child component through props,
numerous cases can already be covered. However,
numerous other patterns have become established in React,
which you can use to structure your React application in
such a way that it still remains manageable even with a
larger range of functions.
However, before we get into the actual patterns, you’ll first
learn about the lifecycle of a component.
4.1 The Lifecycle of a Component
A component in an application goes through a lifecycle that
you can divide into the following three stages:
Mount
The first step in the life of a component is that it is hooked
into the component tree and thus rendered. During this
stage of the lifecycle, you mostly perform initialization
tasks.
Update
Very few components are purely static. Most of the time
they contain dynamic information that can change during
the lifetime of the component. Such a change entails an
update of the component. Those updates can occur more
frequently and sometimes unintentionally.
Unmount
The last stage in the lifecycle of a component is the
unhooking of the component from the component tree.
You have the option to execute logic at this point as well.
Most often, you use this opportunity to free up resources
that the component has requested.
The lifecycle of function components differs from that of
class components. In this chapter, we focus on function
components. You’ll learn more about class components and
their lifecycle in Chapter 5.
4.2 The Lifecycle of a Function
Component with the Effect Hook
Quick Start
The useEffect function allows you to intervene in the
various stages of a component’s lifecycle. The useEffect
function accepts a callback function and an optional array
as arguments. The syntax looks as follows:
useEffect(effectFunction, dependencies)
This function covers all three stages of the lifecycle:
Mount
You pass a callback function which is supposed to be
executed on mount. You also pass an empty array as
the second argument.
Update
Besides the callback function, you either pass no
second argument, in which case the callback is
executed on each update; or you specify an array, and
then the function will be executed only if one of the
specified dependencies has changed.
Unmount
You can respond to the unhooking of your component
from the component tree by again returning a function
from the callback function. This function gets executed
upon the removal of the component.
So far, you only know the component function itself, but you
haven’t yet intervened in the lifecycle of the component, at
least not consciously. The component function executes
React as the first step in the lifecycle. Here, however, you
should refrain from using any side effect as React is capable
of interrupting the render process, resuming it later, or
canceling it entirely. Examples of a side effect include
operations that aren’t directly related to rendering the
component, such as communicating with a web server to
load data or setting a timeout or interval. If you were to
place such a side effect in the component function directly
and the render process were to be aborted and restarted,
then the side effect would be executed a second time, which
in the best case does no further damage, but can also cause
serious problems.
In the following sections, you’ll learn about the different
stages of the component lifecycle and how you can
intervene there using the effect hook.
4.2.1 Mount: The Mounting of a Component
Now that you know that you aren’t allowed to trigger side
effects directly in the component function, you need a place
where you are allowed to do so. As an example, we want to
use the books list from the previous example again, but in a
much simpler variant. The component manages its own
state and stores the data records there as an array. Initially
you start with an empty array. Once the component has
been loaded, you fill the state with data. Listing 4.1 shows
the code of the component:
import { useState } from 'react';
import './BooksList.css';
import { useEffect } from 'react';
function BooksList() {
const [books, setBooks] = useState([]);
useEffect(() => {
setTimeout(() => {
setBooks([
{
id: 1,
title: 'JavaScript - The Comprehensive Guide',
author: 'Philip Ackermann',
isbn: '978-3836286299',
rating: 5,
},
]);
}, 2000);
}, []);
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 4.1 Asynchronous Filling of the Component State during Mounting
(src/BooksList.jsx)
The BooksList component renders the empty array from the
initial state in the first step. This means you first get a view
in the browser like the one shown in Figure 4.1.
Figure 4.1 Initial Representation of the “BooksList” Component
The new aspect in the component is the call of the useEffect
function. Note at this point that the function is called with
two arguments. The first one is a callback function and the
second one is an empty array. The empty array plays a
significant role here. It specifies that the callback function
you pass as the first argument is executed only once when
the component is mounted. If you omit this array, React
executes the callback function on every update. You will see
what the consequences are in the next section.
Inside the callback function you pass to useEffect, you first
set a timeout of two seconds. This is just to help you see the
effect of the mount hook. In the timeout function, you
overwrite the current state of the component with a new
array containing a data record that is displayed afterward by
your component. After these two seconds, you’ll see the
final display in the browser, as shown in Figure 4.2.
Figure 4.2 Updated Display after the Mount Hook
Usually, the time span between these two views is relatively
short. But you should be aware that your users will see both
variants. You can take advantage of this fact and, for
example, display a loading indicator or something similar to
inform your users that the data will be displayed soon. In
the best case, they won’t notice the loading indicator. But if
the operation takes a little longer, the users will be informed
that an action is still being performed in the background.
Another aspect of the effect hook when mounting the
component is that the component function is executed twice
in this case. The first time the component is rendered with a
message that there is no data, and then the second time
React displays the record. Normally, such component
functions are lightweight, so this won’t matter, especially if
you avoid side effects. Nevertheless, you should be aware
that the function is called significantly more often than just
once.
4.2.2 Update: Updating the Component
As soon as the state of a component changes, React
redraws it. In this case, the component enters the update
stage of its lifecycle. The effect hook enables you to
intervene here. This option becomes relevant, for example,
when you need to react to updates of the state. State
updates are asynchronous in React. So you can't attach
logic in the sense of “execute this function after the state
has been updated” directly to it. And this is where the effect
hook comes into play. The code in Listing 4.2 illustrates this:
import { useState } from 'react';
import './BooksList.css';
import { useEffect } from 'react';
function BooksList() {
const [books, setBooks] = useState([]);
useEffect(() => {
setTimeout(() => {
setBooks([
{
id: 1,
title: 'JavaScript - The Comprehensive Guide',
author: 'Philip Ackermann',
isbn: '978-3836286299',
rating: 5,
},
]);
}, 2000);
}, []);
useEffect(() => {
console.log('Elements in the state: ', books.length);
console.log(
'Table rows: ',
document.querySelectorAll('tbody tr').length
);
});
if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 4.2 Responding to Updates in the Component (src/BooksList.jsx)
In this example, you can see that React allows you to make
more than one useEffect call in a component. The first
useEffect takes care of mounting the component, so it is
executed exactly once. On the second call of useEffect, you
omit the second argument. This causes React to execute the
callback function on every render operation. So the output
in the browser console is as shown in Listing 4.3:
Elements in the state: 0
Table rows: 0
Elements in the state: 0
Table rows: 0
Elements in the state: 1
Table rows: 1
Listing 4.3 Output of the Effect Hook
The Effect function is therefore executed exactly three
times:
The first time is the initial representation. Each update
function is also executed initially.
The second output block is from StrictMode. In strict mode,
React renders all components twice to detect potential
issues. However, this only happens in the development
process. You can disable this behavior by removing the
React.StrictMode component in the index.jsx file. But doing
so takes away React's ability to warn you about potential
issues.
Finally, the third output occurs after the state update that
you perform in the first call of useEffect. As you can see,
React performs the Effect function after the state and
DOM updates. So you can be sure that the display is
already updated as soon as the function is executed.
Warning: Avoid Direct DOM Access
Please do not try to interact with the DOM directly. The
use of document.querySelectorAll is for demonstration
purposes only. In most cases, accessing the DOM indicates
an antipattern in your application that you should be
cautious about. React works declaratively, so you describe
the target state of your application. Direct DOM
operations fall into the realm of imperative programming.
Selective Updates
You can control the execution of the Effect function with the
second argument, the dependency array. This is shown in
the example in Listing 4.4:
import { useState, useEffect } from 'react';
function MyComponent() {
const [state1, setState1] = useState(0);
const [state2, setState2] = useState(1);
useEffect(() => {
setTimeout(() => setState1(1), 1000);
setTimeout(() => setState2(2), 2000);
}, []);
useEffect(() => {
console.log('State1 changed: ' + state1 + ' state2: ' + state2);
}, [state1]);
useEffect(() => {
console.log('State2 changed: ' + state2 + ' state1: ' + state1);
}, [state2]);
return <div>MyComponent works</div>;
}
export default MyComponent;
Listing 4.4 Demonstration of Various State Changes (src/MyComponent.jsx)
This example is somewhat contrived, but it nicely illustrates
the implications of the second argument of the useEffect
function. The component has two substates named state1
and state2 and three calls of the useEffect function. In the
first useEffect, you set a timeout for each change of state1
and state2. The second useEffect takes care of the change of
state1, and the third useEffect is triggered when state2 is
changed.
The output you get consists of a total of six lines in the
browser. The first four are from the initial mount of the
component as both useEffects for the state changes are also
executed initially. In addition, StrictMode takes effect here
again, which provides for a doubling of the output. After one
second, state1 is changed, which triggers the useEffect
function with state1 as a dependency. After another second,
state2 is changed, which triggers the final useEffect function.
In the console where your build process is running, you’ll
see the following warning messages:
WARNING in src/MyComponent.jsx
Line 14:6: React Hook useEffect has a missing dependency: 'state2'. Either
include it or remove the dependency array react-hooks/exhaustive-deps
Line 18:6: React Hook useEffect has a missing dependency: 'state1'. Either
include it or remove the dependency array react-hooks/exhaustive-deps
Listing 4.5 Dependency Warnings in the Console
You get these warnings when React detects that you are
accessing external values in your useEffect callback but you
haven’t listed them as a dependency. This means that your
effect relies on external structures but doesn’t automatically
execute when they change, indicating a potential source of
error.
4.2.3 Unmount: Tidying Up at the End of the
Lifecycle
Only in rare cases do you need the unmount stage in the
lifecycle of a component. Typically, this is only the case if
you requested a resource when mounting the component.
Examples of such resources are data streams like
WebSockets, but also intervals—that is, callback functions
that are executed regularly via setInterval. Listing 4.6 shows
an example that contains an interval:
import { useState, useEffect, useRef } from 'react';
function MyComponent() {
const [show, setShow] = useState(true);
useEffect(() => {
setTimeout(() => setShow(false), 5000);
});
return show ? <Child /> : <div />;
}
export default MyComponent;
function Child() {
const intervalRef = useRef(null);
const [time, setTime] = useState(0);
useEffect(() => {
console.log('Mount');
intervalRef.current = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 1000);
return () => {
console.log('Unmount');
clearInterval(intervalRef.current);
};
}, []);
return <div>{time}</div>;
}
Listing 4.6 Deleting an Interval when Unmounting (src/MyComponent.jsx)
The example consists of two components. The outer
component named MyComponent displays the child component
and ensures that it’s removed after five seconds. You can do
this by creating a new state with the value true. In an effect
hook, you set a timeout in the callback function of which
you change the state to the value false after five seconds.
You use this value during rendering to decide whether to
display the child component or an empty div element.
The child component, Child, also has its own local state
where you store the number of seconds that have passed
since mounting. In the callback function of the useEffect call,
you first output the mount string on the console, then define
an interval that increments the state value by one every
second. Again you define a function as the return value of
the callback function. Here you first output the Unmount string
to the console and then break the interval with the
clearInterval function.
The useRef function is a prequel to Chapter 6, where you'll
learn even more about the Hooks API of React. At this point,
let's just say that the useRef hook can store an unchanging
value over multiple render cycles. This allows you to ensure
that you always have access to the same interval identifier.
When you integrate the component and switch to your
browser, you’ll see a number in the display that increases
by 1 every second starting from 0 until the value 4 is
reached. After that, the number is hidden again. If you open
the console in the browser, you’ll see the following output
there:
Mount MyComponent.jsx:26
Unmount MyComponent.jsx:20
Mount MyComponent.jsx:26
Unmount MyComponent.jsx:20
Listing 4.7 Console Output of the Unmount Example
The double output of mount and unmount is again from
StrictMode, where React double mounts the component as a
test. If you observe the console, you will notice that the last
unmount is output only after a delay of five seconds—that is,
when the Cleanup function of the effect hook is executed.
If you don’t implement the unmount logic, the interval
continues to run in the background after the component is
unmounted. Depending on how often you use the
component in your application, this can lead to performance
issues.
Note
Whenever you request a resource, you must also release
it again!
Cleanup
In a way, handling the unhooking of a component is just a
special case of the cleanup routine of the effect hook. The
function you return in the callback function to the useEffect
function is executed before each new execution of the
callback function to perform cleanup operations. An
example of this is shown in Listing 4.8. The timer
component shows the number of seconds since it was
mounted. You can also reset the counter via a button.
import { useState, useRef, useEffect } from 'react';
function Timer() {
const [time, setTime] = useState(0);
const [reset, setReset] = useState(null);
const intervalRef = useRef(null);
useEffect(() => {
console.log('useEffect');
setTime(0);
intervalRef.current = setInterval(() => {
setTime((prevTime) => prevTime + 1);
}, 1000);
return () => {
console.log('clearInterval');
clearInterval(intervalRef.current);
};
}, [reset]);
return (
<div>
<div>{time}</div>
<button onClick={() => setReset(Math.random())}>reset</button>
</div>
);
}
export default Timer;
Listing 4.8 Timer Component with Cleanup Routine (src/Timer.jsx)
For the component, you need two states:
In one of them, you store the number of seconds that
have passed. You display this value when you render the
component.
The second state is used to control the resetting of the
timer. Because the timer again uses an interval, you need
a Ref object, which you create using the useRef function.
The core element of the timer is the call of the useEffect
function. In the callback function, you first create a console
output and then set the timer to the value 0. Then you start
the interval that increases the time value by 1 every second.
As a return value, you define a function that also generates
a console output and cancels the interval. As a dependency
of the effect hook, you set the reset state.
In this way, you have created an effect that will be executed
every time reset gets updated. When mounting the
component, React executes the callback function for the
first time, starting the timer. By clicking on the button, you
update the reset state with a random value. The effect
callback will then be executed again. This cancels the
interval and restarts the timer.
So you have now also become acquainted with an example
of a cleanup routine with the effect hook. In the next
section, we’ll look at an operation you’ll need very often in a
React application: loading data from a server interface.
4.3 Server Communication
After the description of the component lifecycle, it’s time to
return to our running example, the books list, which we’ll
use to describe the server communication.
4.3.1 Server Implementation
At this point, you access a server interface in read-only
mode. The server provides the data for the list and has no
logic itself. For this reason, you can use a simple server
implementation such as the json-server NPM package. You
can install this package in your application using the npm
install json-server command. In addition to the installation,
you need a JSON file to store the data, which is the basis for
the server. An example of such a file is shown in Listing 4.9.
Save this file in the root directory of your application under
the name data.json.
{
"books": [
{
"id": 1,
"title": "JavaScript - The Comprehensive Guide",
"author": "Philip Ackermann",
"isbn": "978-3836286299",
"rating": 5
},
{
"id": 2,
"title": "Clean Code",
"author": "Robert Martin",
"isbn": "978-0132350884",
"rating": 4
},
{
"id": 3
"title": "Design Patterns",
"author": "Erich Gamma",
"isbn": "978-0201633610",
"rating": 5
}
]
}
Listing 4.9 Server Data (data.json)
This file enables you to start the server using the npx json-
server -p 3001 -w data.json command. You can then access the
data via http://localhost:3001/books. You can specify the
port with the -p option. The default port of the json-server is
3000; to avoid conflicts with the React dev server, you want
to use port 3001 in this case. The -w option specifies that the
file specified will be opened in watch mode and changes will
take effect automatically.
In the console output, you should check if the books resource
is listed as shown in Figure 4.3. If it isn’t, then there’s a
problem and the server does not deliver the data correctly.
In this case, check the file and see if you have specified the
correct path.
If you look into the file, you’ll see an object structure in the
top level. The keys determine the paths; in the example,
that’s the books key. Below this key you’ll find an array of
objects. You can access the objects all together, but also
individually. When you open this address in the browser,
you’ll see a result like the one shown in Figure 4.4.
Figure 4.3 Output of “json-server”
Figure 4.4 Accessing the Interface of “json-server” in the Browser
If you run the server on your system, you can customize
your application to load and display the data from the
server.
4.3.2 Server Communication via the Fetch API
Communicating with a web server is a classic side effect in a
React application. For this reason, you place these requests
not directly in the component function, but in an effect
hook.
import { useState } from 'react';
import './BooksList.css';
import { useEffect } from 'react';
function BooksList() {
const [books, setBooks] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
fetch('http://localhost:3001/books')
.then((response) => {
if (!response.ok) {
throw new Error('Request failed');
}
response.json();
})
.then((data) => {
setBooks(data);
})
.catch((error) => setError(error));
}, []);
if (error !== null) {
return <div>An error has occurred: {error.message}</div>;
} else if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 4.10 Loading the Books Data from the Server (src/BooksList.jsx)
The BooksList component has the books state, where you
store the data for display. You also define an additional error
state, which serves as a repository for any error messages
that may occur.
You call the useEffect function with a callback function and
an empty dependency array so that the data is loaded only
when the component is mounted. If you forget this second
parameter, you won’t notice it at first. But when you open
the developer tools of your browser and look at the outgoing
network requests, you’ll notice that not just one request is
sent, but hundreds of them in no time. That’s because in
this case you start a server request, and when it responds
you update the state, which in turn triggers the Effect
callback, and so on, so you’re in an infinite loop. For this
reason, you should look at the developer tools every now
and then so that you’ll notice any unusual behavior of your
application during development.
Inside the Effect callback, you call the fetch function of the
browser with the address of the server interface. The result
is a Promise object that maps the asynchronous operation.
You can use the then method of this object to access the
response object of the request. There you can also check if
everything was OK with the response by checking the ok
property of the response object. If it contains the value false,
you throw an exception in our case, which you’ll handle
later. If the response doesn’t contain any error, you can
process the response in the next step. You must keep in
mind that HTTP is a streaming protocol, where the body of
the response can be transmitted in several parts from the
server to the browser.
For this reason, you must also consume the body separately.
The browser’s Fetch API provides you with the json method
for this purpose, which is also asynchronous. It allows you to
decode the response and interpret it as a JSON object. The
resulting Promise object then contains the server's
response, which you write to the component's state. If the
request was successful, you’ll see the book list in your
browser.
Error Handling
What happens if the server doesn’t respond or you mistyped
the interface URL? If you don’t provide for these cases, the
browser will throw an exception and stops running your
application. For this reason, the source code from
Listing 4.10 handles these types of errors at a general level.
To the chain of calls of the then methods of the Promise
objects, you add the call of the catch method. If an error
occurs when requesting the server or decoding the
response, the browser executes the callback function of the
catch method and writes the error object to the error state of
the component.
To display the error, you must check that the error state no
longer has a value of null when you render the component,
and then display the message property of the error object. The
type and scope of information you display to your users in
the event of an error is entirely up to you. You should
generally use error information sparingly or be careful with
the content of error messages so as not to reveal too much
to potential attackers on your application.
You can test whether your error-handling routine works by
terminating the server process or specifying an invalid path
such as booksX. In both cases, your application does not
render the list, but displays the corresponding error
message.
Using “async”/“await”
When using promises, as in the example case with server
requests, you often use the async/await feature of the
browser. It allows you to use promises, but without callback
functions. This means you can streamline your source code
and make it easier to read. The key is to add the async
keyword to the function in which you use promises. Then
you can prefix each asynchronous operation with the await
keyword and get the promise value directly. The browser
takes care of waiting for the result and doesn’t block the
rest of your application either.
What you also need to know at this point is that an async
function always returns a promise object. This fact makes
integration with the Effect callback more difficult, as the
React API dictates that this callback function should either
return a function or nothing at all. You can still use
async/await, but you have to create a helper construct for it.
Listing 4.11 shows the customized source code of the
useEffect call. The rest of the component remains
unchanged.
useEffect(() => {
(async () => {
try {
const response = await fetch('http://localhost:3001/books');
if (!response.ok) {
throw new Error('Request failed');
}
const data = await response.json();
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
Listing 4.11 “useEffect” with “async”/“await” (src/BooksList.jsx)
To enable you to use async/await in the useEffect callback, you
want to define an immediately invoked function expression
(IIFE), an anonymous self-calling function in the form (async
() => {...})(). This is a function that you define and call
immediately. The async keyword allows you to use await. For
error handling, you can then use a try-catch statement. The
logic itself—calling fetch, checking and decoding the
response, and setting the state and handling errors—
remains the same. So if you switch to the browser with this
customization, you will see no difference from the previous
implementation. Which one you choose is up to you. But you
should then consistently opt for your chosen solution
instead of switching back and forth.
4.3.3 Things to Know about Server
Communication
There are two more aspects that may be of interest to you
in connection with server communication. First, there is the
proxy feature, which allows you to redirect requests to a
backend; and second, there is the use of environment
variables, and in particular the specification of the server
address. The information in the following sections refers to
an application you’ve set up using Create React App. Other
environments provide similar features, but you should look
at the respective documentation for this.
Proxy
Currently you’re communicating with the server interface at
the address http://localhost:3001/. This causes problems in
three places at once: In a productive application,
unencrypted communication via the HTTP protocol is a no-
go. The localhost host name also doesn’t work in a
production environment as it refers to the local system, and
the port specification 3001 is also rather unusual and is
replaced by the default port 80 in live operation. So you
should avoid writing the address of the development system
directly into the source code.
There are several approaches that help you solve this
problem. One of the simplest variants is the use of the proxy
feature. In this approach, you add the proxy configuration to
the package.json file of your application. In your application,
you just reference the server interface as a regular path in
your application, and the react-scripts package takes care of
redirecting requests to the interface. Not only does this
solve the problem of the server address stored in the source
code, but it also avoids potential difficulties with cross-origin
resource sharing (CORS). Your browser disallows requests to
remote systems if they are directed to a different origin—
that is, a combination of scheme, host, and port. For the
deployment of your application, however, this means that
you also need a system that delivers the frontend and
backend via the same origin.
To make the proxy work for the development environment,
you need to add an appropriate entry to your package.json
file. For the interface of the example, which is accessible at
http://localhost:3001/, the entry looks as follows:
{
…
"proxy": "http://localhost:3001"
}
Listing 4.12 Proxy Configuration in the package.json File
This customization allows you to specify only the server
interface path in the components. The proxy receives these
requests and forwards them to the server. Listing 4.13
shows the customized call of the useEffect function in the
BooksList component:
useEffect(() => {
(async () => {
try {
const response = await fetch('/books');
if (!response.ok) {
throw new Error('Request failed');
}
const data = await response.json();
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
Listing 4.13 Using the Proxy in the Application (src/BooksList.jsx)
But there’s another and even more flexible solution to get
rid of references to third-party systems in the code.
Environment Variables
Environment variables represent information that is
provided on your system. The applications of the system
can access this information and adjust their behavior. The
advantage is that you can design the contents of the
environment variables differently on each system on which
you run your application, and you do not have to change the
application's code to do so.
You can set environment variables in different ways. The
simplest variant is to set it on the command line, where you
also start the application. Another option is to use .env files.
Create React App already prepares the use of environment
variables for you, so you can use them directly. By default,
you can access the NODE_ENV variable. In it you specify
whether you are currently in a development or production
environment. You can also define any environment variables
of your own. The only requirement is that the variable
names start with REACT_APP. For example, for the case of the
API server, the name of the variable could be
REACT_APP_API_SERVER. You access the contents of the variables
via process.env. Listing 4.14 shows how to use the
REACT_ENV_API_SERVER variable in your component:
useEffect(() => {
(async () => {
try {
const response = await fetch(
`${process.env.REACT_APP_API_SERVER}/books`
);
if (!response.ok) {
throw new Error('Request failed');
}
const data = await response.json();
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
Listing 4.14 Using Environment Variables in Components (src/BooksList.jsx)
In the case of the BooksList component, you prefix the path
of the fetch call with the name of the environment variable
in the Effect callback. In the next step, you take care of
setting the environment variable. You can do this, for
example, directly when starting the application with the
following command:
REACT_APP_API_SERVER=http://localhost:3001 npm start
Listing 4.15 Starting the Application with Environment Variables
Note that setting environment variables works differently
from system to system. For example, the command in
Listing 4.15 works on Unix systems. Much more comfortable
and flexible is a combination of the cross-env package and
an NPM script. For this purpose, you first need to install the
package using the npm install cross-env command. Then you
want to modify the start script in your package.json file as
shown in Listing 4.16:
{
…
"scripts": {
"start": "cross-env REACT_APP_API_SERVER=http://localhost:3001
react-scripts start",
…
}
}
Listing 4.16 Setting Environment Variables in the package.json File
This solution is convenient and works across different
systems. However, this variant turns into a problem if you
want to set several environment variables. In that case, the
package.json file quickly becomes confusing, and here the
use of a .env file is recommended. You can see the contents
of this file, which you place in the root directory of your
application, in Listing 4.17:
REACT_APP_API_SERVER=http://localhost:3001
Listing 4.17 Storing Environment Variables in a .env File
One aspect to keep in mind when using environment
variables is that Create React App integrates the
environment variables into the application at build time. So
the environment variables are not analyzed regularly in a
production application, but only once when you call npm run
build to build the application.
In the next section, we’ll introduce another alternative to
the browser's Fetch API: the Axios library.
4.3.4 Server Communication with Axios
The Fetch API of the browser is the default tool when it
comes to server communication. But especially for larger
applications, this interface lacks some convenient features.
Libraries such as Axios, which build on the Fetch API and
add additional features, provide a workaround.
You install Axios in your application using the npm install url
axios command. The additional installation of url package is
required because all Webpack versions prior to version 5
included polyfills for node core packages. However, newer
versions of Create React App use Webpack 5, which results
in a bug when you use Axios. You can fix this by installing
the URL package. Once installed, you can import Axios and
use the library instead of the Fetch API in your components.
Listing 4.18 shows the deployment in the BooksList
component:
import axios from 'axios';
…
useEffect(() => {
(async () => {
try {
const { data } = await axios.get(
`${process.env.REACT_APP_API_SERVER}/books`
);
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
Listing 4.18 Using Axios in a React Application (src/BooksList.jsx)
As you can see in the code, Axios handles the correct
decoding of the JSON response as well as error handling at
this point. Like the Fetch API, Axios uses promises, so you
only need to make minor adjustments to your source code.
However, a few saved lines do not justify the use of an
additional library like Axios. This library provides even more
features, such as the configuration of instances. This has
the advantage that you can centrally configure an instance
and provide it with information such as the baseURL or
specific header fields and then use it anywhere in your
application. Listing 4.19 contains an example of such an
Axios instance:
import { useState, useEffect } from 'react';
import './BooksList.css';
import axios from 'axios';
const client = axios.create({
baseURL: process.env.REACT_APP_API_SERVER,
});
function BooksList() {
const [books, setBooks] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
(async () => {
try {
const { data } = await client.get(`/books`);
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
if (error !== null) {
return <div>An error has occurred: {error.message}</div>;
} else if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>…</table>
);
}
}
export default BooksList;
Listing 4.19 Using an Axios Instance (src/BooksList.jsx)
If you use the instance in more than one component, you
can swap it out to a separate file and access it from multiple
places in your application.
So far, you’ve combined the logic and display in your React
components—but this quickly leads to the components
becoming large and confusing. You can address this problem
in different ways. Container components represent one
option.
4.4 Container Components
React makes very few specifications about the structure and
content of a component. However, if you follow the
conventions, you should make sure that your components
remain simple and clear and that a component is
responsible for exactly one task only.
React supports you here by keeping the creation of new
components very lightweight. If you start from the BooksList
component—that is, a component that displays a simple list
—it takes care of loading the data, organizing the state, and
displaying it.
So far, we have only used visual features to divide a
component. Container components allow you to also
differentiate between the logic and the presentation. The
counterpart of a container component is a presentational
component. Dan Abramov also distinguishes between smart
and dumb components in one of his blog articles at http://s-
prs.co/v570506. This designation alludes to the fact that
presentational components are only supposed to take care
of rendering; that is, they are supposed to be dumb in terms
of the application logic. Container components on the other
hand return a JSX structure, but this usually consists of only
one or at least very few presentational components. The
container component encapsulates the logic for the
presentational component, carries the state in the form of
its internal state, and performs the communication with the
server. The information and functions that the
presentational component needs for the display are passed
to it as props by the container component.
The distinction between presentational and container
components has the advantage that you clearly separate
display and logic. This results in an improved reusability of
the presentational components because the logic becomes
interchangeable. Also, you can test the logic separately
from the actual rendering.
For a better distinction, you can extend the file name of the
container component with the name container.
In an application, you should normally also proceed
according to this scheme and implement your components
first. If you find that a component is getting too large or that
you want to reuse certain parts of the application logic, you
should separate the representation from the logic and insert
containers.
4.4.1 Swapping Out Logic to a Container
Component
For the implementation of container and presentational
components, we start from the BooksList component:
import { useState, useEffect } from 'react';
import './BooksList.css';
import axios from 'axios';
const client = axios.create({
baseURL: process.env.REACT_APP_API_SERVER,
});
function BooksList() {
const [books, setBooks] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
(async () => {
try {
const { data } = await client.get(`/books`);
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
if (error !== null) {
return <div>An error has occurred: {error.message}</div>;
} else if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
</tr>
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 4.20 Initial Situation: The “BooksList” Component (src/BooksList.jsx)
When splitting the component, you first need to identify all
the parts that have nothing to do with rendering and swap
them out to a separate component. Choose
BooksList.container.jsx as the file name. The component is
called BooksListContainer. In Listing 4.21, you can see the
implementation of the container component:
import { useState, useEffect } from 'react';
import BooksList from './BooksList';
import axios from 'axios';
const client = axios.create({
baseURL: process.env.REACT_APP_API_SERVER,
});
function BooksListContainer() {
const [books, setBooks] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
(async () => {
try {
const { data } = await client.get(`/books`);
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
return <BooksList error={error} books={books} />;
}
export default BooksListContainer;
Listing 4.21 Container Component (src/BooksList.container.jsx)
In this case, you cut out the state and effect hooks and pass
on all the structures needed by the presentational
component via props. For the BooksList component, these
are the error and books states.
4.4.2 Integrating the Container Component
When you integrate a component, it doesn’t matter to React
what type of component it is—that is, whether it’s a
presentational or a container component. All components
are specified to another component by a tag in the JSX. It
also does not distinguish the interface to the outside system
—that is, the props. Because you use default exports
throughout for the components, when you include the
component in app, you only need to modify the import
statement in the App component, as shown in Listing 4.22:
import './App.css';
import BooksList from './BooksList.container';
function App() {
return (
<div>
<h1>Books management</h1>
<BooksList />
</div>
);
}
export default App;
Listing 4.22 Integration of the Container Component (src/App.jsx)
Before you can test the new version of the application, you
need to convert the presentational component.
4.4.3 Implementing the Presentational
Component
You implement the presentational component as a function
component that returns the JSX structure of the original
BooksList component. To make sure that this will continue to
work, you need to include the books array and the error
object as props. The source code of the presentational
component is shown in Listing 4.23:
import PropTypes from 'prop-types';
import './BooksList.css';
function BooksList({ error, books }) {
if (error !== null) {
return <div>An error has occurred: {error.message}</div>;
} else if (books.length === 0) {
return <div>No books found</div>;
} else {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
</tr>
))}
</tbody>
</table>
);
}
}
BooksList.protoTypes = {
books: PropTypes.arrayOf(
PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
isbn: PropTypes.string.isRequired,
})
),
error: PropTypes.object,
};
export default BooksList;
Listing 4.23 Implementation of the Presentational Component
(src/BooksList.jsx)
The only major customization here is the insertion of
PropTypes for the presentational component, which you use
to ensure that the correct data structures are passed to the
component.
Tip
The division into containers and presentational
components is a continuous process in an application. Do
not try to divide your entire application into these
categories at the very beginning of the development, but
instead add containers where you need them.
4.5 Higher-Order Components
React components should generally be focused on exactly
one purpose. Often, components then need to be extended
by functionality that is not directly related to the display or
is used in a similar way in several components. A classic
example of this is the connection to a data source. One
possibility is to use container components. Another, quite
frequently used solution is to use so-called higher-order
components (HOCs). This designation alludes to the higher-
order functions from functional programming; this type of
function is often found in JavaScript, for example, in array
functions. These are functions to which functions are passed
as arguments or that return functions themselves. The
higher-order components in React follow a similar concept. A
HOC consists of a function to which you pass a component
and which in turn returns a component.
A HOC is a design pattern and not a concrete
implementation of React. The pattern exploits the fact that
components can be used like objects. HOCs are often used
when the functionality of components is to be extended.
They are used both within applications and in the creation of
libraries. However, with the increasing adaptation of the
Hooks API, HOCs are becoming less important as you can
usually implement the functionality with a custom hook.
You'll learn how this works in Chapter 6.
4.5.1 A Simple Higher-Order Component
Before we integrate a HOC into a larger context, let's first
look at a simple example: A button component is supposed
to be extended with a log option. Basically, using a HOC is
about adding functionality to the passed component. The
button component has a log prop through which logging
functionality is passed to it. The button component calls this
functionality as a function. The HOC does nothing more than
generate a new component consisting of the button
component and a concrete log function. So you assign a
fixed value to the log prop.
function Button({ log, onClick, title }) {
return (
<button
onClick={(e) => {
log(e);
onClick(e);
}}
>
{title}
</button>
);
}
function withLogger(Component) {
function log(item) {
console.log('Logger: ', item);
}
return function (props) {
return <Component log={log} {...props} />;
};
}
const ButtonWithLogger = withLogger(Button);
function App() {
return (
<div>
<ButtonWithLogger
onClick={() => console.log('click handled')}
title="Click me"
/>
</div>
);
}
export default App;
Listing 4.24 Simple Higher-Order Component
The component to be extended here is the button
component. This component accepts a log function, a click-
handler function, and the button label. The button
component returns a button element whose label you set
and in whose onClick prop you call the log function and the
onClick function. The log function is passed to the
component via the props. Such a logging facility is usually
required in several places in an application. In addition,
different implementations are used for different
environments, such as the development or production
environment. To avoid having to replace the implementation
in all places, you can use a HOC and extend the components
that need the logging feature accordingly. If the functionality
is to be replaced later, you only need to adjust the HOC.
The withLogger function represents the HOC. This function
receives a component as an argument and in turn returns a
component, in this case in the form of a function
component. This component renders the originally passed
component and passes the log function to it. In the example,
the log function writes the value passed to it to the browser
console. However, an alternative implementation could also
send the value to a logging server. A widely used convention
states that a HOC should be preceded by the word with to
identify the type of component at first glance.
When implementing HOCs, you should keep in mind that
these functions should be pure functions—that is, functions
without side effects that generate an output from a given
input via reproducible rules. The input and output in this
case are React components. Moreover, the HOC does not
modify the passed component, but merely enriches it with
additional functionality that is passed to the inner
component in the form of props.
As you can see in the code example, the withLogger function
is used like an ordinary JavaScript function, which means
that in this case you can also control the behavior of the
function with additional arguments.
The component you created through the withLogger function
passes all props passed to it to the component it
encapsulates. You can achieve this by having the wrapper
component of the HOC itself accept props, and you pass
them to the inner component through a destructuring
statement.
One last point to note is that you shouldn’t call the HOC
within another component function. This would result in the
component being recreated each time it’s rendered. If you
call the function outside the component function, the
component is created once and merely redisplayed during
each rendering process.
4.5.2 Integrating a Higher-Order Component
in the BooksList Component
For the concrete implementation of a HOC in an application,
we’ll use the BooksList component. The logic for loading the
data records gets swapped out to a HOC. This has the
advantage that you can easily replace this HOC as a data
provider later and, for example, use a WebSocket stream to
load and update the data instead of an HTTP request to a
backend. The name of the HOC is withBooks. It enriches the
passed component with the books prop, which contains an
array of data records, and the error prop, which contains
either null or an error object. Listing 4.25 contains the
implementation of this method.
import { useEffect, useState } from 'react';
import axios from 'axios';
function withBooks(Component) {
return function (props) {
const [books, setBooks] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
(async () => {
try {
const { data } = await axios.get(
`${process.env.REACT_APP_API_SERVER}/books`
);
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
return <Component {...props} books={books} error={error} />;
};
}
export default withBooks;
Listing 4.25 HOC for Loading Data Records (src/withBooks.jsx)
You implement the HOC as a function named withBooks. The
function in turn returns an anonymous function that
represents the component that is responsible for loading the
data and managing the state. For this purpose, you create a
books and an error state and load the data from the server
via an effect hook. The effect hook takes care of setting the
state and error handling. The return value of the anonymous
component is the passed component, to which you pass all
passed props. In addition, you insert the books and error
props.
4.5.3 Integrating the Higher-Order
Component
You can take the inner component—the BooksList component
—directly from Listing 4.23. Because the interface via the
props already corresponds to what the HOC requires, no
adjustments are necessary.
When integrating the HOC in the application, the rule is that
you shouldn’t call the HOC function within the function
component; otherwise, it would be created again for each
rendering process.
import './App.css';
import BooksList from './BooksList';
import withBooks from './withBooks';
const BooksListWithBooks = withBooks(BooksList);
function App() {
return (
<div>
<h1>Books management</h1>
<BooksListWithBooks />
</div>
);
}
export default App;
Listing 4.26 Integration of the higher-order component (src/App.jsx)
As you can see in Listing 4.26, you run the withBooks function
outside the actual component. The App component then
renders the BooksListWithBooks component within its JSX
structure. When you switch to the browser, you shouldn’t
notice any visible change in the display, as it doesn’t matter
whether you choose a container component or a HOC. The
only issue here is how and where you swap out the logic of
your application. In addition to these two approaches, using
render props is another method of removing logic from a
component and reusing it elsewhere in the application.
4.6 Render Props
Like HOCs, render props denote an architectural pattern in
React and not a feature of the library. Here, you create a
component that accepts a prop named render and then
executes it in its own function body. You can then pass
additional information during this execution. We’ll
demonstrate the implementation of render props with a
small example first, before we integrate it into the sample
application. For demonstration purposes, we’ll first use the
logger example again. Listing 4.27 contains the source code
of the example:
function Button({ log, onClick, title }) {
return (
<button
onClick={(e) => {
log(e);
onClick(e);
}}
>
{title}
</button>
);
}
function log(item) {
console.log('Logger: ', item);
}
function Logger({ render }) {
return render(log);
}
function App() {
return (
<div>
<Logger
render={(log) => (
<Button
log={log}
onClick={() => console.log('click handled')}
title="Click me"
/>
)}
/>
</div>
);
}
export default App;
Listing 4.27 Implementation of Render Props
In Listing 4.27, the parts of the example that differ from the
HOC example are marked in bold. The logger component
replaces the withLogger HOC in this case. The Logger
component assumes that the render prop passed returns a
component that the function component itself can return.
The log function is passed as an argument to the function
from the render prop. The purpose behind this
implementation only becomes clear when you imagine that
the Logger component and the log function are stored in a
separate file and are used in different places in the
application.
In the App component itself, you render the Logger
component and then specify a function in the render prop
that receives the log function and renders the Button
component.
4.6.1 Alternative Names for Render Props
Render props get their name from the naming of the prop
that determines the rendering. However, this name is not
fixed; you can choose it freely. The name render is merely a
relatively widespread convention here. Similarly, the name
children is also used frequently. However, this prop name
has a special feature: it refers to the children of a
component—that is, the elements located between the
opening and closing tags. So you can use this prop in two
ways. In the simpler case, you just rename the render prop
to children and don't have to change anything else.
Listing 4.28 shows how you can use the children prop to
access a function that you have defined as a child element
of the logger component:
function Button({ log, onClick, title }) {
return (
<button
onClick={(e) => {
log(e);
onClick(e);
}}
>
{title}
</button>
);
}
function log(item) {
console.log('Logger: ', item);
}
function Logger({ children }) {
return children(log);
}
function App() {
return (
<div>
<Logger>
{(log) => (
<Button
log={log}
onClick={() => console.log('click handled')}
title="Click me"
/>
)}
</Logger>
</div>
);
}
export default App;
Listing 4.28 Render Props with “children” Elements
The only adjustment to the logger component consists of
renaming the prop from render to children. During the
integration, you implement an arrow function between the
opening and closing logger tags, which you can access
through the children prop of the logger component. This
arrow function is passed the log function, which you can
then in turn pass to the button component.
Compared to the other implementations, this is probably
one of the most elegant solutions. However, the choice of
the specific implementation ultimately remains a matter of
taste.
4.6.2 Integrating the Render Props into the
Application
For the integration of the render props, we use the last
described variant with the children prop and the
implementation of the render callback function within the
component. The withBooks component is replaced by a new
component called BooksLoader. The source code of the
component remains almost unchanged. Only the props and
the returned JSX structure need to be adjusted. Listing 4.29
contains the source code:
import { useEffect, useState } from 'react';
import axios from 'axios';
function BooksLoader({ children }) {
const [books, setBooks] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
(async () => {
try {
const { data } = await axios.get(
`${process.env.REACT_APP_API_SERVER}/books`
);
setBooks(data);
} catch (error) {
setError(error);
}
})();
}, []);
return children(books, error);
}
export default BooksLoader;
Listing 4.29 The “BooksLoader” Component with Render Props
(src/BooksLoader.jsx)
In the BooksLoader component, you manage the state, load
the data from the server, and pass it to the function you
pass to the component via the children prop. In the App
component, you now include the BooksLoader component
instead of the HOC. You can see what the corresponding
source code looks like in Listing 4.30:
import './App.css';
import BooksList from './BooksList';
import BooksLoader from './BooksLoader';
function App() {
return (
<div>
<h1>Books management</h1>
<BooksLoader>
{(books, error) => <BooksList books={books} error={error} />}
</BooksLoader>
</div>
);
}
export default App;
Listing 4.30 Integration of the Component with Render Props
Between the opening and closing BooksLoader tags, you
define the function called by the BooksLoader component. The
function returns the BooksList component, which in turn
receives the data to display and an optional error object.
With these adjustments, you've now become familiar with
both HOCs and render props and seen that you can easily
interchange the two.
4.7 Context
Quick Start
The Context API allows you to access centrally stored
information independently of the component hierarchy
and without passing the information to child components
via props.
You can create the context using the createContext
function. The resulting object gives you a provider
component in whose value prop you can store any
structures, from simple values to objects and arrays to
functions.
You can access these values from child components of the
provider using the useContext function in conjunction with
the context object.
The Context API of React helps you provide structures in the
component tree without the need to use props to pass them
through each layer from source to target. The use of the
Context API is particularly suitable for larger applications in
which different places access information. The interface is
used by external packages (such as the React router or
Redux) as well as in application development. Thus the
Context API enables you to decouple your components more
and avoid passing around props. While this means that
component function signatures are simplified as you save
props, it also means that complexity within your application
may increase due to the use of different contexts.
In general, you should only use the Context API if you use a
particular piece of information in multiple places within your
application or if the source of a piece of information is
several layers away from its actual use.
4.7.1 The Context API
The Context API of React provides a set of functions and
components. The createContext function plays a central role
as you use it to create a context. You can pass a default
value to this function when you call it, which will be used by
React if you don't assign an explicit value in your
application.
The object returned by the createContext method contains
two properties—Provider and Consumer. Both properties can be
used as components. The Provider property can be used to
set a value for the context. The Consumer property is used for
read access to the context, where you must use the
provider component to work with the context. However, you
no longer use the Consumer component in a modern React
application; instead, you access the context via the
useContext function of the Hooks API.
Normally you generate the context object in a separate file
using the createContext function. You can pass the context
object a default value that React will use if no value is
assigned elsewhere. The context can contain any number of
objects, which are then available to you in the scope of the
context. You use the Provider component to place the
context in the component tree. Then you can access the
context from all child components of the provider. Typically,
you place the context near the root component of your
application.
To illustrate the use of the Context API, let's first look at a
simple example before we add the context to a larger
application.
Creating a “Context” Object
First, you create a new file in your application called
Context.js. The contents of this file are shown in
Listing 4.31:
import { createContext } from 'react';
const Context = createContext(0);
export default Context;
Listing 4.31 Creating a “Context” Object (src/Context.js)
The context is initialized with the value 0 in this case. You
can access the resulting object in your application. This
happens, for example, in the App component.
Integrating the Context
The Context object provides you with two components,
Provider and Consumer. The Provider component provides the
context for the component tree. In Listing 4.32, you can see
the integration into the App component:
import { useState } from 'react';
import Context from './Context';
function App() {
const [counter, setCounter] = useState(0);
function increment() {
setCounter((prevState) => prevState + 1);
}
return (
<Context.Provider value={counter}>
<button onClick={increment}>increment</button>
</Context.Provider>
);
}
export default App;
Listing 4.32 Integrating the Context in the Application (src/App.jsx)
In this example, you create a counter state in the App
component. The increment function allows you to increase
the value of the counter by the value 1. In the JSX structure
of the component, you use the Provider component of the
Context object and assign the counter value as a value using
the value prop. In the click-handler function of the button
element, you call the increment function so that a click on the
button increases the counter by one. Currently, this action
doesn’t yet affect the display. In the next step, you
implement another component that accesses the context
and displays the value.
Read Access to the Context
For read access to the context, you use the useContext
function. To display the counter value, create a new
component called Counter and save it in a file called
Counter.jsx:
import { useContext } from 'react';
import Context from './Context';
function Counter() {
const value = useContext(Context);
return <div>Counter: {value}</div>;
}
export default Counter;
Listing 4.33 Read Access to the Context with the “Context.Consumer”
Component
As you can see in Listing 4.33, you use the useContext
function in combination with the context object you created
via the createContext function. This gives the hook function
access to the provider closest in the component tree and
makes the value available to you. Now you still need include
the component in the App component so that it gets
displayed:
import { useState } from 'react';
import Context from './Context';
import Counter from './Counter';
function App() {
const [counter, setCounter] = useState(0);
function increment() {
setCounter((prevState) => prevState + 1);
}
return (
<Context.Provider value={counter}>
<Counter />
<button onClick={increment}>increment</button>
</Context.Provider>
);
}
export default App;
Listing 4.34 Integrating the “Counter” Component (src/App.jsx)
When you switch to the browser with this configuration, you
can click on the button and the counter component will
display the updated value. The most important difference is
that you can access this value without passing it as a prop
to the component.
4.7.2 Using the Context API in the Sample
Application
This introductory example represented a simple use of the
Context API. However, you can implement much more
complex solutions with the context. So you can not only
store simple values (like numbers in this example) in the
context, but also objects, arrays, or functions. Next, you
create a context where you store the array structure that
the useState function returns to you. This allows you to
access the state at various points, but also to modify it. This
context is responsible for the management of book data
records. In Listing 4.35, you can see the source code that
takes care of creating the context:
import { createContext } from 'react';
const BooksContext = createContext([null, () => {}]);
export default BooksContext;
Listing 4.35 Creating the Books Context (src/BooksContext.jsx)
By default, you set the context to an array with the null
elements and an empty arrow function. These represent the
two elements from useState. In the next step, you implement
a component that creates the state, connects it to the
context, and provides the context's provider. You name this
file BooksProvider.jsx. The corresponding source code can
be found in Listing 4.36:
import { useState } from 'react';
import BooksContext from './BooksContext';
function BooksProvider({ children }) {
const [books, setBooks] = useState([]);
return (
<BooksContext.Provider value={[books, setBooks]}>
{children}
</BooksContext.Provider>
);
}
export default BooksProvider;
Listing 4.36 Creating “BooksProvider” (src/BooksProvider.jsx)
Here you can see another use of the children prop of a
component. This implementation allows you to integrate the
BooksProvider component into your component tree, and
React will take care of ensuring that the elements you insert
between the opening and closing tags are correctly placed
as child elements in the component and thus have access to
the context.
Now you need to include the BooksProvider component in
your application. As mentioned earlier, you usually place the
context near the root component, or in this case, just inside
the App component, as shown in Listing 4.37:
import BooksProvider from './BooksProvider';
import BooksList from './BooksList';
function App() {
return (
<BooksProvider>
<BooksList />
</BooksProvider>
);
}
export default App;
Listing 4.37 Integration of “BooksProvider” into the Application (src/App.jsx)
The BooksList component is the first component in the tree
that can access the context. As before, your task is to
display the frame of the list and iterate over the individual
data records. Listing 4.38 shows the source code of the
component:
import { useEffect, useContext } from 'react';
import BooksContext from './BooksContext';
import BooksListItem from './BooksListItem';
import './BooksList.css';
import axios from 'axios';
function BooksList() {
const [books, setBooks] = useContext(BooksContext);
useEffect(() => {
(async () => {
const { data } = await axios.get(
`${process.env.REACT_APP_API_SERVER}/books`
);
setBooks(data);
})();
}, []);
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th></th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<BooksListItem key={book.id} book={book} />
))}
</tbody>
</table>
);
}
export default BooksList;
Listing 4.38 “BooksList” Component with Access to the Context
(src/BooksList.jsx)
The BooksList component uses the useContext function to gain
access to the context. It loads the data from the server
within an effect hook and writes it to the context using the
setBooks function. In the JSX structure, the component
iterates over the books array and creates new instances of
the BooksListItem component for each data record. Here you
pass the respective data record to the component for
display. The code of the BooksListItem component is shown in
Listing 4.39:
import { useContext } from 'react';
import BooksContext from './BooksContext';
import produce from 'immer';
import { StarBorder, Star } from '@mui/icons-material';
function BooksListItem({ book }) {
const [, setBooks] = useContext(BooksContext);
function handleRate(id, rating) {
setBooks((prevState) => {
return produce(prevState, (draftState) => {
draftState.map((book) => {
if (book.id === id) {
book.rating = rating;
}
return book;
});
});
});
}
return (
<tr>
<td>{book.title}</td>
<td>{book.author ? book.author : 'Unknown'}</td>
<td>{book.isbn}</td>
<td>
{new Array(5).fill('').map((item, i) => (
<button
className="ratingButton"
key={i}
onClick={() => handleRate(book.id, i + 1)}
>
{book.rating < i + 1 ? <StarBorder /> : <Star />}
</button>
))}
</td>
</tr>
);
}
export default BooksListItem;
Listing 4.39 “BooksListItem” Component for Displaying the Individual Data
Records (src/BooksListItem.jsx)
The component receives all information for display from its
parent component. This means that no access to the
context is required. However, the component includes the
possibility to rate the books as well. This means that the
component must also manipulate the data records.
Previously, this task was performed by the parent
component, which was also responsible for the state. In this
case, you can get the setBooks function from the context,
and the BooksListItem component can change the data itself.
With the resulting state change, React updates the display,
and the changed rating becomes visible to users.
When loading the setBooks function from the context, you
see a special feature of the destructuring statement for
arrays: if you aren’t interested in the first array element,
you can put a comma at the beginning and access the
second element directly. This omission of elements works for
any number, but quickly becomes confusing, so you
shouldn’t use this feature too excessively.
When creating the rating buttons, you’ll see another
feature, but it isn’t React-specific: you use the array
constructor to create a new array with five memory
locations. In the next step, these will be filled with empty
strings so that you can insert button elements via the map
method in the final step. These elements call the handleRate
function in their click handler, which takes care of
communicating with the context.
After examining the somewhat more extensive Context API,
we now come to a practical and manageable feature of
React: fragments.
4.8 Fragments
When designing a React component, there are rules you
must follow. One of the most important rules is that a
component returns either exactly one root element or an
array of elements. Thus, it isn’t possible to render multiple
parallel elements on the root layer of a component.
Often in such a case, you insert an additional div element to
follow the rule. However, additional elements also mean
that the DOM—the tree of HTML elements that the browser
works with—will grow. This isn’t yet a problem with
individual elements, but with extensive component trees
such additional elements become very important. React
provides the fragment component for such cases. This
component, introduced with version 16.2, is a container
element that React does not render, but uses only for
internal structuring.
For fragments, you can use two different notations. In the
long notation, you use the React.Fragment component. The
short notation consists only of an empty tag. In Listing 4.40,
you can see that the h1 element with the heading and the
BooksProvider are located at the top level of the component
hierarchy. Instead of a div element, you can use a fragment
here to return only a root element.
import BooksProvider from './BooksProvider';
import BooksList from './BooksList';
function App() {
return (
<>
<h1>Books management</h1>
<BooksProvider>
<BooksList />
</BooksProvider>
</>
);
}
export default App;
Listing 4.40 Using Fragments in the Application (src/App.jsx)
When you switch to the developer tools in the browser with
this customization, you will see that the fragment is not
rendered, nor is it translated to another HTML element.
4.9 Summary
In this chapter, you mainly dealt with the reusability of
components and the swapping out of logic from
components:
You learned about the lifecycle of a component and how
to influence its different sections using the effect hook.
You learned how to communicate with a web server to use
information in your application.
Container components enable you to outsource logic from
the components of your application.
Higher-order components are a means of adding
additional functionality to your components. These
functions accept components as inputs and return
extended components.
Another means of increasing reusability within an
application is via render props, where you pass a
component to another component, extend it, and finally
render it.
The Context API allows access to global values from
multiple places in the application without passing the
information through each level of the component tree.
Fragments are a means of grouping elements without
having to insert an explicit container element. Fragments
are not rendered.
In the next chapter, you'll learn more about the class
components of React. This is the second method to
implement components in React besides the function
components we have used so far.
5 Class Components
In a modern React application, function components are
used by default. Although this design feature has nearly
replaced the previously very widely used class components,
class components still exist and will continue to be
supported by React for quite some time, according to its
developers. So if you don't need to work with class
components because of an existing application, and you
don't plan to introduce the deprecated class components in
your application, you can skip this chapter and go directly to
Chapter 6, where you'll learn more about the Hooks API that
was the cause of the demise of class components.
5.1 Class Components in React
Quick Start
A class component consists of a JavaScript class derived
from React.Component and has at least one render method. In
addition to this, there is a set of methods you can use to
handle the lifecycle of the component. You can use the
state property of the class to get read access to the
component's state. With setState, you write the state.
Listing 5.1 contains an example of a class component:
import React from 'react';
class BooksList extends React.Component {
state = { books: [] };
async componentDidMount() {
const response = await fetch(
`${process.env.REACT_APP_API_SERVER}/books`
);
const data = await response.json();
this.setState({ books: data });
}
render() {
return (
<ul>
{this.state.books.map((book) => (
<li key={book.id}>{book.title}</li>
))}
</ul>
);
}
}
export default BooksList;
Listing 5.1 Example of a Class Component
Class components get their name from the fact that they
are implemented as JavaScript classes. In contrast to
function components, they are more clearly structured.
Especially if you are switching to React from other
frameworks, you’ll find your way around here much faster
with class components than with function components.
Class components were the original type of components in
React and were the only way a component could manage its
own state and lifecycle and access context prior to the
introduction of the Hooks API.
5.2 Basic Structure of a Class
Component
For a class component, you define a new JavaScript class
that you derive from React.Component. This base class makes
sure that the class becomes a component. The same rules
and recommendations apply for handling class components
as for function components. So the name of your class
component must start with a capital letter, and the render
method of the class must return a JSX structure with exactly
one root element or an array of React elements. You should
also make sure that you implement only one component per
file and that the name of the file matches that of the
component.
In Listing 5.2, you can see a minimal version of a class
component consisting of only one class with a render
method:
import React from 'react';
class App extends React.Component {
render() {
return (
<div>
<h1>Books management</h1>
</div>
);
}
}
export default App;
Listing 5.2 Minimal Version of a Class Component (src/App.jsx)
It’s always been possible to implement this type of
component as a function component. This was even
recommended because function components have
significantly less overhead compared to class components.
Class components only become relevant if they have their
own state or lifecycle methods, which you can now also
reproduce in function components via the useState and
useEffect functions.
But before we delve into those topics, let's first look at how
props should be handled in a class component.
5.3 Props in a Class Component
In a class component, you can use the props property to
access the props passed from the parent component. And
once again, the same principle applies: props can accept
any data type, from simple numbers or strings to objects,
arrays, and functions. Listing 5.3 shows how you can access
the props in a class component:
import React from 'react';
class BooksListItem extends React.Component {
render() {
const { book } = this.props;
return (
<tr>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
</tr>
);
}
}
export default BooksListItem;
Listing 5.3 Accessing Props in a Class Component (src/BooksListItem.jsx)
If you include the BooksListItem component in your
application using the <BooksListItem book={book} /> tag, React
allows you to access the book prop via this.props.book within
the class component. By using this prop in more than one
place in the render method, you can save yourself some
typing work by extracting the information into a variable via
a destructuring statement.
5.3.1 Defining Prop Structures Using
PropTypes
If you use JavaScript, you have the option to use PropTypes
to define the structure of your props and thus make sure
that it is adhered to. This is similar to using function
components. You define PropTypes as a static property
named propTypes for the class component:
import React from 'react';
import PropTypes from 'prop-types';
class BooksListItem extends React.Component {
static propTypes = {
book: PropTypes.shape({
id: PropTypes.number.isRequired,
title: PropTypes.string.isRequired,
isbn: PropTypes.string.isRequired,
rating: PropTypes.number.isRequired,
}).isRequired,
};
render() {
const { book } = this.props;
return (
<tr>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
</tr>
);
}
}
export default BooksListItem;
Listing 5.4 Definition of PropTypes as Static Property of the Class
(src/BooksListItem.jsx)
Listing 5.4 shows how to define PropTypes as a static
property directly in the class. Alternatively, you can define
them outside the class structure, as shown in Listing 5.5:
import React from 'react';
import PropTypes from 'prop-types';
class BooksListItem extends React.Component {
render() {…}
}
BooksListItem.propTypes = {
book: PropTypes.shape({…}).isRequired,
};
export default BooksListItem;
Listing 5.5 Definition of PropTypes Outside the Class Component
(src/BooksListItem.jsx)
Both variants are equivalent. However, the second variant is
somewhat neater as especially with more extensive
PropTypes the readability of the class will suffer. Regardless
of which of the two variants you choose, however, if you
violate the rules you have defined in PropTypes, then you’ll
receive a warning in the browser console.
5.3.2 Default Values for Props
In some cases, you specify that your component can be
affected via props, but these props do not have to be
specified. In this case, you can set default values for such
optional props to create a defined starting point. You set
these default props using the static defaultProps property.
Listing 5.6 shows the implementation of a headline
component that accepts an optional title prop. If you don't
pass it when including it, React uses the Default heading
default value and displays it:
import React from 'react';
import PropTypes from 'prop-types';
class Headline extends React.Component {
static defaultProps = {
title: 'Default heading',
};
render() {
return <h1>{this.props.title}</h1>;
}
}
Headline.propTypes = {
title: PropTypes.string,
};
export default Headline;
Listing 5.6 Default Values for Props (src/Headline.jsx)
In addition to the render method and the props, the basic
functionality of a class component also includes the state of
the component, which you can use to manage its internal
state.
5.4 State: The State of the Class
Component
The state of a component contains data that’s needed for
the display or to control the display. The component has
control over the state and can modify it in any way. As with
the function components, access to the state is divided into
two parts: you can use the state property of the class to
access the state in read-only mode, and the setState method
to manipulate the state. Each time you make a change,
React renders the component again.
You can set the initial value of the state in two ways,
depending on whether this operation is static or dynamic.
5.4.1 Initializing the State via the State
Property of the Class
A component is basically an ordinary JavaScript class, and
as such it allows the use of properties. By deriving the
component class from React, some methods (such as render)
and also some properties (like the state property) receive a
special meaning. Listing 5.7 shows how to use the state
property to initialize the component's state. You’ll also see
how to use the state of the class component for display.
import React from 'react';
import BooksListItem from './BooksListItem';
const initialBooks = [
{
id: 1,
title: 'JavaScript - The Comprehensive Guide',
author: 'Philip Ackermann',
isbn: '978-3836286299',
rating: 5,
}
];
class BooksList extends React.Component {
state = {
books: initialBooks,
};
render() {
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
</tr>
</thead>
<tbody>
{this.state.books.map((book) => (
<BooksListItem book={book} key={book.id} />
))}
</tbody>
</table>
);
}
}
export default BooksList;
Listing 5.7 Initialization of the State and Read Access (src/BooksList.jsx)
You can define the initial state directly via the state property
of the class and assign an appropriate value to it. The
disadvantage of this approach is that you have no dynamics
here. So if you want to calculate the initial state, you have
to switch to the constructor of the class, as you’ll learn in
the next section.
Inside the render method, you can see how to access the
state in read-only mode. Again, follow the JavaScript rules
and access the state property using this.state.
5.4.2 Initializing the State in the Constructor
The first method executed in a class component is the
constructor, which allows you to perform initialization tasks
for your component. But this isn’t the right place to
communicate with a server to dynamically load data; you’ll
learn how to do this when we describe the lifecycle of a
class component. In the constructor, however, you can
initialize the state of your component and use methods of
the component. Also, you can use the props that were
passed to your component. To demonstrate how the
constructor works, we’ll use the Headline component for this
example. Listing 5.8 shows the implementation of this
component:
import React from 'react';
import PropTypes from 'prop-types';
class Headline extends React.Component {
static defaultProps = {
title: 'Default heading',
};
state = {
title: '',
};
constructor(props) {
super(props);
this.state.title = props.title;
}
render() {
return <h1>{this.state.title}</h1>;
}
}
Headline.propTypes = {
title: PropTypes.string,
};
export default Headline;
Listing 5.8 Creating the State Dynamically in the Constructor
(src/Headline.jsx)
Note that you must run the parent constructor with
super(props) when using the constructor of a class
component. If you omit this line, you’ll get a warning during
the compile process and then a ReferenceError exception in
the browser. In JavaScript, you must call the parent
constructor before you can use this.
By default, React passes the props as an object to the
constructor during the creation of a new component
instance. You can also access the state via this.state. Thus,
it’s possible to dynamically fill the state once when the
component instance is created. In that case, you want to
use the title prop and assign it to the title property of the
state. If you don’t set the prop when you include the
component, React uses the default value you set previously.
You’re still missing one important building block for the
implementation of class components: access to the
individual stages of the lifecycle.
5.5 The Component Lifecycle
When you implement components, situations can occur in
which you have to intervene in the component's lifecycle at
different points in time. Typical examples include the
creation of the component in the constructor, the update of
props or states, or the unmounting of the component.
Although the stations in the lifecycle of a class component
are the same as in a function component, with class
components, you have more intervention options. shows a
graphical representation of the different lifecycle hooks
available to you.
The lifecycle of a class component is divided into different
sections:
In the first section—the mounting section—the component
is created and mounted in the application so that it
becomes visible.
The updating section describes the time during which the
component is visible and users can interact with it. The
main events that occur here are the changes from outside
the component to the props and inside a component to
the state.
The last section is the unmounting section, which is
triggered when the component is removed. This section is
mainly about tidying up.
Figure 5.1 Lifecycle of a Class Component in React
Different hooks are available in each section. These are
methods that you implement and that are executed as soon
as the component is in the respective section. Each section
is in turn divided into up to three different phases. The
phases have different characteristics when it comes to
accessing the component and creating side effects as well
as accessing the DOM:
The render phase is responsible for rendering the
component. The execution of the render method marks the
end of this phase. This phase has no relevance for the
unmounting section. The methods of this phase must not
have any side effects because they can be stopped,
aborted, and restarted by React.
In the precommit phase, you can access the DOM of the
component in read-only mode. This phase exists only in
the updating section of the component lifecycle.
The final phase of a component's lifecycle is the commit
phase. It exists in every section and allows you to access
the DOM. In addition, side effects, such as communication
with a server, can be performed and updates can be
scheduled.
To demonstrate the different lifecycle hooks, you can
implement a timer component that counts up time using the
different lifecycle hooks. For this example, assume that you
have a newly created React application that consists of two
components, App and Timer:
import React from 'react';
import './App.css';
import Timer from './Timer';
class App extends React.Component {
state = {
time: 0,
};
handleClick() {
this.setState({ time: Math.floor(Math.random() * 10) });
}
render() {
return (
<div className="App">
<Timer time={this.state.time} />
<button onClick={() => this.handleClick()}>set</button>
</div>
);
}
}
export default App;
Listing 5.9 Initial Component (src/App.jsx)
As you can see in Listing 5.9, the App component has its own
state, which contains the number time. This is passed to the
timer component in the render method via the time prop.
There’s also a button that can reset the value through its
click handler. Here, keep in mind that you lose the context
inside an event-handler function. So if you were to pass the
handleClick method directly to the onClick prop, the this
inside the method would no longer point to the component
instance. For this reason, you wrap the function call in an
arrow function, which ensures that you keep the context.
5.5.1 Constructor
You use the constructor to perform the initialization of the
component. As you already know, the constructor receives
the passed props as an argument. Before you can use this
in the constructor, you must call the parent constructor via
the props object. Within the constructor, you can access the
state via the state property and set it directly at this point
without using the setState method:
import React from 'react';
export default class Timer extends React.Component {
constructor(props) {
console.log('Constructor');
super(props);
this.state = {
time: 0,
};
}
}
Listing 5.10 Constructor of the “timer” Component (src/Timer.jsx)
In Listing 5.10, you first write to the console the information
that the constructor has been called. Finally, you set the
time property of the state object to its initial value of 0.
5.5.2 “getDerivedStateFromProps”
The static getDerivedStateFromProps method is called on each
change. This method replaces the componentWillReceiveProps
method and is rarely used. The method is used to determine
if the props have changed and if the state needs to be
adjusted. In the getDerivedStateFromProps method, you have
access to the props and the state object. The method returns
a new state object or null if the state remains unchanged.
import React from 'react';
export default class Timer extends React.Component {
constructor(props) {
console.log('Constructor');
super(props);
this.state = {
initial: 0,
time: 0,
};
}
static getDerivedStateFromProps(props, state) {
console.log('getDerivedStateFromProps');
if (props.time !== state.initial) {
return {
initial: props.time,
time: props.time,
};
}
return null;
}
}
Listing 5.11 Implementation of the “getDerivedStateFromProps” Method
(src/Timer.jsx)
In the example in Listing 5.11, you add the initial property
to the state. You use this property to check if the props have
changed. In the getDerivedStateFromProps method, you first
output the information that the function was called. If the
props have changed, you set the time and initial values to
the passed value.
5.5.3 “render”
If you don’t implement the render method, React cannot
display the component. This component must return a JSX
structure, which is then rendered by React.
import React from 'react';
export default class Timer extends React.Component {
constructor(props) {…}
static getDerivedStateFromProps(props, state) {…}
render() {
console.log('render');
return <div>{this.state.time}</div>;
}
}
Listing 5.12 Implementation of the “render” Method (src/Timer.jsx)
The last step in the mounting section involves the
componentDidMount method.
5.5.4 “componentDidMount”
With the methods used so far, you shouldn’t trigger any side
effects yet as they can be aborted by React, which in turn
can cause inconsistencies. You can trigger side effects using
the componentDidMount method. Typically, these consist of
asynchronous requests to a server or, as in our current
example, setting an interval:
import React from 'react';
export default class Timer extends React.Component {
interval = null;
constructor(props) {…}
static getDerivedStateFromProps(props, state) {…}
render() {…}
componentDidMount() {
console.log('componentDidMount');
this.interval = setInterval(
() => this.setState(state => ({ time: state.time + 1 })),
1000,
);
}
}
Listing 5.13 Implementation of the “componentDidMount” Method
(src/Timer.jsx)
In Listing 5.13, you output the information about the
execution of the method on the console again. Then you
create an interval that increments the time value of the state
by one every second. After you start your application on the
console, switch to the browser. There you should see a
result like the one shown in Figure 5.2.
Once the componentDidMount method has been run, the
component moves on directly to the updating section of its
lifecycle. The getDerivedStateFromProps and render methods
will continue to be executed on every update. These
updates are caused by the setState calls in the
componentDidMount method.
Figure 5.2 Display of the Lifecycle in the Browser
5.5.5 “shouldComponentUpdate”
You can use the shouldComponentUpdate method to specify
whether the component should be redrawn after a setState
call. If this method returns false, the render method won’t be
executed. The default value true ensures that the
component will be displayed anew. As arguments, you get
access to the current props and the new state. In the
example, you make sure that only time entries with an even
value are displayed:
export default class Timer extends React.Component {
interval = null;
constructor(props) {…}
static getDerivedStateFromProps(props, state) {…}
render() {… }
componentDidMount() {…}
shouldComponentUpdate(newProps, newState) {
console.log('shouldComponentUpdate');
return newState.time % 2 === 0;
}
}
Listing 5.14 Implementation of the “shouldComponentUpdate” Method
(src/Timer.jsx)
When you return to the browser after this change, you’ll see
in the console that the render method is only executed on
every other setState. The getDerivedStateFromProps and
shouldComponentUpdate methods are still called every second.
5.5.6 “getSnapshotBeforeUpdate”
The getSnapshotBeforeUpdate method enables you to
implement a hook that is executed after the render method
has been run and before the changes are displayed. In this
method, you have access to the previous state and props.
The return value of this method must be either null or a
value. This is then available in the componentDidUpdate
method. The getSnapshotBeforeUpdate method isn’t executed if
the shouldComponentUpdate method returns false.
export default class Timer extends React.Component {
interval = null;
constructor(props) {…}
static getDerivedStateFromProps(props, state) {…}
render() {…}
componentDidMount() {…}
shouldComponentUpdate(newProps, newState) {…}
getSnapshotBeforeUpdate(oldProps, oldState) {
console.log('getSnapshotBeforeUpdate');
return Date.now();
}
}
Listing 5.15 Implementation of the “getSnapshotBeforeUpdate” Method
(src/Timer.jsx)
In Listing 5.15, you merely generate the current timestamp
and return it. You can then access this value in the
componentDidUpdate method. This also marks the end of an
updating section.
5.5.7 “componentDidUpdate”
In the componentDidUpdate method, as in the componentDidMount
method, you can place side effects. The values of the
previous props and the previous state are available as
arguments in this method. You can use them to find out if
certain values have changed in the current updating
section. For this purpose, you can access the current values
of the props and the state, respectively, via the props and
state properties:
export default class Timer extends React.Component {
interval = null;
constructor(props) {…}
static getDerivedStateFromProps(props, state) {…}
render() {…}
componentDidMount() {…}
shouldComponentUpdate(newProps, newState) {…}
getSnapshotBeforeUpdate(oldProps, oldState) {…}
componentDidUpdate(oldProps, oldState, snapshot) {
console.log('componentDidUpdate');
if (oldState.initial !== this.state.initial) {
console.log(`${snapshot}: Time was reset`);
}
}
}
Listing 5.16 Implementation of the “componentDidUpdate” Hook
Figure 5.3 Updating Section of the Component
Listing 5.16 shows a concrete example of the
componentDidUpdate method. Here you check if the initial
property of the state has changed. If that’s the case—that
is, the button has been clicked on—then the browser
console will show that the time has been reset. It also
outputs the timestamp you created earlier in the
getSnapshotBeforeUpdate method.
Figure 5.3 shows the console output during the updating
section—in this case, also with the output after the users
have clicked on the button.
The last stage of a component's lifecycle is initiated when
the component is removed.
5.5.8 “componentWillUnmount”
A component can be removed by using conditional
rendering, which allows you to display a component only
under certain conditions. If the condition is no longer true,
the component is removed. However, if you allocate
resources within a component, you must ensure that these
resources will be released again when you unmount the
component. A classic example of such a resource is an
interval, like the one you start in the timer component. This
kind of cleanup work is carried out by the
componentWillUnmount method.
The componentWillUnmount method is called directly before
removing a component and receives no arguments. Inside
the method, you shouldn’t call the setState method again
because the component won’t be rendered again after the
execution of the componentWillUnmount method.
To demonstrate the componentWillUnmount method, you can
add an additional button in the App component that
alternately sets the show property of the component state to
true or false. You should set the default value of this
property to true in the component. Depending on the value
of the show property, you show or hide the timer component.
You can achieve this, for example, by using the && operator,
as shown in Listing 5.17:
class App extends Component {
state = {
time: 0,
show: true,
};
getClickHandler() {…}
handleToggleShow() {
this.setState((state) => ({ ...state, show: !state.show }));
}
render() {
return (
<div className="App">
{this.state.show && <Timer time={this.state.time} />}
<button onClick={() => this.handleClick()}>set</button>
<button onClick={() => this.handleToggleShow()}>toggle</button>
</div>
);
}
}
Listing 5.17 Conditional Rendering of the “timer” Component (src/App.jsx)
In the timer component, you implement the
componentWillUnmount method and use the clearInterval
function to end the interval. You can see the required source
code in Listing 5.18:
export default class Timer extends React.Component {
interval = null;
constructor(props) {…}
static getDerivedStateFromProps(props, state) {…}
render() {…}
componentDidMount() {…}
shouldComponentUpdate(newProps, newState) {…}
getSnapshotBeforeUpdate(oldProps, oldState) {…}
componentDidUpdate(oldProps, oldState, snapshot) {…}
componentWillUnmount() {
console.log('componentWillUnmount');
clearInterval(this.interval);
}
}
Listing 5.18 Implementation of the “componentWillUnmount” Method
(src/Timer.jsx)
5.5.9 Unsafe Hooks
The hook methods presented here have been available in
this form only since version 16.3. Prior to that release, there
were other methods that are now marked as deprecated
and will be removed in future releases. With the deprecated
methods, some new features cannot be implemented—such
as asynchronous rendering, for example. For this reason, the
new lifecycle was introduced.
The methods that will be removed are as follows:
componentWillMount
This method is executed in the mounting section before
the render method.
componentWillReceiveProps
The componentWillReceiveProps method is called before the
props are updated. The argument provides access to the
new props.
componentWillUpdate
This method is executed once the props and state have
been updated and before the component is rendered.
These lifecycle methods are given the prefix UNSAFE_ to
indicate that problems may occur when they are used. The
existing components can be automatically updated using
the rename-unsafe-lifecycles codemod. For more information
on this and other codemods, visit
https://github.com/reactjs/react-codemod.
The different lifecycle methods are used in React
applications. A typical example is communication with a
server to load the data for display.
5.6 Error Boundaries
In addition to the lifecycle methods presented so far, there
are two other methods that have less to do with the lifecycle
of a component directly and more with the handling of
lifecycle errors and with the render method. Usually, you
should provide an appropriate error-handling routine within
your application at the points where errors may occur.
Uncaught errors cause the entire component tree to be
removed. This means that you must take care of error
handling in any case. The getDerivedStateFromError and
componentDidCatch methods provide a means for general error
handling. If a class component implements one method or
both, it’s referred to as an error boundary. The name refers
to the fact that within the render method of a component,
other components can be included and these themselves
can cause errors. Thus, an error boundary takes care of not
only its own errors, but also the errors of its child
components.
import React from 'react';
class ErrorComponent extends React.Component {
render() {
throw new Error('oh no!');
return <h1>Error Component works!</h1>;
}
}
export default ErrorComponent;
Listing 5.19 Component that Throws an Exception in the “render” Method
(src/ErrorBoundary.jsx)
If you include the ErrorComponent component from
Listing 5.19 in the App component of your application, you’ll
only see a blank page and an error message in the console,
as shown in Figure 5.4. The following sections describe how
you can deal with such faulty components.
Figure 5.4 Output of a Faulty Component
5.6.1 Logging Errors Using
“componentDidCatch”
The componentDidCatch method is not well-suited for error
handling, but rather for logging errors. This method is
implemented in a class method and receives two
parameters. The first one provides access to the thrown
error in the form of an object. The second parameter is a so-
called component stack trace. This parameter indicates
where exactly in the component tree the error occurred. In
Listing 5.20, you can see the source code of the App
component that contains the ErrorComponent component. In
addition, the App component implements the
componentDidCatch method:
import React from 'react';
import './App.css';
import ErrorComponent from './ErrorComponent';
class App extends React.Component {
componentDidCatch(error, info) {
console.log('*'.repeat(20));
console.log(error, info);
console.log('*'.repeat(20));
}
render() {
return <ErrorComponent />;
}
}
export default App;
Listing 5.20 Implementation of an Error Boundary (src/App.tsx)
Figure 5.5 contains the output of the componentDidCatch
method.
Figure 5.5 Output of the “componentDidCatch” Method
The componentDidCatch method is executed in the commit
phase of the component. This means that side effects are
allowed, such as communicating with a server to store the
information. As mentioned previously, the componentDidCatch
method is only used for logging errors. This means that the
application won’t catch the thrown error, which will cause
the application to terminate.
5.6.2 Alternative Representation in Case of
an Error with “getDerivedStateFromError”
You can use the getDerivedStateFromError method to catch
errors and make sure that your application continues to
function and produce meaningful output even if an error
occurs. This static method allows you to set the state of the
component. Within this method, you can access the error
via the parameter. In Listing 5.21, you make sure that the
application displays an appropriate message instead of the
component tree to the user in case of an error:
import React from 'react';
import './App.css';
import ErrorComponent from './ErrorComponent';
class App extends React.Component {
state = {
error: null,
};
static getDerivedStateFromError(error) {
return {
error: error.message,
};
}
componentDidCatch(error, info) {
console.log('*'.repeat(20));
console.log(error, info);
console.log('*'.repeat(20));
}
render() {
if (this.state.error) {
return <div>An error has occurred:
{this.state.error}</div>;
} else {
return <ErrorComponent />;
}
}
}
export default App;
Listing 5.21 Use of the “getDerivedStateFromError” Method (src/App.jsx)
The getDerivedStateFromError method returns a new state
object for the component. Due to this state, you can display
the error message inside the render method. In addition to
this method, you can also use the componentDidCatch method
to log the error message.
Figure 5.6 Catching the Error Using the “getDerivedStateFromError” Method
Based on the features presented so far, you now know
almost everything you need to know about class
components. The last important building block of a React
application you can use directly in your class component is
the Context API.
5.7 Using the Context API in a Class
Component
The Context API allows you to access centralized
information, independent of the props. For this purpose, you
must first create a context. In Listing 5.22, you can see how
to create the context using the createContext function:
import { createContext } from 'react';
export const BooksContext = createContext([]);
Listing 5.22 Creating “BooksContext” (src/BooksContext.jsx)
You include this context in the App component of your
application and place a data record in the context. The
corresponding source code of the App component is shown
in Listing 5.23:
import React from 'react';
import './App.css';
import { BooksContext } from './BooksContext';
import BooksList from './BooksList';
class App extends React.Component {
state = {
books: [
{
id: 1,
title: 'JavaScript - The Comprehensive Guide',
author: 'Philip Ackermann',
isbn: '978-3836286299',
rating: 5,
},
],
};
render() {
return (
<BooksContext.Provider value={this.state.books}>
<BooksList />
</BooksContext.Provider>
);
}
}
export default App;
Listing 5.23 Integrating the Context into the Application (src/App.jsx)
In the App component, you integrate BooksContext.Provider
and assign it the state with the data array as a value. This
allows all child components, such as the BooksList
component, to access it. You can see the source code of the
BooksList component with context access in Listing 5.24:
import React from 'react';
import { BooksContext } from './BooksContext';
class BooksList extends React.Component {
static contextType = BooksContext;
render() {
return (
<ul>
{this.context.map((book) => (
<li key={book.id}>{book.title}</li>
))}
</ul>
);
}
}
export default BooksList;
Listing 5.24 Accessing the Context from within a Class Component
(src/BooksList.jsx)
In a class component, you first define a static property
named contextType for accessing the context. To this
property, you assign the BooksContext object. This configures
the component so that React gives you access to the
context directly through the context property of the class.
5.8 Differences between Function
and Class Components
In a modern React application, you’ll no longer find class
components, and there are good reasons for this. One of the
most obvious differences is the syntax and the overhead
that comes with it. Function components are more
lightweight. In the simplest case, you define a function and
return a JSX structure. You don’t need to import React.
The difference between function and class components
becomes really clear when it comes to state and lifecycle.
5.8.1 State
In a class component, you have a defined state property and
the setState method to set the state. Changing the state will
cause React to render the component anew.
Function components work similarly. You use the useState
function, which returns an array for reading and writing the
state. The big difference between the two approaches is
that you can define multiple state fragments in a function
component. This gives you more flexibility and allows you to
name the state appropriately, making the source code of
your application more understandable.
5.8.2 Lifecycle
Things look similar for the lifecycle. In a class component,
you have several methods that allow you to intervene in the
lifecycle of a component in a very fine-grained manner.
For function components, you use the useEffect function and
can respond to mounting, updating, and unmounting a
component. The dependency array and cleanup routines
give you additional flexibility.
Unlike the lifecycle methods of the class component, one of
which is available to you for each phase of the lifecycle, you
can define several specialized lifecycle functions via the
useEffect function. This also makes your source code more
flexible, readable, and maintainable.
5.9 Summary
In this chapter, you learned about the class components of
React. Although you should avoid these types of
components in general, you cannot always do so, especially
in older legacy applications, and therefore it’s worth
knowing the peculiarities of class components:
Class components are JavaScript classes that derive from
the React.Component class and have at least one render
method.
Like function components, class components accept the
passing of information from the parent component in the
form of attributes called props. You can access the values
of the props via this.props.
Class components can have their own state. You have
read access to it via this.state and write access via the
setState method. A change causes React to re-render the
component.
Using various lifecycle methods, you can respond to the
phases of the component's lifecycle. One of the most
important methods is componentDidMount, which is executed
by React once the component has been mounted. In this
method, you can trigger side effects, such as
communication with a web server.
You can use a combination of the static contentType
property and the context property of the component class
to access the Context API of React.
You can use error boundaries for logging or catching
errors.
Function components are more flexible and lightweight
compared to class components. They also allow multiple
small state fragments and multiple lifecycle functions.
In the next chapter, you'll learn more about the Hooks API of
React, the interface that led to the replacement of class
components.
6 The Hooks API of React
Version 16.8 introduced a comparatively extensive
extension to React: the Hooks API. For many React
developers, the Hooks API marks a turning point in writing
React applications. Some consider the Hooks API to be so
important that there is even talk of moving from ReactJS to
React, as was the case with Angular during the transition
from version 1 to version 2. However, Angular has been
rewritten from the ground up. In contrast, the Hooks API is
merely an extension. The foundation for this strategic
expansion of the library was laid by the integration of the
new Fiber reconciler.
At this point, the following question arises: If the Hooks API
is such a significant enhancement, why hasn't the version
number been increased to 17? React follows the semantic
versioning approach, which states that a breaking change
leads to an increase in the major version. However, such a
change isn’t present in the Hooks API. Indeed, you can
implement your application completely without hooks and
with no fear of any restrictions.
The Hooks API represents an upgrade to the functional
components. Prior to the introduction of the new interface, it
was only possible to implement a local state and access
lifecycle methods in class components. These and other
restrictions have been largely removed by the hooks. The
reasons for introducing hooks are primarily the following:
Reduction of the component scope
Often, it isn’t possible without a problem to swap out the
state and the associated methods of a component and
thus reduce the complexity with the existing means. Due
to the Hooks API, it becomes possible to split an extensive
state into several substates and manage them separately.
Removal of duplicates
Previously, the state was tightly bound to a class
component. It wasn’t possible to reuse the structure and
methods for manipulating the state in multiple places. You
can also find a solution for this with the Hooks API.
Replacement of complex design patterns
With higher-order components and render props, you’ve
gotten to know two design patterns that help you extend
the functionality of components. Custom hooks provide an
alternative in this context.
Throughout this chapter, you’ll learn about the benefits and
architectural approaches of the Hooks API and how you can
use it in your application. By using hooks, you can swap out
the logic from a component to smaller units. React
applications have always been characterized by a clearly
modeled data flow and the composition of components. You
can pursue these design patterns not only between the
individual components of your application, but also within a
component, thanks to the Hooks API.
The advantage of the Hooks API in conjunction with function
components over class components is that you can
implement a local and self-contained state through the
Hooks API. The introduction of hooks is intended to
significantly lower the barrier to entry. In addition, the
features to be learned should be reduced somewhat in the
long term. Function components are generally easier to
understand and do not have problems related to context—
that is, the this within a component.
6.1 A First Overview
What you’ll quickly notice when dealing with hooks is the
naming convention: Hook functions always start with the
word use, followed by the actual name of the hook. The
name of the state hook is therefore useState. Hooks are
divided into two categories: basic hooks and additional
hooks.
6.1.1 The Three Basic Hooks
You already know about the three most important hooks of
the Hooks API from the previous chapters. They are grouped
together in the basic hooks category:
useState
The state hook makes sure that you can implement a
local state within a function component. This state
survives several render cycles.
useEffect
The effect hook can be used to emulate some lifecycle
methods. This hook allows you to respond to the
mounting of a component, changes to a component, and
the unmounting of a component.
useContext
The context hook provides convenient access to the
Context API of React. In the context, you can store objects
in a central location and access them from elsewhere.
In addition to these three basic hooks, there are several
other hooks that can be useful when implementing an
application.
6.1.2 Other Components of the Hooks API
The three basic hooks are often referred to in discussions
about hooks in the React context. However, it's also worth
looking at the other hooks provided by React. The individual
hooks are as follows:
useReducer
The reducer hook serves a similar purpose as the state
hook: it provides a local state in a function component.
The difference between the two is that with the reducer
hook, you use the Flux architecture to modify the state.
useCallback
This hook returns a memoized callback function that can
be used for performance optimization.
useMemo
Like the callback hook, the memo hook is used to improve
performance and returns a memoized value.
useRef
You can use the ref hook to control access to JSX
elements. Refs denote references to HTML elements in
the React environment.
useImperativeHandle
This rather rarely used hook is used to customize the
interface of a component when it’s used as a ref.
useLayoutEffect
The layout effect hook is similar to the effect hook for
handling side effects and lifecycle functions. The layout
effect hook has the special feature that the side effects
are executed synchronously after all DOM changes.
useDebugValue
This hook is used to generate output on the developer
tools.
useDeferredValue
Using the deferred value hook, you can add a delay. In the
case of time-consuming operations, you sometimes have
to introduce an artificial delay. React takes care of that for
you with this hook. However, the delay depends on the
rendering process and not on external factors.
useTransition
This hook allows you to mark an interface update as
noncritical so that React can decide which update to give
preference to.
useId
This hook creates unique IDs that you can use in various
places in your components.
In addition to the extended Hooks API, there are two hook
functions aimed at library authors:
useSyncExternalStore
You can use this hook to connect external data sources.
useInsertionEffect
This hook is similar to the other two effect hooks.
However, React executes it before any DOM changes.
Memoization
In software development, the term memoization refers to
the caching of a value. For example, if you memoize a
callback function, the return value is cached and returned
directly when called again, rather than being recalculated.
Memoization can result in a significant performance
improvement, especially when you consider that React's
render functions may be run very frequently. If you define
function objects within such a render function, such as for
event handlers, this means that these function objects
must be generated anew for each render process.
You already know the three basic hooks from the previous
chapters. In Chapter 3, you saw how to use useState in your
function components. In Chapter 4, you looked at the use of
useEffect and useContext, among other things.
In this chapter, we'll look at the other hook features React
makes available to you. The reducer hook will start us off.
6.2 “useReducer”: The Reducer
Hook
Quick Start
const [state, dispatch] = useReducer(state, reducer);
Listing 6.1 Syntax of the Reducer Hook
The reducer hook works like the state hook, with the
difference that you do not manipulate the state directly
via a setter function but use the dispatch function to
dispatch action objects. You process the action objects in
the reducer function and create a new state on this basis.
You can use the reducer hook as a replacement for the state
hook. Listing 6.2 shows an implementation of the BooksList
component that creates a local state via the useReducer
function. The only operation allowed by this component is to
evaluate the entries.
Import { useReducer } from 'react';
import produce from 'immer';
import { StarBorder, Star } from '@mui/icons-material';
function reducer(state, action) {
switch (action.type) {
case 'RATE':
return produce(state, (draftState) => {
const index = draftState.findIndex(
(book) => book.id === action.payload.id
);
draftState[index].rating = action.payload.rating;
});
default:
return state;
}
}
const initialBooks = [
{
id: 1,
title: 'JavaScript - The Comprehensive Guide',
author: 'Philip Ackermann',
isbn: '978-3836286299',
rating: 5,
},
…
];
function BooksList() {
const [books, dispatch] = useReducer(reducer, initialBooks);
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th></th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>
{new Array(5).fill('').map((item, i) => (
<button
className="ratingButton"
key={i}
onClick={() =>
dispatch({
type: 'RATE',
payload: { id: book.id, rating: i + 1 },
})
}
>
{book.rating < i + 1 ? <StarBorder /> : <Star />}
</button>
))}
</td>
</tr>
))}
</tbody>
</table>
);
}
export default BooksList;
Listing 6.2 Using the Reducer Hook (src/BooksList.jsx)
The core of this component, which renders a list of books, is
the call of the useReducer function. When calling it, you pass
a reducer function and an initial state to it. As a return, you
get an array with two elements. The first element provides
read access to the state, similar to the state hook. The
second element of the array is the dispatch function.
6.2.1 The Reducer Function
The reducer function is the eponymous element of the
reducer hook. Like the setter function of the state, it is a
function that gets the previous state as well as an action
object. The action object describes the change to be made
to the state. The core of the reducer function is a switch
statement in which you decide to what branch you want to
jump based on the type property of the action object. In the
case of the RATE type, you use Immer to create a copy of the
state and set the new rating for the specified record. Then
you return the modified state as the value of the reducer
function.
6.2.2 Actions and Dispatching
The second element that makes up the reducer hook is an
action. Actions are simple JavaScript objects that describe a
change to the state. There is a best practice that such an
action object should have a type property as well as a payload
property. You use the type property to decide which
operation you want to perform in the reducer function. The
payload property can have any data structure required to
control the operation. In the case of the rating example, the
payload property contains an object with the ID of the
affected record and the new rating value.
What’s still missing is the connection between the action
object and the reducer function. This is where the dispatch
function comes into play. You get this function as the second
element of the return value of the useReducer function. The
dispatch function makes sure that the reducer function is
executed along with the action object that has been passed
in order to produce a new state and re-render the
component. In the example, you call the dispatch function in
the click handler of the rating buttons.
The rating example contains only one operation, and it is
synchronous—for good reason. It isn’t possible to handle
asynchronicity within the reducer function. In the next
section, you’ll learn how to deal with asynchronous
operations.
6.2.3 Asynchronicity in the Reducer Hook
You have several options to deal with asynchronicity:
The first and simplest option is to first perform the
asynchronous operation (e.g., loading or writing data) and
then dispatch a corresponding action. However, by doing
so, you lose the advantage of the reducer hook, which
allows you to pull operations out of your component and
encapsulate them cleanly.
The second variant is to attach one or more effect hooks
to your reducer hook, which respond to the corresponding
state changes and encapsulate the asynchronous side
effects. However, this variant makes the source code
significantly more complex, and the dependencies
between the state and the effect hooks are not
necessarily always directly obvious.
The third and most elegant variant is that you create an
additional wrapper function to wrap the reducer hook,
handle the asynchronicity, and dispatch the action.
Listing 6.5 shows the implementation of such a
middleware option that allows for loading and evaluating
data records with a server.
Due to the asynchronous middleware, this example is a bit
more extensive and is divided into three parts: middleware
function, reducer function, and the BooksList component. For
a better understanding, let's look at the three parts
separately. To test the code yourself, you’ll want to put the
individual parts together in one file.
import { useReducer, useMemo, useEffect } from 'react';
import produce from 'immer';
import { StarBorder, Star } from '@mui/icons-material';
function middleware(dispatch) {
return async function (action) {
// eslint-disable-next-line default-case
switch (action.type) {
case 'FETCH':
const fetchResponse = await fetch(
`${process.env.REACT_APP_API_SERVER}/books`
);
const books = await fetchResponse.json();
dispatch({ type: 'LOAD_SUCCESS', payload: books });
break;
case 'RATE':
await fetch(
`${process.env.REACT_APP_API_SERVER}/books/${action.payload.id}`,
{
method: 'PUT',
headers: { 'Content-Type': 'Application/JSON' },
body: JSON.stringify(action.payload),
}
);
dispatch({
type: 'RATE_SUCCESS',
payload: { id: action.payload.id
rating:action.payload.rating },
});
break;
}
};
}
Listing 6.3 Middleware Function (src/Bookslist.jsx)
The middleware function accepts a reference to the dispatch
function you get from the reducer hook as a return value as
an argument, and in turn returns a function itself. This
function assumes the role of the dispatch function and is
called with an action object. The function body consists of a
switch statement that decides what needs to be done based
on the action passed. In the example, you implement cases
for actions using the FETCH and RATE types. In the case of
FETCH, you load the data for the display from the server
interface and then execute the dispatch function with a new
action object. This is of type FETCH_SUCCESS and contains the
loaded data as payload. The action is then processed by the
actual reducer function.
The second action supported by the middleware is of the
RATE type. In this case, you perform an asynchronous write
operation on the server. To do this, you use the PUT method
and configure the request so that it has the correct content
type and contains the modified record as the payload. If the
server reports a success of the write operation, you dispatch
a RATE_SUCCESS action, which in turn is further processed by
the reducer function.
In the next step, you implement the reducer function that
takes care of processing the FETCH_SUCCESS and RATE_SUCCESS
actions. Listing 6.4 contains the source code of the function:
function reducer(state, action) {
switch (action.type) {
case 'LOAD_SUCCESS':
return action.payload;
case 'RATE_SUCCESS':
return produce(state, (draftState) => {
const index = draftState.findIndex(
(book) => book.id === action.payload.id
);
draftState[index].rating = action.payload.rating;
});
default:
return state;
}
}
Listing 6.4 Reducer Function (src/BooksList.jsx)
As with previous implementations, this reducer function is
completely synchronous in design. The middleware function
triggers the reducer function by calling the dispatch function.
In the case of the FETCH-SUCCESS action, you return the data of
the action object stored in the payload property.
If your application triggers the RATE_SUCCESS action, you use
the Immer library to create a copy of the previous state and
change the rating of the affected record.
Using these two functions, you have done all the
preparatory work and can move onto implementing the
component. This component is responsible for the state,
must trigger the loading of the data, and displays the list of
books. Listing 6.5 contains the implementation of the
BooksList component:
function BooksList() {
const [books, dispatch] = useReducer(reducer, []);
const middlewareDispatch = useMemo(() => middleware(dispatch), [dispatch]);
useEffect(() => {
middlewareDispatch({ type: 'FETCH' });
}, [middlewareDispatch]);
return (
<table>
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th></th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>
{new Array(5).fill('').map((item, i) => (
<button
className="ratingButton"
key={i}
onClick={() =>
middlewareDispatch({
type: 'RATE',
payload: { ...book, rating: i + 1 },
})
}
>
{book.rating < i + 1 ? <StarBorder /> : <Star />}
</button>
))}
</td>
</tr>
))}
</tbody>
</table>
);
}
export default BooksList;
Listing 6.5 “BooksList” Component with Reducer Hook and Middleware
(src/BooksList.jsx)
In the first step, you call the useReducer function to get
access to the state and dispatch function. As the initial value,
you pass an empty array. This results in the component not
displaying any data at first. Then you call the middleware
function and pass the dispatch function to it. At this point,
you use the useMemo function that ensures the middleware
function will not be recreated for each render operation.
Finally, using the effect hook, you make sure that the data
for the display is loaded from the server. You call the
middlewareDispatch function with a FETCH action object, which
is first processed by the middleware and then sets the state
via the reducer function, leading to the display of the data.
In the table, you represent the individual data records, and
when a rating star is clicked on, you make sure that the RATE
action will be processed by the middleware. Again, the
middleware triggers the server communication and then the
RATE_SUCCESS action, which then leads to the update of the
representation via the reducer.
6.3 “useCallback”: Memoizing
Functions
Quick Start
You can use the useCallback function to create a memoized
version of a function that React recreates only when one
of the specified dependencies changes.
const func = useCallback((params) => { /*logic*/ }, [dependency]);
Listing 6.6 Syntax of the “useCallback” Function
Listing 6.6 shows how to use the useCallback function to
memoize the passed function. If the dependency variable
changes, React creates a new version of the function and
stores it in the func variable.
The callback hook is intended to optimize the rendering
process. If you define a function within a component, it will
be created again during each render process. In most cases,
this is unnecessary. You can use the useCallback function to
make React reuse the function unless certain aspects of the
function change that require the function to be created.
Memoizing functions is especially interesting if you have a
lot of functions in your component tree and this becomes a
noticeable performance problem during render operations.
You shouldn’t try to optimize every callback function in your
components using the callback hook as it often happens
that the hoped-for effect fails to materialize and the source
code becomes less readable. In Listing 6.7, you can see an
example of using the callback hook:
import { useCallback } from 'react';
import Rating from './Rating';
function BooksListItem({ book, onRate }) {
const handleRate = useCallback(
(event) => {
const rating = event.target.closest(
'[data-value]')?.dataset.value;
if (rating) {
onRate(book.id, parseInt(rating, 10));
}
},
[book.id, onRate]
);
return (
<tr>
<td>{book.title}</td>
<td>{book.author ? book.author : 'Unknown'}</td>
<td>{book.isbn}</td>
<td onClick={handleRate}>
<Rating item={book} />
</td>
</tr>
);
}
Listing 6.7 Using the “useCallback” Function (src/BooksListItem.jsx)
The component is the BooksListItem component, which is
responsible for displaying a table row with a data record.
The component has the rating component as a child
component, which is responsible for displaying the rating
and the option to change it. The BooksListItem component
has a handleRate function that receives a click event and
extracts the rating value from it, which is located in the
data-value property of the clicked-on HTML element. Usually,
this function is regenerated with each render operation.
If you use the useCallback function, React regenerates the
function only if the ID of the record or the onRate function
changes. The function needs both to do its job correctly. For
this reason, you should name the information in the
dependency array. The return value of the useCallback
function is the memoized original function, which you can
use like an ordinary function in your application.
6.4 “useMemo”: Memoizing Objects
Quick Start
You can use the memo hook to create a memoized version
of any object structure. The functionality is similar to that
of useCallback:
const memoObject = useMemo(() => doSomething(obj), [obj]);
Listing 6.8 Syntax of the “useMemo” Function
In the case of Listing 6.8, React stores the return value of
the doSomething function in the memoObject variable and
regenerates it if the obj dependency changes.
You’ve already seen the use of the useMemo function in this
chapter when we talked about the asynchronous
middleware in the reducer hook.
function reducer(state, action) {…}
function middleware(dispatch) {…}
function BooksList() {
const [books, dispatch] = useReducer(reducer, []);
const middlewareDispatch = useMemo(
() => middleware(dispatch), [dispatch]
);
useEffect(() => {
middlewareDispatch({ type: 'FETCH' });
}, [middlewareDispatch]);
…
return (
<table>…</table>
);
}
Listing 6.9 Using the “useMemo” Function (src/BooksList.jsx)
The middleware function from Listing 6.9, to which you pass
the dispatch function, creates the middlewareDispatch function
that you can use in your component to trigger actions. You
only need to regenerate the middlewareDispatch function if the
dispatch function changes. The middlewareDispatch variable
then contains a memoized variant of the generated
function. You can then use this function in the effect hook,
where you can also name it as a dependency as the function
doesn’t change between the individual render operations.
If you were to run the middleware function to create the
middlewareDispatch function without useMemo, the result would
be that React would recreate the function on every render
and thus also rerun the effect hook to load the data.
Note
useCallback and useMemo are very similar. You can use the
useMemo function to simulate the same behavior as
useCallback. useCallback(func, deps) is the same as useMemo(()
=> func, deps).
6.5 “useRef”: References and
Immutable Values
Quick Start
The ref hook allows you to create a changeable reference.
This will outlast the render operations of your component,
similar to state. However, changing the reference doesn’t
cause the component to be rendered again.
const ref = useRef(null);
ref.current = 'myValue';
Listing 6.10 Using the Ref Hook
You can pass an initial value for the ref to the useRef
function. Then you use the value via the current property
of ref, and you have both read and write access to it.
You can use the ref hook in two very different ways. On the
one hand, you can use it to manage references to HTML
elements, and on the other hand, you can keep values that
are not reset by re-rendering the component. Let's first turn
our attention to references to HTML elements in the shape
of form elements.
6.5.1 Form Handling Using the Ref Hook
React takes a declarative approach to generating graphical
interfaces. Direct access to HTML elements is a sign of poor
application design. However, there are cases when you
need to access the HTML structure. An example of this is a
variant of form handling:
import { useState, useRef } from 'react';
import './App.css';
function App() {
const [name, setName] = useState('');
const inputRef = useRef();
function handleChange() {
setName(inputRef.current.value);
}
return (
<div>
<div>{name}</div>
<input type="text" ref={inputRef} onChange={handleChange} />
</div>
);
}
export default App;
Listing 6.11 Form Handling Using the “useRef” Function (src/App.jsx)
The code in Listing 6.11 shows the state of the component
and an input field. In addition to the state, you also define a
ref at the beginning, which you store in the inputRef variable.
In the JSX structure, you use the ref prop of the input
element to connect the ref to the input element. In the
change handler of the input element, you can then access
the element via inputRef.current and use the value property
to read the current value of the element and write it to the
state. React then re-renders the component and displays
the new value.
6.5.2 Caching Values Using the Ref Hook
The second use for the ref hook is to store information
similar to the state hook, except that changing the ref
doesn’t cause the component to be re-rendered. You’ve
already learned about using the ref hook in Chapter 4.
Here’s another short example with a timer component that
creates an interval and deletes it when the component
becomes unhooked:
import { useState, useEffect, useRef } from 'react';
import './App.css';
function App() {
const [showTimer, setShowTimer] = useState(true);
useEffect(() => {
setTimeout(() => setShowTimer(false), 5000);
});
return <div>{showTimer && <Timer />}</div>;
}
export default App;
function Timer() {
const [time, setTime] = useState(0);
const intervalRef = useRef(null);
useEffect(() => {
intervalRef.current = setInterval(
() => setTime((prevTime) => prevTime + 1),
1000
);
return () => clearInterval(intervalRef.current);
}, []);
return <div>{time}</div>;
}
Listing 6.12 Storing Values in Ref Hook (src/App.jsx)
The example consists of two components: the App
component and the timer component. The App component
has a Boolean state that determines whether the timer
component is displayed or not. After five seconds, the value
is set to false via an effect hook so that the timer component
is unhooked.
The timer component also has its own state, which stores
the time in seconds from the point at which the component
was mounted. You also create a ref where you store the
handle, which refers to the interval. In the effect hook of the
component, you start the interval and update the state once
every second. In the cleanup routine of the effect hook, you
call the clearInterval function with the value from the ref.
This will ensure that the interval isn’t accidentally left open
to cause potential memory problems.
6.6 “useImperativeHandle”:
Controlling Forward Refs
Quick Start
You can use the useImperativeHandle function to replace the
object in a forward ref. For this purpose, you pass the ref
object and a function that replaces it. In addition, you can
pass optional dependencies, which, if changed, will cause
the function to be executed again and thus regenerate the
object.
useImperativeHandle(ref, createHandle, [deps])
Listing 6.13 Syntax of the “useImperativeHandle” Function
The useImperativeHandle function gives you better control
over forward refs. To that end, let's first look at this feature
of React before looking at the actual hook function.
6.6.1 Forward Refs
The concept of forward refs refers to a pattern where you
pass a ref to an element from a child component to a parent
component. To make this work, you use the forwardRef
function and pass to it the child component that contains
the element to be referenced. In Listing 6.14, you can see
the child component:
import { forwardRef } from 'react';
function Input({ label }, ref) {
return (
<div>
<label>{label}</label>
<input type="text" ref={ref} />
</div>
);
}
export default forwardRef(Input);
Listing 6.14 Child Component with “forwardRef” (src/Input.jsx)
The Input component from the example accepts a label prop
as the label of the input field, and via the forwardRef function
that surrounds it, the component function receives the
forward ref as the second argument. You then associate this
with the ref attribute of the input element.
In the next step, you’ll see in Listing 6.15 how you can
access the ref of the input element from the parent
component:
import { useState, useRef } from 'react';
import Input from './Input';
import './App.css';
function App() {
const ref = useRef(null);
const [name, setName] = useState('');
function handleClick() {
setName(ref.current.value);
}
return (
<div>
<div>Hello {name}!</div>
<Input title="Name: " ref={ref} />
<button onClick={handleClick}>say hello</button>
</div>
);
}
export default App;
Listing 6.15 Integration of the “Input” Component via “ForwardRef”
(src/App.jsx)
In the parent component, the App component in this case,
you first define a ref and then the state you want to use for
the display. In the JSX structure, you show the state, and
you also include the Input component and pass it the title
prop and the ref via the ref prop. Finally, you define a button
element to whose click event you bind a function that reads
the value from the ref and writes it to the state.
This will pass the reference to the input element from the
Input component to the parent component, and you can
access it directly from there.
6.6.2 The Imperative Handle Hook
You can use the ImperativeHandle function to further influence
the behavior of the forward ref by overriding the ref object.
In Listing 6.16, you’ll first see the implementation of the
Input component:
import { useRef, forwardRef, useImperativeHandle } from 'react';
function Input({ label }, ref) {
const inputRef = useRef(null);
useImperativeHandle(ref, () => ({
getUCValue() {
return inputRef.current.value.toUpperCase();
},
}));
return (
<div>
<label>{label}</label>
<input type="text" ref={inputRef} />
</div>
);
}
export default forwardRef(Input);
Listing 6.16 Using the Imperative Handle Hook (src/Input.jsx)
In the child component, you first define your own ref to
access the input element. Then you call the
useImperativeHandle function with the ref passed to the
component and a function. This function creates an object
with a getUCValue function that returns the current value of
the input element and converts it to uppercase.
Due to the use of the useImperativeHandle function, you can
no longer directly access the value property of the input
element in the parent component. However, you can run the
getUCValue function instead, as shown in Listing 6.17:
import { useState, useRef } from 'react';
import Input from './Input';
import './App.css';
function App() {
const ref = useRef(null);
const [name, setName] = useState('');
function handleClick() {
setName(ref.current.getUCValue());
}
return (
<div>
<div>Hello {name}!</div>
<Input title="Name: " ref={ref} />
<button onClick={handleClick}>say hello</button>
</div>
);
}
export default App;
Listing 6.17 Integration of the “Input” Component via the
“ImperativeHandle” Hook in the Parent Component (src/App.jsx)
6.7 “useLayoutEffect”: The
Synchronous Alternative to
“useEffect”
Quick Start
The useLayoutEffect function works exactly like the
useEffect function, but with the difference that React
executes useEffect asynchronously and useLayoutEffect
synchronously.
The useLayoutEffect function is one of the hook functions
you’ll come across quite rarely. As a rule, you’ll useEffect and
do well with it. The difference between the two functions is
the time of execution. In the effect hook, React renders a
component. Then the rendering is updated, and only then
does React execute the effect hook asynchronously. So the
effect hook doesn’t block the display.
With the layout effect hook, React also renders the
component, but then executes the layout effect—and only
after this is complete does React update the rendering.
One situation where you should use the layout effect hook
instead of the effect hook is when your component flickers
during rendering—that is, it renders first and then
immediately renders again.
6.8 “useDebugValue”: Debugging
Information in React Developer Tools
To use the debug value hook, you must have React
Developer Tools installed and enabled in your browser.
These are available for all Chrome-based browsers as well
as Firefox. For this hook, I'm getting a little ahead of myself.
You can use the debug value hook in conjunction with
custom hooks, which you’ll learn about later in this chapter.
The idea is to swap out React hooks into separate functions,
which in turn can be used in components. Listing 6.18
contains an example of a custom hook function. This
function swaps out the state as well as the effect of a
component into a function named useName:
import { useState, useEffect, useDebugValue } from 'react';
import './App.css';
function useName() {
const [name, setName] = useState('');
useDebugValue(`Name is ${name}`);
useEffect(() => {
setTimeout(() => {
setName('React');
}, 5000);
});
return name;
}
function App() {
const name = useName();
return <div>{name}</div>;
}
export default App;
Listing 6.18 Using “useDebugValue” in a Custom Hook (src/App.jsx)
You can use the useDebugValue function to create additional
labels and debugging output in React Developer Tools. In
this case, you output the value of the name state. The dev
tools show you the string directly and update it if the value
changes. To generate the output as shown in Figure 6.1, go
to the Components tab of your browser's dev tools and
select the App component there. By default, the hooks
section then shows you only the name of the hook—that is,
Name. However, via the debug value hook, you will then see
the additional debugging output, which may include, for
example, certain properties of state objects or the like.
Figure 6.1 Output of the Debug Value Hook in React Developer Tools
With React 18, some additional hook features have made
their way into the core of React. In the following sections,
you’ll learn more about these.
6.9 “useDeferredValue”:
Performing Updates According to
Priority
Quick Start
You pass a state value and a timeout in milliseconds to the
useDeferredValue function. React then uses the previous
value up to a maximum of this timeout value. This gives
the library the opportunity to process changes with higher
priority and only then take care of the less important
update.
const deferredValue = useDeferredValue(name, {timeoutMs: 1000});
Listing 6.19 Syntax of the “useDeferredValue” Function
With React 16, numerous changes to the core of React were
introduced. Some of these changes appeared directly as
new features. Other customizations have only gradually
found their way into applications. An important feature of
React is that the library recognizes different priorities of
changes and that it’s possible to pause, resume, or restart
the render process. Only a few applications and libraries
have made use of this feature so far. However, the
useDeferredValue function now enables you to intervene
directly in the rendering process.
In the following example, you’ll implement the display of a
book list, where you have the option to filter the entries. For
the example to work, you need a JSON file with data as
shown in Listing 6.20. You save this file as data.json in the
root directory of your application.
{
"books": [
{
"id": 1,
"title": "JavaScript - The Comprehensive Guide",
"author": "Philip Ackermann",
"isbn": "978-3836286299",
"rating": 5
},
{
"id": 2,
"title": "Clean Code",
"author": "Robert Martin",
"isbn": "978-0132350884",
"rating": 4
},
{
"id": 3
"title": "Design Patterns",
"author": "Erich Gamma",
"isbn": "978-0201633610",
"rating": 5
}
]
}
Listing 6.20 Data for the Server Interface (data.json)
You can then start the server process from the command
line using the npx json-server -p 3001 -w data.json command.
Then you can implement the App component, which contains
the search form and integrates the BooksList component that
takes care of displaying the list. The source code of the App
component is shown in Listing 6.21:
import { useState, useDeferredValue, useMemo } from 'react';
import BooksList from './BooksList';
import './App.css';
function App() {
console.log('render');
const [searchString, setSearchString] = useState('');
const deferredSearchString = useDeferredValue(searchString, {
timeoutMs: 1000,
});
const list = useMemo(() => {
console.log('memo');
return <BooksList searchString={deferredSearchString} />;
}, [deferredSearchString]);
return (
<div>
<div>
Search:{' '}
<input
type="text"
value={searchString}
onChange={(event) => {
setSearchString(event.target.value);
}}
/>
{list}
</div>
</div>
);
}
export default App;
Listing 6.21 Implementation of the Search Form (src/App.jsx)
In the App component, you connect the input element to the
searchString state by assigning the value of the state to the
value prop of the element and using a change handler to
manipulate the state on input. Then you use the deferred
value hook to create a deferred version of the state value
and use the memo hook to regenerate the BooksList
component only when the deferredSearchString value—that is,
the return value of the deferred value hook—changes.
Through the console outputs, you can see that the App
component is rendered more often than the BooksList
component. You save resources on high-frequency changes
that involve expensive operations such as server
communications. The BooksList component receives the
value as a prop and is thus only indirectly affected by the
deferred value hook, as you can see in Listing 6.22:
import { useEffect, useState } from 'react';
function BooksList({ searchString }) {
const [books, setBooks] = useState([]);
useEffect(() => {
fetch(`http://localhost:3001/books?title_like=${searchString}`)
.then((response) => response.json())
.then((data) => setBooks(data));
}, [searchString]);
return (
<ul>
{books.map((book) => (
<li key={book.id}>{book.title}</li>
))}
</ul>
);
}
export default BooksList;
Listing 6.22 Display of the Books List (src/BooksList.jsx)
6.10 “useTransition”: Lowering the
Priority of Operations
Quick Start
The useTransition function returns an array with a Boolean
value and a function. This startTransition function in turn
accepts a callback function that marks React as a
transition and treats it with low priority. The first element
of the return array of the useTransition function signals
whether the transition is already complete.
const [ispending, startTransition] = useTransition();
Listing 6.23 Syntax of the “useTransition” Function
The useTransition function allows you to assign a lower
priority to operations. Here you should especially distinguish
between operations that generate direct feedback for the
user (e.g., updating a text field when typing) and operations
that cause the interface to be updated in some other way
and for which you can also briefly display a loading
indicator.
Direct feedback to users is important and should always be
the highest priority. Delayed text input quickly causes
frustration. As a concrete example, you can see a list in
Listing 6.24 that you can filter via a text field. Updating the
text field has a high priority, whereas updating the list has a
lower priority.
import { useState, useTransition } from 'react';
import './App.css';
function App() {
const [searchString, setSearchString] = useState('');
const [books, setBooks] = useState([]);
const [isPending, startTransition] = useTransition();
function search() {
startTransition(() => {
fetch(`http://localhost:3001/books?title_like=${searchString}`)
.then((response) => response.json())
.then((data) => setBooks(data));
});
}
return (
<div>
<div>
<input
type="text"
value={searchString}
onChange={(event) => setSearchString(event.target.value)}
/>
<button onClick={() => search()}>filter</button>
</div>
{isPending && <div>loading</div>}
{!isPending && (
<ul>
{books.map((book) => (
<li key={book.id}>{book.title}</li>
))}
</ul>
)}
</div>
);
}
export default App;
Listing 6.24 Using the “useTransition” Function (src/App.jsx)
The useTransition function returns an array with two
elements. The first element indicates whether the transition
is currently in process. You can use this information to either
inform users that the list is being loaded or display the list
itself when the transition is complete.
You can use the second element of the array returned by the
useTransition function—the startTransition function—to
encapsulate the state change in it. React then ensures that
the list update gets a correspondingly lower priority when
rendering the component.
6.11 “useId”: Creating Unique
Identifiers
Quick Start
The Id hook generates unique IDs that you can use, for
example, to assign labels. The function generates the
same IDs during the server-side rendering and in the
hydration process.
const id = useId()
Listing 6.25 Syntax of the “useId” Function
The useId function is primarily intended for applications that
are rendered on the server side. This is where problems can
arise if you generate unique IDs on the server side and the
hydration process, which takes control of React on the client
side, generates different IDs.
To get around this problem, the React development team
created the Id hook.
However, you can also use this hook in a purely client-side
React application. Listing 6.26 shows an example of this.
import { useId } from 'react';
import './App.css';
function App() {
const id = useId();
return (
<div>
<label htmlFor={id}>Name:</label>
<input type="text" id={id} />
</div>
);
}
export default App;
Listing 6.26 Using the “useId” Function (src/App.jsx)
The component in the example uses the Id hook to create a
unique ID and uses this ID to link the label element with the
htmlFor property to the input element.
6.12 Library Hooks
The two library hooks React introduces with version 18 will
probably only be used in rare, exceptional cases directly in
your application. For this reason, only a brief look at what
these hooks can do follows here, as they are aimed more at
authors of libraries for React.
6.12.1 “useSyncExternalStore”
The sync external store hook is used when it comes to
connecting your application to an external data source. This
hook is intended to make the connection to the data source
compatible with the new render process. A typical external
data source, also called a store, can be a Redux store, for
example.
When called, the hook function returns a state object that
you can use in your components. When you execute the
function, you pass a subscribe function that you can use to
register a callback function for the changes to the store. The
second argument is a function that returns the current value
of the data source. As an optional third argument, you can
pass a function that returns the current value of the data
source during server-side rendering of your application.
For example, the Redux central state management library
uses this hook function for the integration into a React
application, replacing the previously used useMutableSource
function.
6.12.2 “useInsertionEffect”
The insertion effect hook is aimed at authors of CSS-in-JS
libraries. Similar to useEffect and useLayoutEffect, React
executes it when changes are made to a component, but
before the adjustments are made to the DOM. This allows
you to apply styles before the browser renders the
elements.
CSS-in-JS
The term CSS-in-JS refers to libraries that allow you to
write CSS code in JavaScript and benefit from that. Thus,
the resulting styles are limited in scope, and you can use
program logic to define your styles.
6.13 Custom Hooks
Custom hooks are JavaScript functions that can use hook
functions and are themselves used in a React component.
The advantage here is that you can use these custom hooks
in multiple places in your application. In addition, custom
hooks are the only way to use hooks besides function
components.
If you implement a custom hook in your application, you
should save it in a separate file and export it. In this way,
you ensure the best possible reusability. You should also
follow the naming convention for hooks and prefix the hook
name with the word use. To illustrate this, we’ll first
implement a simple example of a custom hook, detached
from the sample application.
In the following example, you implement a counter that
displays a value incremented once per second. In this
implementation, you separate the logic and the
presentation by using a custom hook. Try to make a
component only care about the presentation if possible and
have little logic of its own. You can solve this by swapping
out all the logic into a custom hook. For this purpose, you
create a file named useCounter.js. The source code of this
file is shown in Listing 6.27:
import { useState, useEffect } from 'react';
function useCounter() {
const [counter, setCounter] = useState(0);
useEffect(() => {
const interval = setInterval(
() => setCounter((prevCounter) => prevCounter + 1),
1000,
[]
);
return () => clearInterval(interval);
}, []);
return counter;
}
export default useCounter;
Listing 6.27 Custom Hook with Counter Functionality (src/useCounter.js)
The counter hook consists of a state hook and an effect
hook. You encapsulate both function calls in the useCounter
function. A special feature of custom hooks is that when you
create the hook, you need to consider what the user needs
access to. In this case, only read access to the state—that
is, the counter variable—is required.
You only need the setCounter function for the internal logic of
the hook. It therefore remains hidden from the outside
world. For more complex hooks, you may need to export
more than just the state and the function to set the state.
Here you can either use an array, as with the state hook, or
an object.
The integration of the custom hook into the App component
is similar to the integration of a base hook: you import the
function and execute it within the function component.
Listing 6.28 contains the respective source code:
import useCounter from './useCounter';
function App() {
const counter = useCounter();
return <div>{counter}</div>;
}
export default App;
Listing 6.28 Integration of the Custom Hook (src/App.ts)
As you can see in the code example, the component
becomes much simpler than if you include the two hooks
directly there. The effect of the custom hook is similar to
that of the individual base hooks. This means that for each
call of the useCounter function, the state hook creates a
standalone state that is independent of the other instances.
The same applies to the effect hook: it’s triggered whenever
the integrating component changes, unless you restrict this
with the second parameter. If you return a callback function,
it will be executed when you unmount.
In some places in this chapter, we already mentioned that
hooks may only be used in function components or custom
hooks. The developers of React have dedicated a separate
section of the documentation to the rules for hooks, which
they called Rules of Hooks, which you can find at
https://react.dev/warnings/invalid-hook-call-warning.
6.14 Rules of Hooks: Things to
Consider
Okay, here’s a newly developed interface, and it’s already
regulated. But that's not as bad as it sounds. Overall, there
are two rules you should heed when using hooks. To better
monitor compliance with these rules, an ESLint plugin is
available for you to integrate into your application. If you
violate any of the rules, you’ll receive a warning on the
console. The plugin is named eslint-plugin-react-hooks and
is already included in the default configuration of Create
React App, so you don't need to install and include it
separately.
6.14.1 Rule #1: Execute Hooks Only at the
Top Level
You should only execute a hook function like useState or
useEffect directly in the component or a custom hook and
not in a nested function, loop, or condition. This ensures
that a hook is not executed multiple times and that the
hooks are always activated in the same order. Take a
component such as the one in Listing 6.29, where the state
hook is called within an if statement:
import { useState } from 'react';
function App() {
if (true) {
const [state, setState] = useState(0);
}
return <div>Hello World</div>;
}
export default App;
Listing 6.29 Component with a State Hook in a Condition
Based on the ESLint rule for the Hooks API, you receive the
following error message:
React Hook "useState" is called conditionally. React Hooks must be called in the
exact same order in every component render. Eslint(react-hooks/rules-of-hooks)
Listing 6.30 Error Message that Appears when a Hook Is Not Used at the Top
Level
6.14.2 Rule #2: Hooks May Only Be Used in
Function Components or Custom Hooks
The Hooks API is strongly coupled to the function
components of React and for this reason should only be
used either directly in function components or in custom
hooks. In the source code shown in Listing 6.31, the call of
the useEffect function is swapped out to a separate function.
Such a move violates the second rule.
import { useEffect } from 'react';
function init() {
useEffect(() => {
console.log('Effect hook');
}, []);
}
function App() {
init();
return <div>Hello World</div>;
}
export default App;
Listing 6.31 Component with an Effect Hook in an External Function
A run of ESLint on this component returns the following error
message:
React Hook "useEffect" is called in function "init" that is neither a React function
component nor a custom React Hook function. React component names must start with an
uppercase letter. React Hook names must start with the word "use". eslint(react-
hooks/rules-of-hooks)
Listing 6.32 Error Message that Appears when a Hook Is Called in a Function
In this example, the problem would be easily solved by
renaming the function. In this case, only the use prefix is
missing to convert the function to a custom hook. However,
if the init function is a classic helper function that contains
other functionality besides the effect hook, the violation
becomes even clearer.
6.15 Changing over to Hooks
The Hooks API has become an integral part of the feature
set of React. This doesn’t mean that class components will
disappear from the library in the foreseeable future.
However, the combination of hooks and function
components has supplanted class components as the
standard for how components are built in React. One of the
ulterior motives for introducing the Hooks API was to
simplify concepts and reduce the barrier to entry.
The developers of React recommend that existing
applications—especially if they have a larger range of
functions—do not immediately and completely switch to
hooks. Instead, such a changeover, if it makes sense, should
only be made in small steps. New developments of
extensions and modules, in which hooks are introduced, are
the first choice for this. Then the components of the
application can be changed step by step. A viable solution
to this is to have a component be converted as soon as its
source code is changed in the course of developing a new
feature or a bug fix.
Many of the existing concepts of React are picked up by the
Hooks API and thus remain almost unchanged. A good
example is the context hook, which gives you convenient
access to the context, similar to a class component. The
Context API remains unchanged, but the functional
components are significantly upgraded at the same time.
6.16 Summary
In this chapter, you learned how the Hooks API of React
significantly impacts the way you use components:
The Hooks API enables you to reduce the size of your
components, eliminate duplicates in your code, and
implement complex design patterns much more easily.
Due to the Hooks API, class and function components are
equivalent.
The Hooks API is divided into the following three parts:
base hooks, advanced hooks, and library hooks.
The base hooks are the state hook, the effect hook, and
the context hook. You will use these most of the time.
The extended hooks add additional functionality, such as
the ability to memoize structures or intervene in the
render process of React.
You generally use the library hooks only when you
implement libraries for React.
You can group the hooks into custom hooks to swap them
out from your components and make them reusable.
Hooks may only be used at the top level and only in
components or custom hooks.
In the next chapter, you'll learn how to bring more structure
to your React application using TypeScript.
7 Type Safety in React
Applications with TypeScript
JavaScript has a weak type system, which supports only a
handful of types. It isn’t possible to assign a fixed type to
variables, just as you cannot assign types to the signature
of functions. The more extensive an application becomes
and the greater the number of developers who are involved
in the implementation of the application, the more the lack
of a strict type system in JavaScript becomes noticeable.
To address this problem, there are type systems based on
JavaScript. There are two different categories here: static
type checking and languages compiled to JavaScript. In this
chapter, we’ll introduce Flow and TypeScript,
representatives of each of these two approaches, and
demonstrate how they work with React.
7.1 What Is the Benefit of a Type
System?
Before you integrate one of the type systems available on
the market into your application, you should first ask
yourself: How will I benefit from a type system? In general,
you get additional structure and safety. By specifying what
type a particular variable, parameter, or return value of a
function has, you do limit the freedom that JavaScript gives
you. But it also means that you can no longer accidentally
turn a string variable into a number or a Boolean.
If you strictly follow the type system and specify the types
for every variable and function, it also forces you to think
more about the structure of your application. At the same
time, this requires you to document your code. A comment
block of a function can easily become obsolete because you
are not forced to adjust it when the source code changes.
When using a type system, you must adjust the type
specification in the signature of a function; otherwise you
will receive error messages during checking.
The readability of the source code is positively influenced by
the use of a type system. When looking at the signature of a
function, you can see at a glance what it expects as input
and what it returns. If you then combine the types with a
meaningful naming of the function itself as well as the
parameters, even someone who did not write the function
himself should be able to recognize at a glance what it does.
A type system can also serve well in regard to
troubleshooting and in the maintenance of applications as it
reduces the risk of problems arising with complex
dependencies because they are stored directly in the source
code.
The most obvious advantage of a type system is improved
support via programming tools such as the development
environment or tools for static code analysis. Most
development environments support the common type
systems by default or have extensions that can be installed
in a few steps. If your development environment is
configured correctly, you will receive immediate feedback
on your source code during development. This is done on
the one hand by error messages if you violate the rules of
the type system, and on the other hand by autocompletion
during development—for example, when you start writing
the name of a method of an object and get possible
suggestions. This feature exists for native JavaScript as well,
but it’s significantly better and more reliable with the type-
safe variant.
7.2 The Different Type Systems
As mentioned previously, type systems for JavaScript can be
roughly divided into two categories: tools that use certain
annotations to check compliance with the rules, and full-
fledged type systems, where the source code is formulated
in its own language. In the first category, the source code is
already in JavaScript and only the type information is added.
Before the source code can be executed, the type
specifications must be removed; otherwise syntax errors
would be thrown. A typical representative of this type is
Flow.
The second type of type system, which includes TypeScript,
for example, also uses annotations to check compliance
with type rules. The TypeScript code is compiled into
JavaScript before execution. This removes the type
specifications and emulates certain features. The TypeScript
compiler is capable of generating different versions of
JavaScript—both modern code, as it can only run in a
modern browser, and older JavaScript source code that
follows the specification of ECMAScript 3 or ECMAScript 5.
7.3 Type Safety in a React
Application with Flow
Flow was released by Facebook in 2014 and has since been
managed as an open-source project on GitHub. The website
for Flow can be found at https://flow.org/. Flow was
developed to improve the quality of source code. In
addition, the productivity of developers should be
significantly increased by the static types in JavaScript.
Flow was designed to be a lightweight tool for development.
The basic character of JavaScript should remain as
unchanged as possible and only be supplemented by the
support of types. An important requirement for type
checking with Flow is that this process is done very quickly
so that it doesn’t have a negative impact on the
development process. Flow checks the types based on files.
This allows the verification to be performed simultaneously
in multiple files. It’s also possible to mark only certain files
for checking.
Like most type systems, Flow provides a feature called type
inference. Here, Flow derives type information from existing
source code that isn’t tagged with types. This means that
you don’t necessarily have to mark up all source code with
types: once the type is apparent from the source code, you
can omit the explicit assignment.
Flow is being developed in parallel with JavaScript so that
modern interfaces and features are also supported.
7.3.1 Integration into a React Application
Both React and Flow come from Facebook, so it's not
surprising that Flow can be integrated into a React app
created with Create React App in just a few steps.
For the short introduction to Flow that follows, you first want
to create a new application with Create React App using the
npx create-react-app flow-test command. Then you need to
install the flow package using npm install --save-dev flow-bin
and configure it. For this purpose, you must first add a new
entry to the scripts section with the flow key and flow value
in your package.json file. This way you make sure that you
can run Flow on the command line using the npm run flow
command.
The npm run flow init command enables you to generate the
configuration for Flow, which the tool stores in the
.flowconfig file in the root directory of the application.
However, before you can run your application in the
browser, you must remove the Flow-specific extensions to
the source code because they are JavaScript syntax
violations. You can either let Babel do this cleanup or you
can use the flow-remove-types tool, which you can install via
the npm install --save-dev flow-remove-types command. You can
integrate the processing of your source code into the NPM
scripts of your application, as shown in Listing 7.1:
{
…
"scripts": {
"flow": "flow",
"prestart": "flow-remove-types src/",
…
},
…
}
Listing 7.1 Extension of the “package.json” File for Flow
Create React App supports the new versions of Flow directly,
which means you don’t need to install flow-remove-types
separately.
After installing Flow, you can use the tool in your
application. Listing 7.2 contains an App component with Flow
support enabled:
// @flow
import React from 'react';
import type { Node } from 'react';
function App(): Node {
let name: string = 'World';
name = 1337;
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}
export default App;
Listing 7.2 Component with Active Flow Support (src/App.jsx)
In the first line, you use the @flow comment to enable
checking via Flow. Without this comment, Flow would ignore
this file. The second part in the source code relevant for
Flow is the declaration of the name variable. Here you can see
that the variable name, separated by a colon, is followed by
the specification of the type. The subsequent assignment of
the number 42 is an obvious violation of the type system
and generates a corresponding error when Flow is executed.
Despite the error and the syntax violation due to the type
specification, the application remains executable. You can
easily test this by entering the npm start command on the
command line in the root directory of your application.
You can run the check directly in your development
environment, on the command line, or with a combination of
both approaches.
Execution in the Development Environment
Both WebStorm and Visual Studio Code provide support for
Flow. In the case of WebStorm, this is already included but
still needs to be configured by changing the language
version of your application to Flow. The only requirement
you then need to fulfill is to make sure that Flow is installed
in your application.
Using Flow in Visual Studio Code is similar. Here you install
the Flow extension, which is named Flow Language Support.
For this to work correctly, you must set the
javascript.validate.enable key to the value false in the IDE
settings; otherwise the flow statements will be considered
syntax errors. Again, the flow package must be installed
locally in your application. Figure 7.1 contains the source
code from Listing 7.2 in Visual Studio Code. Here the
number 1337 is marked in red to indicate an error. If you
move the mouse over the number, you’ll see the
corresponding error message, which will provide you with
additional information about the error and a possible fix.
Figure 7.1 Error Message in Visual Studio Code
Execution on the Command Line
The integration of Flow with the development environment
ensures that you get instant feedback as you generate
source code. Errors are displayed immediately and can be
corrected directly. However, for use in a build or release
process, you should resort to the command line tool. For this
purpose, you want to go to the root directory of your
application in the command line and execute the npm run flow
command there. As a result, you’ll get output like that
shown in Listing 7.3:
$ npm run flow
> flow
Error ------------------------------------------------------ src/App.jsx:8:10
Cannot assign 1337 to name because number [1] is incompatible with string
[2]. [incompatible-type]
5| function App(): Node {
[2] 6| let name: string = 'World';
7|
[1] 8| name = 1337;
9| return (
10| <div className="App">
11| <h1>Hello {name}</h1>
Found 1 error
Listing 7.3 Execution of Flow in the Command Line
An exit code other than 0 indicates a failure of the
command. Thus, the tool can be integrated into an
automated build process. The build process can be aborted
if the source code has type system violations.
7.3.2 The Major Features of Flow
Besides the base types, such as strings, numbers, or
Booleans, arrays and objects are also supported. Also, in
Flow, each class you create is a separate type, which you
can specify. In addition, you can use union types to create a
combination of multiple types using the pipe operator.
Flow also supports generics and interfaces. Generics allow
you to create generic data structures and make them
concrete only when you use them. A typical example of
generics is a collection—that is, a collection of objects of a
certain type. Interfaces define structures without concrete
implementations. You can use them to indicate that an
object must have a certain structure.
7.3.3 Flow in React Components
Flow doesn’t show its advantages in combination with React
only when typifying variables and function signatures, but
also when defining components. The critical parts are the
definition of props and states.
For the props, you already learned about a possible solution:
PropTypes. However, there is no solution for defining the
structure of the state of a component. For a function
component, you can set the props as the type in the
parameters list and the return value as the regular return
value of the function. There are also type definitions for all
type-dependent hooks, such as the state hook. In
Listing 7.4, you can see a BooksList component as an
example. This component gets its initial value via the
initialBooks prop and then stores it in the state. The useState
function is typed as a generic function, so you can specify
the type explicitly. In most cases this isn’t necessary
because Flow can derive the type from the initial value. But
if you have the option, be as explicit as possible about type
specifications.
// @flow
import React, { useState } from 'react';
import type { Node } from 'react';
type Book = {
id: number,
title: string,
author: string,
isbn: string,
rating: number,
};
type Props = {
initialBooks: Book[],
};
function BooksList({ initialBooks }: Props): Node {
const [books, setBooks] = useState<Book[]>(initialBooks);
return (
<ul>
{books.map((book) => (
<li key={book.id}>{book.title}</li>
))}
</ul>
);
}
export default BooksList;
Listing 7.4 “Counter” Component with Flow (src/BooksList.jsx)
To enable Flow, you add the @flow comment at the beginning
of the file. Then you define a type alias for each of the book
records and the component's props. The state contains the
initial book records. When you call the useState function, you
specify the Book[] type so that Flow can ensure for itself that
you can only work with a list of books. If you violate the type
specifications during implementation, you’ll receive
corresponding error messages. You can run the application
as usual via npm start, despite the type specifications. The
Flow-specific parts are removed by the build process.
In addition to Flow, TypeScript is another widely used type
system that is also very popular in the React community.
7.4 Using TypeScript in a React
Application
TypeScript has been developed by Microsoft as an open-
source project since 2012. The way it works is
fundamentally different from Flow. Whereas Flow focuses on
type markup and leaves the rest of the source code
untouched, TypeScript is a programming language in its own
right that extends the core of JavaScript and adds other
features.
Basically, valid JavaScript code is also valid TypeScript code.
However, this statement isn’t true in the other direction.
Trying to run TypeScript directly in the browser usually
results in syntax errors and termination of the application.
TypeScript has become the de facto standard for type-safe
JavaScript in recent years.
In any case, you should consider using a type system for
your application, as the advantages clearly outweigh the
disadvantages. As a type system, we clearly recommend
TypeScript, as it has a much higher penetration in the
community and is also very well supported by the various
add-on libraries for React.
7.4.1 Integrating TypeScript in a React
Application
The combination of React and TypeScript has been
supported for quite some time. TypeScript wasn’t officially
included in Create React App until version 2.1 in October
2018. Since the release of this version, it’s possible to start
the development of an application directly with TypeScript.
Meanwhile, the Create React App team has tweaked the
architecture a bit so that you can specify a template for
creating your app. One of the default templates is named
typescript and initializes a React application with TypeScript
support for you. Listing 7.5 shows the command you use to
initialize the application with TypeScript:
npx create-react-app library --template typescript
Listing 7.5 Initializing an Application with TypeScript
By using the --template typescript option, you make sure that
Create React App installs all the dependencies required to
use TypeScript in your application. It also creates the
necessary structures and configurations so that you can
start developing right away. The development and build
process is also modified accordingly.
The first difference from an ordinary React application is the
presence of the tsconfig.json file in the root directory, which
can be used to configure the behavior of TypeScript. Also,
unlike Flow, TypeScript doesn't give you the option to
selectively check only part of the files; you have to do this
with all the files in your application. The component files
also have the extension .tsx, which should indicate that it is
a combination of TypeScript and JSX. For helper files that
don’t contain JSX, you can use the .ts extension. For
demonstration purposes, you can use the source code from
Listing 7.2:
import React from 'react';
import './App.css';
const App: React.FC = () => {
let name: string = 'World';
name = 42;
return (
<div className="App">
<h1>Hello {name}</h1>
</div>
);
}
export default App;
Listing 7.6 TypeScript Source Code (src/App.tsx)
When you use TypeScript, you can omit the explicit
specification of the type. The first assignment of a value to a
variable simultaneously determines its type. Otherwise,
TypeScript behaves very similarly to Flow in the case of
faulty source code, as in this example.
Execution in the Development Environment
TypeScript is supported by all major development
environments, such as WebStorm or Visual Studio Code. The
source code is checked immediately upon its creation, and
errors are displayed right away. With this direct feedback,
many errors can be found and fixed before the source code
is executed. Also, the suggestions that the development
environment gives you when writing the source code are
significantly better than when using pure JavaScript because
the signature of functions and the structure of objects are
known. Figure 7.2 shows the error message you get for the
source code from Listing 7.6 when you open it in Visual
Studio Code.
Figure 7.2 TypeScript Error Message in Visual Studio Code
You can use TypeScript not only in the development
environment, but also in the command line, and thus
integrate it into an automated build process, for example.
Execution in the Command Line
At the core of TypeScript, there is the TypeScript compiler or
tsc. This command-line tool checks the TypeScript source
code of your application and transforms it into valid
JavaScript source code that can be run in the browser. For
this transformation to work, the typescript package must be
installed on your system. Create React App will do the
installation for you. After the installation, you can write
TypeScript and transform the code using the default
TypeScript configuration. Alternatively, you can use the tsc -
-init command to have a configuration generated in the
form of tsconfig.json. This affects the way the TypeScript
compiler works. In the current example, such a
configuration file already exists, and you only need to switch
to the command line and enter the npx tsc command. The
compiler automatically finds the configuration file and
applies it. In Listing 7.7, you can see the output in the
command line.
$ npx tsc
src/App.tsx:7:3 - error TS2322: Type 'number' is not assignable to type 'string'.
7 name = 42;
~~~~
Found 1 error in src/App.tsx:7
Listing 7.7 Output of the TypeScript compiler in the Command Line
The current configuration of TypeScript ensures that no
JavaScript source code is stored in the file system during the
development process. This isn’t necessary because
TypeScript is very deeply integrated into the development
process. With npm start, you run the application directly
based on Webpack with the dev server. For production use,
you need to have the source code translated to JavaScript.
The npm run build command will help you with this. It creates
an executable application for you, which you can deploy to
any web server. With this knowledge, you can now dive
deeper into the world of TypeScript.
The following explanations and examples of TypeScript are
significantly more extensive than those for Flow as the rest
of the book is based on TypeScript. The reasons for this are
mainly that TypeScript is much more powerful compared to
Flow and that it has a wider distribution.
7.4.2 Configuration of TypeScript
You can influence the behavior of the TypeScript compiler
through options in the command line or through the
tsconfig.json file. If such a file exists in the root directory of
your application, where you also run the compiler, the file is
used automatically and you don’t need to reference it
explicitly.
The most important specifications in the configuration are
the compilerOptions, which you can use to control the
compiler, and include, which you can use to specify a list of
directories that contain the files with the TypeScript source
code. If necessary, you can use the exclude key to exclude
certain patterns from the compilation process and use files
to specify individual files. In Listing 7.8, you can see the
default configuration for TypeScript that Create React App
generates for you:
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}
Listing 7.8 TypeScript Configuration of a React Application (tsconfig.json)
Usually, the default configuration should be sufficient for
using React, so you’ll hardly come into contact with the
configuration during the development process.
7.4.3 The Major Features of TypeScript
The differences in source code markup and key features are
minor between Flow and TypeScript. However, the two differ
seriously in the basic handling of the source code.
TypeScript also supports basic data types such as Boolean,
string, or number as well as composite data types such as
objects and arrays. In addition, there are special data types
such as enum, which you can use to map keys to values.
TypeScript allows the use of classes that represent types at
the same time and thus can be used in type assignments.
Type aliases, interfaces, and generics are also supported by
TypeScript and used in React applications. In TypeScript, you
can use modules, which are self-contained units that each
reside in a file. The module syntax is the same as in the
ECMAScript module system, so you can still use the import
and export keywords in your application to import and export
your modules, respectively.
7.4.4 Type Definitions: Information about
Third-Party Software
TypeScript has a special feature when dealing with libraries
written in JavaScript that are to be used in a TypeScript
application. The interfaces of these libraries are usually not
provided with type information, so you would lose most of
the benefits of TypeScript at this point as the compiler would
fall back to the implicit any type. If you use any, further type
checking is no longer possible at this point.
To solve this problem, TypeScript provides a feature called
type definitions that can be used to provide types to
JavaScript interfaces. An increasing number of libraries in
the React environment ship these type definitions right
along with the actual source code so that the versions of the
interface and the type definition are identical. Far more
often, however, the type definitions are offered as an
additional package. This creates the risk that the versions
are different and you are working with a potentially
outdated version of the interface definition. But type
definitions are often also maintained by the people who
created the libraries, so few problems are expected here.
DefinitelyTyped has established itself as the most important
source. This is a repository from which you can obtain type
definitions for numerous libraries. Type definitions are
maintained in a GitHub repository
(https://definitelytyped.github.io/) and can be selectively
installed via a package manager such as Yarn or NPM. When
installing a type definition, you want to make sure to install
it as devDependency. Type definitions are used only during
development and not for running your application.
To install the type definition for React, which Create React
App automatically does for you, you need to run the npm
install -D @types/react command. The @types prefix stands for
the DefinitelyTyped repository. After the installation, you
don't need to do anything else: you can work directly with
the type definitions because TypeScript automatically looks
for an @types directory in node_modules if there are no
direct type definitions for a particular library.
With this knowledge of TypeScript, you can now move on to
integrating TypeScript into your application step by step.
7.5 TypeScript and React
To use TypeScript in your application initialized with Create
React App, you have two options: you can start directly with
TypeScript and specify the --template typescript option when
initializing your application, or you can add TypeScript to
your existing project. In the following sections, we'll first
demonstrate how you can use TypeScript by means of an
independent example. Then the key aspect of the sample
application is converted to TypeScript.
7.5.1 Adding TypeScript to an Existing
Application
The sample application from the previous chapters is based
on JavaScript. However, you can integrate TypeScript with a
few steps. For this, you must first install some additional
dependencies. Listing 7.9 contains the appropriate
command:
npm install typescript \n
@types/node \n
@types/react \n
@types/react-dom \n
@types/jest
Listing 7.9 Installing Dependencies for TypeScript
After the installation, you need to rename the JavaScript
files in the src directory: The .js extension becomes .ts, and
.jsx becomes .tsx. After that, you must restart the
development server. The server recognizes that this is a
TypeScript project after the changes and automatically
creates a tsconfig.json file for you.
But renaming the files alone doesn’t turn your JavaScript
application into a TypeScript application. This becomes clear
when you look at the console where you started the
development server. There you’ll see errors that cause your
application to fail to start. In the following sections, we’ll
troubleshoot these error messages. During this, you’ll learn
about some features of TypeScript that are important for
developing a React application.
7.5.2 Basic Features
The basics of TypeScript are the same no matter what
environment you're in. That's why we'll first look at the
basics of TypeScript before you get to know the React-
specific parts.
Variables
In TypeScript, you declare a variable as you would in
JavaScript, except that you can still specify an optional type.
If you initialize the variable with a value, you can omit the
type assignment because TypeScript automatically takes the
type of the assigned value. The var, let, and const keywords
are available for the declaration. If possible, you should only
use let and const. Try to use const as often as possible to
avoid accidental reassignment. Only when you need to
reassign a value or know this in advance should you use the
let keyword. The const and let keywords create constants
and variables respectively in the block scope, so you have
very good control over validity. Listing 7.10 shows an
example of a declaration and simultaneous initialization of a
variable, where you also specify the type:
let title: string = 'Design Patterns';
Listing 7.10 Declaration and Initialization of Variables
You always write the specification of the type after a colon
separating it from the name of the variable and before the
equal sign. Table 7.1 contains an overview of the data types
of TypeScript.
Type Description
boolean The truth values true and false
number Numerical values like integers, floats, and BigInt
string Strings
array Corresponds to the array type of JavaScript
tuple A fixed length array with fixed assigned types
enum An enumeration type where keys are mapped to
numbers or optionally other values
unknown The content of a variable of this type can be of
any type
any Turns off type checking for a variable
void Absence of a value, such as in the case of a
function without a return value
null The null value of JavaScript
Type Description
undefined The undefined value of JavaScript
never A value that never occurs, such as a function
that throws an exception in every case
object Stands for a JavaScript object
Table 7.1 Basic Data Types in TypeScript
Functions
Functions take a prominent role in the development of React
applications. In a React application, you usually deal with
functions much more often than with class constructs.
Functions can appear as an arrow function, a named
function, or an anonymous function. For TypeScript, the
signature of the function is relevant—that is, the parameters
list and the return value.
function add(a: number, b: number): number {
return a + b;
}
const getFullname = function (firstname: string, lastname: string): string {
return `${firstname} ${lastname}`;
};
const greet = (name: string): string => {
return `Hello ${name}`;
};
Listing 7.11 Examples of Functions in TypeScript
Listing 7.11 contains examples of the following:
A named function—that is, a function that has its own
name
An anonymous function—that is, a function that has no
name and that you assign to a constant
An arrow function
No matter what type of function you decide to use, try to be
as explicit as possible and always specify the types for the
parameters and the return value. Especially the return value
can save you from careless mistakes—for example, if you
forget a return statement.
Classes
Even though functions are clearly gaining importance over
class constructs in the React world, there are always places
where it pays off for you to rely on classes instead. Classes
are used especially for data encapsulation, the
implementation of certain business logic, and, last but not
least, for class components.
A TypeScript class behaves very much like a class in modern
JavaScript. To define it, you use the class keyword followed
by the name of the class, which should start with an
uppercase letter according to the naming convention. After
the class name, you can use the extends keyword and specify
a class name to inherit from that class, or you can use the
implements keyword and specify an interface that the class
must then implement. The use of interfaces is the first
major difference from traditional JavaScript.
In a TypeScript class, you can define a constructor. This is a
special method that’s called by TypeScript when you create
a new instance of the class via new. The name of the
constructor is constructor. In the constructor, you can define
a parameters list. These values are passed during the
instantiation.
Besides the constructor, you can define properties and
methods in a TypeScript class. Methods follow the same
rules for specifying the signature as functions do. For
properties and methods, you can specify their visibility
using the access modifiers: private, public, and protected.
Access Modifiers in TypeScript
Unlike native JavaScript, TypeScript supports access
modifiers that let you affect the visibility of the properties
and methods of a class. TypeScript has the three modifiers
—private, protected, and public—which are also known in
other programming languages:
private
Properties and methods marked with the private
modifier can only be accessed within the class. This
means that they aren’t available outside, nor in derived
classes.
protected
A property marked as protected can be used in the class
and its subclasses.
public
The default modifier in TypeScript is public. If you don’t
specify a modifier, the property or method is
automatically public and can be used anywhere in your
application.
Another modifier you can use in a class definition is
readonly. Properties marked with readonly must be
initialized in the constructor or when they are declared
and cannot be changed later.
If you define a parameters list in the constructor and you
want to assign these values to specific properties of the
class, you should perform this operation directly in the
constructor. TypeScript provides a shortcut for this very
common use case: if you specify a combination of access
modifier, property name, and type for a parameter,
TypeScript automatically assigns that value to the specified
class property and you don't have to worry about anything
else. This shorthand notation is called parameter properties.
In Listing 7.12, you can see an example of a TypeScript class
including instantiation and method call:
class User {
constructor(private firstname: string, private lastname: string) {}
get fullname(): string {
return `${this.firstname} ${this.lastname}`;
}
greet(greeting: string): string {
return `${greeting} ${this.fullname}`;
}
}
const tom = new User('Tom', 'Miller');
const greeting = tom.greet('Hello');
console.log(greeting); // Hello Tom Miller
Listing 7.12 Class in TypeScript
Type Aliases versus Interfaces
In TypeScript, you have several options to define your own
types. You’ve already learned about classes, which
represent one of these options. Two other variants are type
aliases and interfaces. You can use both of them to specify
types, such as in variable declarations or in function
signatures, but they differ on some points:
You can extend interfaces by means of interface merging.
If you define an interface multiple times, TypeScript
merges these definitions into one interface.
Interfaces can inherit from other interfaces via the extends
keyword. For types, you can use the & operator to use a
type as a base and add more information to create a new
type.
Type aliases can’t be changed once they’ve been defined.
For simple cases and when you don’t need to ensure that a
class implements an interface, a type alias is sufficient in
most cases.
With this basic knowledge of TypeScript, let's now turn to
something more React-specific.
7.5.3 Function Components
Quick Start
In a function component, you use the generic React.FC
type and pass the props structure to it. This usually looks
like the example in Listing 7.13:
import React from 'react';
type Props = {
title: string;
};
const Headline: React.FC<Props> = ({ title }) => {
return <h1>{title}</h1>
}
export default Headline;
Listing 7.13 Typing in a Function Component
For function components, TypeScript mainly affects the
signature of the function. React, or rather the type definition
of React, provides the generic React.FC type for function
components. This type has come under criticism for some
time because it always provides child components for a
component even though the component doesn’t use them,
which has been somewhat misleading in places. But this
problem has been solved by this point, and so nothing
stands in the way of using this type.
For a function component, you should define a type for the
props and store it as a separate type alias within the file.
Unless you use the props elsewhere in your application, you
shouldn’t export them unnecessarily. As a type for the
function component itself, you can use React.FC, which is
implemented as a generic type and accepts the structure of
the props. React.FC is responsible for defining the correct
return type for the function component. As an example of a
typical function component, see the BooksListItem
component in Listing 7.14, which is responsible for
displaying a data record in a list. This component receives
the data record as a prop and represents the title of the
record in a li element.
import React from 'react';
import Book from './Book';
type Props = {
book: Book;
};
const BooksListItem: React.FC<Props> = ({ book }) => {
return <li>{book.title}</li>;
}
export default BooksListItem;
Listing 7.14 Typed Function Component (src/BooksListItem.tsx)
In this component, you reference the Book type in the props.
You usually need such types more than once in an
application, so you should store it in a separate file, in this
case in the src directory and named Book.ts. You can see
the source code of this file in Listing 7.15:
export default type Book = {
id: number;
title: string;
author: string;
isbn: string;
rating: number;
};
Listing 7.15 Book Type (src/Book.ts)
The BooksItemList component must receive the record it’s
supposed to display from its parent component. The parent
component in turn must then also take care of the state of
the list.
The State Hook
Quick Start
The useState component is defined as a generic
component. This means you can specify the type of the
state separately. Alternatively, you can use the type
inference of TypeScript, which is TypeScript's ability to
infer a type from given type specifications. In this case,
TypeScript derives the type from the initial value of the
state:
const [state, setState] = useState<string[]>([]);
Listing 7.16 Syntax of the “useState” Function in TypeScript
The state hook is the first base hook where TypeScript
becomes relevant as you don't need to specify types in the
effect hook. The type definition of the useState function
provides that it’s a generic function. This means that you
can specify the type the component works with in the angle
brackets after the function name. The BooksList component
from Listing 7.17 reads the data within an effect hook from
the server, writes it to the state, and takes care of rendering
the child components:
import React, { useState, useEffect } from 'react';
import { Book } from './Book';
import BooksListItem from './BooksListItem';
import axios from 'axios';
const BooksList: React.FC = () => {
const [books, setBooks] = useState<Book[]>([]);
useEffect(() => {
axios
.get<Book[]>('http://localhost:3001/books')
.then((response) => setBooks(response.data));
}, []);
return (
<ul>
{books.map((book) => (
<BooksListItem key={book.id} book={book} />
))}
</ul>
);
}
export default BooksList;
Listing 7.17 Managing the Local State of a Component with TypeScript
(src/BooksList.tsx)
For the example to work, you need a data.json file in the
root directory of your application that contains the data.
Then you can start the process for the server interface via
npx json-server -p 3001 -w data.json in the command line. In
addition, you still need to install the Axios library using the
npm install axios command.
When calling the useState function, you have to specify the
type in this case as the initial value is an empty array. In
TypeScript, you can define an array of Book objects, either
using the more common variant as Book[], or you can use
the generic array notation Array<Book>. Using the Axios
library has the advantage that you can specify in the
request what type you expect as a response from the
server. This is only a support for typing in your application
and doesn’t help you validate the data structure that the
server sends at runtime.
The final step involves including the BooksList component in
your App component so that React renders the list correctly.
7.5.4 Context
Quick Start
The createContext function is defined as a generic function
to which you can pass the type of context when you call it:
const Context = React.createContext<string[]>([]);
Listing 7.18 Syntax of the “createContext” Function
The third basic hook in addition to useState and useEffect, the
context hook, also uses optional type specifications. Note,
however, that here you don’t specify the type when calling
the hook function, but one step prior to that—that is, when
creating the context via the createContext function. Again,
TypeScript is able to infer the type through its type inference
feature.
import { createContext, Dispatch, SetStateAction } from 'react';
import { Book } from './Book';
const BooksContext = createContext<
[Book[], Dispatch<SetStateAction<Book[]>>]
>([[], () => {}]);
export default BooksContext;
Listing 7.19 React Context with TypeScript (src/BooksContext.tsx)
Typing is a bit more complex in this case, as you store the
return value of the state hook in the context to access it
from anywhere in your application. The types used are
specified by the return value of the useState function. As a
default value for the context, you define an array with an
empty array and an empty arrow function. React will use
this structure only if you don’t define a provider in the
component tree. In the next step, you implement such a
provider component that manages the state and assigns it
to the provider value:
import React, { ReactNode, useState } from 'react';
import { Book } from './Book';
import BooksContext from './BooksContext';
type Props = {
children: ReactNode;
};
const BooksProvider: React.FC<Props> = ({ children }) => {
const bookState = useState<Book[]>([]);
return (
<BooksContext.Provider value={bookState}>
{children}
</BooksContext.Provider>
);
}
export default BooksProvider;
Listing 7.20 Provider with TypeScript (src/BooksProvider.tsx)
The BooksProvider component from Listing 7.20 again has a
special feature. You use the provider as a node in the
component tree to manage the state and pass it a hierarchy
of components and elements when you include it in the
component tree. You can access this hierarchy via the
children prop. In TypeScript, you explicitly define the children
prop in the props type and assign the ReactNode type. You
then include the props in the React.FC type as before. This
allows you to specify the child elements in the JSX structure,
and React will integrate them correctly in the application.
You should integrate the provider as close as possible to the
root component of your application. In the example, this is
directly in the App component. The corresponding code is
shown in Listing 7.21:
import React from 'react';
import './App.css';
import BooksList from './BooksList';
import BooksProvider from './BooksProvider';
const App: React.FC = () => {
return (
<BooksProvider>
<BooksList />
</BooksProvider>
);
}
export default App;
Listing 7.21 Integration of the Context in the “App” Component (src/App.tsx)
In the final step, you customize the BooksList component to
use the context instead of its local state. In Listing 7.22, you
can see how this works:
import React, { useEffect, useContext } from 'react';
import { Book } from './Book';
import BooksListItem from './BooksListItem';
import axios from 'axios';
import BooksContext from './BooksContext';
const BooksList: React.FC = () => {
const [books, setBooks] = useContext(BooksContext);
useEffect(() => {
axios
.get<Book[]>('http://localhost:3001/books')
.then((response) => setBooks(response.data));
}, []);
return (
<ul>
{books.map((book) => (
<BooksListItem key={book.id} book={book} />
))}
</ul>
);
}
export default BooksList;
Listing 7.22 Integration of the Context in the “BooksList” Component
(src/BooksList.tsx)
Because the state and context have the same structure in
this case, you don't need to do anything else at this point
except for exchanging the useState and useContext functions.
7.5.5 Class Components
Class components have their own state by default: You can
pass props from outside and define lifecycle methods. If you
use TypeScript, some things in the implementation will
change for you. The basic structure of the class remains
unchanged, but you can specify the structure of the state
and props and no longer need to use PropTypes for this. You
can also draw on the full range of TypeScript features when
implementing the class, such as access modifiers.
The following example implements a counter component as a
class component. The component expects you to pass it a
start value via a start prop. Every second, this value is
increased by the value 1:
import React, { ReactElement } from 'react';
type Props = {
start: number;
}
type State = {
counter: number;
}
export default class Counter extends React.Component<Props, State> {
private interval = 0;
constructor(props: Props) {
super(props);
this.state = {
counter: props.start,
};
}
componentDidMount(): void {
this.interval = window.setInterval(() => {
this.setState((prevState) => ({
...prevState,
counter: prevState.counter + 1,
}));
}, 1000);
}
componentWillUnmount(): void {
clearInterval(this.interval);
}
render(): ReactElement {
return <div>{this.state.counter}</div>;
}
}
Listing 7.23 “Counter” Component as a Class Component in TypeScript
(src/Counter.tsx)
As you can see in Listing 7.23, you first define the structures
for props and state as types. Because you need both
structures only in the current file, you don’t need to export
either. Then you pass the two types to the generic base
class, React.Component. This ensures that the structure of the
props and the state will be maintained. For example, if you
include the counter component and don’t pass the start
prop, you’ll receive a corresponding error message from the
TypeScript compiler.
The setState method of the class automatically assumes the
appropriate state type, so you don't need to explicitly
specify the type here. Aside from that, there are no changes
compared to the class components in JavaScript.
7.6 Summary
This chapter has shown you that a type system such as
TypeScript is a good complement to React and its entire
ecosystem:
By using a type system, you can improve the quality and
readability of your source code. This is especially true for
more extensive applications.
An alternative to the widely used TypeScript is Flow,
developed by Facebook.
Unlike TypeScript, Flow is not a transpiler that translates
source code from one language to another, but a pure
type system.
A type system provides you with a basic set of types and
allows you to define your own types as classes, interfaces,
or type aliases.
You can specify types in the variable declaration and in
the signature of functions so that the TypeScript compiler
can ensure compliance with the interface.
In many cases, you can avoid explicitly specifying types
because TypeScript uses its type inference feature to try
to determine the appropriate type. However, you should
always try to be as explicit as possible, even if that means
that you have to write more source code. TypeScript helps
you to avoid careless mistakes in this case.
DefinitelyTyped provides you with type definitions for
most third-party libraries that can be installed as NPM
packages.
You provide function components with a typed parameter
list as well as a return type.
React provides type definitions for the Hooks API, so you
can specify the appropriate type for the generic useState
function, for example.
The Context API also supports type specifications by
specifying the type used when calling createContext,
similar to useState.
For class components, you use TypeScript primarily for
structuring the props and the state.
In the next chapter, you'll learn about the options available
to you for styling your application and how to combine the
onboard tools of React and the features of additional
libraries.
8 Styling React Components
React is a library for implementing web frontends. In this
context, designing the UI is a key issue. However, React
doesn’t make any specifications here. What initially appears
to be a positive aspect turns out to be a disadvantage at
second glance. The lack of specifications and guidelines
makes it difficult to get started with the implementation of
graphical user interfaces.
For you, this means looking at the different approaches and
choosing the one that fits your project. In this chapter, we’ll
introduce several approaches along with their respective
advantages and disadvantages and different areas of
application so that you can decide which of them you want
to use. This will also enable you to evaluate and classify
other styling approaches and future solutions yourself.
8.1 CSS Import
You already know the first approach to the styling of
components more or less consciously: it consists of using
regular CSS stylesheets. For the build process of a React app
that you created using Create React App, Webpack is used.
This tool can include not only JavaScript files, but also other
types of files, such as CSS files.
The import statements work in both native JavaScript and
TypeScript. The import keyword is followed by the name of
the CSS file you want to integrate. Make sure that you
specify the relative path to ensure the best possible
portability of your application. In many React applications,
CSS files are given the same names as associated
components, but with the .css extension. Both files are
usually located in the same directory.
An example of such a CSS import is shown in Listing 8.1 in
the form of the App component:
import ReactElement from 'react';
import './App.css';
const App: React.FC = () => {
return <h1>Hello React</h1>;
}
export default App;
Listing 8.1 CSS Import in the Application (src/App.tsx)
The source code of the App.css file consists of regular CSS
code. Here you can use all available selectors, properties,
and values. A concrete example in the form of the App.css
file is shown in Listing 8.2:
h1 {
font-size: 20px;
margin: 40px;
color: orange;
background-color: black;
}
Listing 8.2 Style Specifications for the “App” Component (src/App.css)
8.1.1 Advantages and Disadvantages of CSS
Import
The advantage of this way of handling the styling is that it
entails very little overhead. It works without any further
modifications, and you can use ordinary CSS to style your
components. This also ensures support in your development
in the form of syntax highlighting and autocompletion.
The disadvantage of this simple variant is that stylesheets
do not support namespacing. In the example, the names of
the components file and the stylesheet, App.tsx and
App.css, suggest that the two are directly related. However,
this impression is deceptive, as the stylesheet is an ordinary
global stylesheet. In the example from Listing 8.2, the h1
selector makes sure that the information affects all h1 tags.
As a rule, however, this is not intended.
You can solve this problem by namespacing the styles
yourself. The simplest variant is to assign a class name to
the root element of a component via the className prop and
to refer all style specifications to this root element. You can
see an example of this in the form of the BooksList
component in Listing 8.3:
import React from 'react';
import { Book } from './Book';
import './BooksList.css';
const books: Book[] = […];
const BooksList: React.FC = () => {
return (
<table className="BooksList">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => (
<tr key={book.id}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>{book.rating && ↩
<span>{'*'.repeat(book.rating)}</span>}</td>
</tr>
))}
</tbody>
</table>
);
}
export default BooksList;
Listing 8.3 Namespacing of Styles in a Component with Class Names
(src/BooksList.tsx)
The associated stylesheet, which provides a slightly more
appealing styling of the table, looks like the one shown in
Listing 8.4:
table.BooksList {
border-collapse: collapse;
}
.BooksList th {
border-bottom: 3px solid black;
}
.BooksList tr:nth-child(2n) {
background-color: #ddd;
}
.BooksList td {
padding: 5px 10px;
}
Listing 8.4 Styling a Component Using Stylesheets (src/BooksList.css)
When you use stylesheets, you can use various CSS
selectors to locate the elements to which each style should
be applied. The three basic selectors are tag, class, and ID
selectors, although you should avoid using the ID selector.
The HTML standard specifies that an ID must be unique on
the current page. However, in the component-based
approach of React, which is heavily based on component
reusability, there is no way to ensure that there is only one
instance of a component. This would also make the ID used
no longer unique and invalidate the HTML structure of your
application. So you’re left with the tag selectors and the
class selectors, the latter being used much more frequently
as they allow finer control of the styles applied.
8.1.2 Dealing with Class Names
The class attribute isn’t allowed in JSX because it overlaps
with the class keyword. JSX is a syntax extension of
JavaScript, and the class keyword is already used there.
Although it would be possible to support the class attribute,
some features wouldn’t work, such as destructuring the
class prop.
Instead, you must use the className attribute. The value of
this prop is a string. You can use multiple class names, as in
standard HTML, and list as many as you like. Especially
when it comes to applying classes based on certain
conditions, it gets a bit more elaborate with a string.
As an example of handling multiple class names, you can
now implement highlighting of book records that are
particularly well-ranked. The table rows of books with a
rating of 5 get a yellow background. For this feature to work,
you need to extend the BooksList component and add the
Highlight class name to the table rows depending on the
rating. The source code of the BooksList component with the
highlight extension is shown in Listing 8.5:
import React from 'react';
import { Book } from './Book';
import './BooksList.css';
const books: Book[] = […];
const BooksList: React.FC = () => {
return (
<table className="BooksList">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => {
const classNames = [];
if (book.rating === 5) {
classNames.push('Highlight');
}
return (
<tr key={book.id} className={classNames.join(' ')}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>{book.rating && ↩
<span>{'*'.repeat(book.rating)}</span>}</td>
</tr>
);
})}
</tbody>
</table>
);
};
export default BooksList;
Listing 8.5 Dynamic Use of Class Names in a Component (src/BooksList.tsx)
In the callback function of the map method, which is
responsible for displaying the individual lines, you declare
the classNames constant and initialize it with an empty array.
If the rating property of the data record has the value 5, you
add the Highlight entry to the array. In the tr element, you
define the className prop and join the individual class names
into a string using the join method. This way you can
support as many class names as you want, and it doesn't
matter if you assign none, one, or multiple class names.
In the component's stylesheet, you add another entry that
handles giving the lines with the Highlight class a yellow
background. In such a case, you must make sure that your
specification is more specific than the previous ones;
otherwise, the gray background would overwrite the yellow
one.
Note
The browser applies the CSS rules based on their
specificity. For example, a general tag selector is less
specific than a class selector, which in turn is less specific
than an ID selector. To increase the specificity, you can
also combine selectors—for example, a tag selector and a
class selector. The combination of parent and child
elements also increases the specificity of a selector.
table.BooksList {
border-collapse: collapse;
}
.BooksList th {
border-bottom: 3px solid black;
}
.BooksList tr:nth-child(2n) {
background-color: #ddd;
}
.BooksList tr.Highlight {
background-color: yellow;
}
.BooksList td {
padding: 5px 10px;
}
Listing 8.6 Styling of the “Highlight” Class (src/BooksList.css)
8.1.3 Improved Handling of Class Names via
the “classnames” Library
You don’t necessarily have to perform the task of merging
class names yourself as you can leave this task to an
external library. An established solution in this context is the
classnames library. You can add it to your application using
the npm install classnames command.
The classnames library comes with its own type definitions, so
you don't need to install an additional package for it if you
use TypeScript in your application. The classnames library
provides a function that you can pass various data types to,
based on which a list of concrete class names is then
calculated. In the simplest case, you use several strings,
which are then joined together accordingly.
Alternatively, you can also use objects. The key specifies the
class name, and the value indicates whether the class
should be active. For this purpose, the value is interpreted
as a Boolean value. In this case, the false value ensures that
the class name isn’t used, while true activates the
respective class. Applied to the BooksList component, this
leads to the source code shown in Listing 8.7:
import React from 'react';
import { Book } from './Book';
import './BooksList.css';
import classnames from 'classnames';
const books: Book[] = […];
const BooksList: React.FC = () => {
return (
<table className="BooksList">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => {
const classes = classnames({ Highlight: book.rating === 5 });
return (
<tr key={book.id} className={classes}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>{book.rating && ↩
<span>{'*'.repeat(book.rating)}</span>}</td>
</tr>
);
})}
</tbody>
</table>
);
};
export default BooksList;
Listing 8.7 Using the “classnames” Library (src/BooksList.tsx)
Another feature provided by the classnames library is the
support for arrays. Here too, class names are allowed as
strings and objects with class names as keys and Boolean
values as values. These arrays, if they are nested
structures, are first put into a flat hierarchy and then
applied.
Another tool when dealing with stylesheets is a
preprocessor like Sass or Less. These can also be integrated
into your React application.
8.1.4 Using Sass as CSS Preprocessor
CSS preprocessors add some useful features to the
functionality of CSS. In the case of the Sass preprocessor,
these include variables, nested rules, or mixins. The next
step is to integrate Sass into your application and use this
preprocessor instead of the previous stylesheets.
Before you can write your stylesheets in SCSS, one of the
stylesheet languages supported by Sass, you must first
install the sass package using the npn install -D sass
command. This is the SCSS preprocessor written in Dart that
is compiled into JavaScript so that it can be used in the build
process of your application.
In the next example, you style the BooksList component
using SCSS instead of the CSS import used previously. In the
BooksList component, you use SCSS variables to bundle the
background color in a central location. You also use nesting
to make the rules a little clearer.
First you create a new file named variables.scss. This file
contains the definition of the color variable, which makes
sure that the variables can be used in multiple places in
your application. The source code of this file is shown in
Listing 8.8:
$grey: #ddd;
$highlight: yellow;
Listing 8.8 Variable Definition in SCSS (src/variables.scss)
In the next step, you rename the BooksList.css file to
BooksList.scss and modify the source code as shown in
Listing 8.9:
@import './variables';
table.BooksList {
border-collapse: collapse;
th {
border-bottom: 3px solid black;
}
tr:nth-child(2n) {
background-color: $grey;
}
tr.Highlight {
background-color: $highlight;
}
td {
padding: 5px 10px;
}
}
Listing 8.9 “BooksList” Stylesheet in SCSS (src/BooksList.scss)
As you can see in the source code, you use the @import
statement to import the variables, which you can access by
their names in the stylesheet. Another special feature of the
SCSS stylesheet is that here the rules are nested within
each other: the table.BooksList selector forms a kind of
namespace where the rules are applied. This has the
advantage that the source code is kept clear and no
unwanted side effects are created by inadvertently
specifying global styles. If you need to access an outside
definition—for example, because you need to extend it or
specify it in more detail—you can prefix an internal selector
with & to refer to the outside definition without having to
repeat it.
To enable the new styling, you just need to move the import
of the stylesheet in the BooksList component from the CSS
file to the SCSS file.
Styling your components using stylesheets enables you to
keep the styling separate from the structure of your
application. However, to implement dynamics in the design,
you must create a separate class for each customization
and include it appropriately via the className prop. A more
direct option is to use inline styling.
8.2 Inline Styling
The simplest method of styling components is inline styling.
In this case, you insert the CSS specifications directly into
an element using the style prop. But unlike traditional inline
styling in HTML, in React it doesn’t consist of a string, but a
JavaScript object that is converted by React into a
corresponding style specification. In this context, you don’t
have the problem of having to deal with assembling a valid
string yourself, as you know from specifying dynamic class
names.
But you shouldn’t use inline styles excessively. The reason is
that inline styles extend the source code of your
components significantly and make it unreadable. In
addition, its reusability is then severely limited. You can
somewhat invalidate both arguments by using the
appropriate conventions: as inline styles are objects, you
can swap them out from the component's code and use the
full feature set of React, including JavaScript classes and
inheritance—which also somewhat invalidates the second
argument, as you can reuse the object structures and
extend them as needed.
Another disadvantage of inline styles, which cannot be
easily avoided, is that pseudoselectors such as :hover cannot
be implemented. For this purpose, you need to use
additional libraries, such as Emotion (Section 8.4).
However, a solution like the already presented combination
of the className prop and a preprocessor or the use of an
additional library is better and much more flexible.
Nevertheless, inline styles have their raison d'être.
Especially when it comes to individual and dynamic styles,
the use of inline styles can be useful. In Listing 8.10, you
extend the BooksList component to allow your users to click
on a line to highlight it:
import React, { useState, CSSProperties } from 'react';
import { Book } from './Book';
import './BooksList.scss';
const books: Book[] = […];
const BooksList: React.FC = () => {
const [active, setActive] = useState<number | null>(null);
return (
<table className="BooksList">
<thead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => {
const style: CSSProperties = {};
if (book.id === active) {
style.backgroundColor = 'yellow';
}
return (
<tr key={book.id} onClick={() => setActive(book.id)}
style={style}>
<td>{book.title}</td>
<td>{book.author}</td>
<td>{book.isbn}</td>
<td>{book.rating && ↩
<span>{'*'.repeat(book.rating)}</span>}</td>
</tr>
);
})}
</tbody>
</table>
);
};
export default BooksList;
Listing 8.10 Inline Styles (src/BooksList.tsx)
For the functionality, you first define a state in which you
keep the active data record. When you click on a row, you
set the ID of the data record as the active row. In the loop
where you display each row, you define an empty object of
type React.CSSProperties. This type defines the structure of
the style prop of React elements and helps you to specify
the correct style. Then you check if the current data record
is active, and if so, you set the background color in the style
object to yellow. You use the style prop to assign the style
object to the element.
Because inline styling has a higher weighting than element
and class styling, this specification overrides the default
styling.
As you can see in the example, inline styles in React are
slightly different from ordinary CSS rules. Because styles are
specified as objects, it’s syntactically not possible—or only
possible via a workaround—to use CSS properties, which are
usually written with a hyphen, as object keys. For this
reason, all properties are written in CamelCase. In the case
of the background-color property, this results in
backgroundColor. You specify the values as ordinary JavaScript
strings.
When specifying numerical values, such as for height or
width, there is another special feature: here, React
automatically inserts the px unit. If you need a different unit,
such as em, you can specify the value as a string—that is, as
'35em'.
Another alternative to styling React components is to use
CSS modules.
8.3 CSS Modules
CSS modules represent a feature that’s supported by Create
React App. This is standards-compliant CSS, but only valid
for the current component. In this case, you don’t have to
bother with namespacing on your own. CSS modules are
imported in a component. You can then access the styles
defined in the CSS module through an object structure. To
import the module correctly, the filename of the CSS
module must end in .module.css. Listing 8.11 contains the
source code of the module for styling the BooksList
component. You save the source code in a file called
BooksList.module.css:
.BooksList {
border-collapse: collapse;
}
.header {
border-bottom: 3px solid black;
}
.tableRow:nth-child(2n) {
background-color: #ddd;
}
.cell {
padding: 5px 10px;
}
Listing 8.11 CSS Module for the “BooksList” Component
(src/BooksList.module.css)
As you can see, the CSS module is standards-compliant CSS,
so you can make use of all the tools your development
environment offers for CSS. For CSS modules, you mainly
use class names. You can also insert tag names, but in most
cases the classes should be sufficient.
The CSS module can be integrated into the component by
way of importing it. Listing 8.12 contains the necessary
source code:
import React from 'react';
import { Book } from './Book';
import styles from './BooksList.module.css';
const books: Book[] = […];
const BooksList: React.FC = () => {
return (
<table className={styles.BooksList}>
<thead className={styles.header}>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => {
return (
<tr key={book.id} className={styles.tableRow}>
<td className={styles.cell}>{book.title}</td>
<td className={styles.cell}>{book.author}</td>
<td className={styles.cell}>{book.isbn}</td>
<td className={styles.cell}>
{book.rating && <span>{'*'.repeat(book.rating)}</span>}
</td>
</tr>
);
})}
</tbody>
</table>
);
};
export default BooksList;
Listing 8.12 Integration of the CSS Module into the “BooksList” Component
(src/BooksList.tsx)
The build process of Create React App supports the CSS
modules directly, and you don't need to do any further
configuration. This is also true if you use TypeScript, as in
the example. During the build process, the component and
CSS module are parsed, and automatically generated class
names are inserted in the rendered component. This
ensures that there are no name conflicts with other
components. The name of the class follows the pattern
<filename>_<classname>__<hash>. For example, in the .header
class example, this results in a class name like
BooksList_header__ayrMz.
As you can see in the source code of the CSS module,
pseudoselectors like hover or nth-child are also supported. To
use them, you need to append the selector to the class
name as usual.
For simple use cases, the functionality of CSS modules is
usually sufficient. But if it isn’t, you can combine this feature
with other functionalities and libraries if necessary, such as
with the classnames library.
By using CSS modules, you stay very close to the CSS
standard while still having the advantages of isolated styles
per component. Moreover, CSS modules additionally support
the native pseudoselectors. However, this is not the last
approach we’ll present for styling your React components. In
the following sections, you’ll get to know another possibility:
the Emotion library.
8.4 CSS in JavaScript Using Emotion
Emotion is a library for CSS-in-JS, an approach where you
make style specifications in JavaScript code. There are quite
a few libraries you can draw on. Probably the best known in
this area are Styled Components and Emotion. Styled
Components specializes in React, while Emotion takes a
framework-independent approach. In the next sections, we'll
introduce Emotion as a possible solution because it provides
the same syntax as Styled Components, but more features,
and you can use the library very well with React.
You can use Emotion either with the css prop or with the
styled approach. The css prop allows you to write the styles
in a prop and apply them directly in the element. This
variant is similar to direct styling via the style prop. The
styled approach follows an entirely different strategy than
the ways of styling a component we’ve presented up to this
point. The styled approach uses a kind of higher-order
component. This exploits a rarely used part of the
ECMAScript standard by using the tag function of the
template strings, a function executed to process the
template string.
By default, the JavaScript environment provides a standard
tag function that is used to perform variable substitution.
Emotion, or which follows the same principles as the idea
generator styled components, takes this idea further and
allows you to create components with local style
specifications via template strings. In addition to just styling
components, the Styled Components library offers
numerous other useful features, some of which we’ll take a
closer look at ahead and integrate into the example
application.
8.4.1 Installing Emotion
Emotion is an open-source library developed under the MIT
license on GitHub. Depending on which variant of Emotion
you want to use, you need to install different packages. For
use in the css prop, all you need is the @emotion/react
package, which you can install using npm install
@emotion/react. If you want to use the styled approach, you
have to install the @emotion/styled package in addition to
that; that is, you need to run the npm install @emotion/react
@emotion/styled command. Both packages come with their
own type definitions, so you don't need to install additional
packages to use them with TypeScript.
8.4.2 Using the “css” Prop
The css prop of Emotion digs deep into React's build process
and makes the Babel plugin, which is responsible for
translation, use the jsx function instead of the
React.createElement function. To use this variant of Emotion
with Create React App and TypeScript, you need to adjust
the TypeScript configuration and add the
"jsxImportSource":"@emotion/react" entry in the compilerOptions
of the tsconfig.json file. Then you still need to add a
comment at the beginning of the file so that Babel
processes it correctly. Listing 8.13 show an example of using
the css prop of Emotion:
/** @jsxRuntime classic */
/** @jsx jsx */
import { jsx, css } from '@emotion/react';
import React from 'react';
import { Book } from './Book';
const headerStyle = {
borderBottom: '3px solid black',
};
const cellStyle = {
padding: '5px 10px',
};
const books: Book[] = […];
const BooksList: React.FC = () => {
return (
<table css={{ borderCollapse: 'collapse' }}>
<thead css={headerStyle}>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</thead>
<tbody>
{books.map((book) => {
return (
<tr
key={book.id}
css={css`
&:nth-of-type(2n) {
background-color: #ddd;
}
`}
>
<td css={cellStyle}>{book.title}</td>
<td css={cellStyle}>{book.author}</td>
<td css={cellStyle}>{book.isbn}</td>
<td css={cellStyle}>
{book.rating && <span>{'*'.repeat(book.rating)}</span>}
</td>
</tr>
);
})}
</tbody>
</table>
);
};
export default BooksList;
Listing 8.13 Use of the “css” Prop of Emotion (src/BooksList.tsx)
The entry point to the file consists of two comments that
Emotion and the build process respectively need to handle
the css prop correctly. The first comment switches the
runtime of the preset-react plugin to classic mode. The
second comment makes sure that the css prop is processed
correctly by Emotion. If your development environment has
problems with the correct processing of the css prop, you
can fix it with the line: /// <reference
types="@emotion/react/types/css-prop" />
Before you get into the component, you define two style
objects. The headerStyle object is responsible for the
appearance of the table's header row, while cellStyle is
responsible for the appearance of the table's individual
cells.
In the BooksList component itself, you can see different
variants of using the css prop:
Inline object
In the table element, you define the css prop and set the
styling directly inline. On the one hand, this has the
advantage that you can see at a glance which styles are
defined for an element. If you use this approach for
multiple elements and have multiple style specifications
per element, the source code of your component quickly
becomes confusing. You should therefore only use this
variant sparingly.
External object
For the table header and the individual cells, you swap out
the styles to separate objects so that the style definitions
do not appear directly in the component. Using this
variant, you achieve a tidier source code in the
component and can reuse the individual objects in several
places, as you can see with the td elements. Also, you can
swap out these style objects to a separate file if needed.
CSS template string
In the third variant, you define a template string with the
styles and provide it with the css function of Emotion. With
this approach, you can use pseudoselectors such as nth-
of-type or hover, for example. The same approach applies
to the template string as to an object: you can define it
either inline as in the example or outside the component.
Emotion offers a second variant—the styled approach—to
style your components.
8.4.3 The Styled Approach of Emotion
The styled approach, also referred to as Emotion styled
components, is a styling variant in which you use template
strings to produce new components. Emotion is not the first
library to implement this approach. The solution was
originally derived from libraries like Styled Components and
Glamorous.
The advantage of styled components over the css prop is
that you don't need to make any adjustments to your
TypeScript configuration and your component, especially
when using TypeScript. To be able to use styled components,
you just need to install the @emotion/styled package.
There are several approaches to organizing styled
components. These range from placing the components
directly in the file where they’re needed to having a
separate file per Styled Component. The first approach has
the disadvantage that styled components are relatively
tightly coupled to the component in which they are used.
The second approach results in a very large number of files
that may make your application very confusing. A good
middle ground is to group styled components thematically
into files.
For the sample application, you create a new file named
BooksList.styles.ts. The styled components of Emotion
provide tagging capabilities for all HTML elements, which
you can use to create corresponding elements with style
definitions for your application. You can directly convert the
static styles of the table for the table element, the table
header, and the cells. The syntax is similar to the CSS
template strings you learned about in the context of the css
prop. Listing 8.14 shows the implementation of the
components as styled components:
import styled from '@emotion/styled';
export const Table = styled.table`
border-collapse: collapse;
`;
export const THead = styled.thead`
border-bottom: 3px solid black;
`;
export const TD = styled.td`
padding: 5px 10px;
`;
Listing 8.14 Creation of Styled Components (src/BooksList.styles.ts)
To create the styled components, you can use the
styled.table, styled.thead, and styled.td tag functions. You
pass the CSS information to them in the template string.
Because styled components are JavaScript strings and not
regular CSS, you have no direct support in your
development environment. However, a plugin is available
for both WebStorm and Visual Studio that gives you syntax
highlighting and autocompletion for styled components.
The result of the tag function is a full-fledged React
component that you can use within your application:
import React from 'react';
import { Book } from './Book';
import { Table, TD, THead } from './BooksList.styles';
const books: Book[] = […];
const BooksList: React.FC = () => {
return (
<Table>
<THead>
<tr>
<th>Title</th>
<th>Author</th>
<th>ISBN</th>
<th>Rating</th>
</tr>
</THead>
<tbody>
{books.map((book) => {
return (
<tr key={book.id}>
<TD>{book.title}</TD>
<TD>{book.author}</TD>
<TD>{book.isbn}</TD>
<TD>{book.rating && ↩
<span>{'*'.repeat(book.rating)}</span>}</TD>
</tr>
);
})}
</tbody>
</Table>
);
};
export default BooksList;
Listing 8.15 Integration of the Styled Component (src/BooksList.tsx)
As you can see in Listing 8.15, you can choose the names of
your styled components in such a way that, except for the
initial uppercase letter, you won't notice any difference from
the React elements you've been using so far. However, the
styled components of Emotion can do much more for you
and your application.
8.4.4 Pseudoselectors in Styled Components
Up to this point, you’ve used pseudoselectors to ensure that
the individual rows of the table were colored differently. You
can also achieve this with styled components by using such
selectors in the template string. Listing 8.16 contains the
implementation of the TR component that takes care of the
coloring of the table rows:
export const TR = styled.tr`
&:nth-of-type(2n) {
background-color: #ddd;
}
`;
Listing 8.16 Pseudoselectors in Styled Components (src/BooksList.styles.ts)
Instead of the nth-child selector, you use the nth-of-type
selector in this example. The reason is that nth-of-type
produces the same result as nth-child, but the former is the
better variant for server-side rendering. For this reason, you
should generally use this selector. If you use nth-child, you
will receive a corresponding warning in the developer tools
console of your browser advising you against using this
selector.
To activate the new component, you need to import it into
the BooksList component and replace the tr element in the
body of the table with the TR styled component.
8.4.5 Dynamic Styling
You can also make your styled components dependent on
props. This further increases the flexibility of your
components and allows you to support multiple variants. To
demonstrate a styled component that can be controlled by
props, you allow your users to highlight an entry in the table
by clicking on a row. The BooksList component then sets the
highlight prop for the respective line, and the TR component
takes care of setting the correct background color.
import { css } from '@emotion/react';
import styled from '@emotion/styled';
type TRProps = {
highlight: boolean;
};