0% found this document useful (0 votes)
302 views373 pages

Learn React Hook

Learn React Hooks, Second Edition by Daniel Bugl provides a comprehensive guide to utilizing React Hooks, Context, Suspense, and Form Actions for scalable state management and clean code. The book covers the fundamentals of React and Hooks, practical applications, and advanced techniques with real-world examples. It aims to enhance developers' skills in building efficient React applications while emphasizing best practices and performance.

Uploaded by

Ahmed Naeem
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
302 views373 pages

Learn React Hook

Learn React Hooks, Second Edition by Daniel Bugl provides a comprehensive guide to utilizing React Hooks, Context, Suspense, and Form Actions for scalable state management and clean code. The book covers the fundamentals of React and Hooks, practical applications, and advanced techniques with real-world examples. It aims to enhance developers' skills in building efficient React applications while emphasizing best practices and performance.

Uploaded by

Ahmed Naeem
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 373

Learn React Hooks

Second Edition

Unlock scalable state, performance, and clean code


with Hooks, Context, Suspense, and Form Actions

Daniel Bugl
Learn React Hooks
Second Edition
Copyright © 2025 Packt Publishing

All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in
any form or by any means, without the prior written permission of the publisher, except in the case of brief
quotations embedded in critical articles or reviews.
Every effort has been made in the preparation of this book to ensure the accuracy of the information
presented. However, the information contained in this book is sold without warranty, either express or
implied. Neither the author, nor Packt Publishing or its dealers and distributors, will be held liable for any
damages caused or alleged to have been caused directly or indirectly by this book.
Packt Publishing has endeavored to provide trademark information about all of the companies and products
mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee
the accuracy of this information.

Portfolio Director: Ashwin Nair


Relationship Lead: Bhavya Rao
Project Manager: Aparna Nair
Content Engineer: Runcil Rebello
Technical Editor: Sweety Pagaria
Copy Editor: Safis Editing
Indexer: Pratik Shirodkar
Proofreader: Runcil Rebello
Production Designer: Pranit Padwal
Growth Lead: Anamika Singh

First published: October 2019


Second edition: May 2025

Production reference: 1250425

Published by Packt Publishing Ltd.


Grosvenor House
11 St Paul’s Square
Birmingham
B3 1RB, UK.

ISBN 978-1-83620-917-1

www.packtpub.com
To my family and friends for supporting me during the creation of this book.

To my parents, who have supported me throughout my whole life.

To my co-founder, Georg Schelkshorn, and my business partner, Matthias Zronek, who always
challenge me and continually inspire my growth. Thank you for taking care of business while I was
writing this book.

To my amazing girlfriend, Junxian Wang, for improving my life in many ways, for making me more
productive, and for always taking care of me. I love you so much.

Without all of you, this book would not have been possible.

– Daniel Bugl
Foreword
I first met Daniel in school when we were about 10 years old during a voluntary course called Web
Design. It didn’t take us long before we started skipping classes to discuss Linux topics or game
development instead – much to our teachers’ dismay. From that point on, I can’t remember a time
when we weren’t working on projects together. What started with PHP-based game development
ideas soon evolved into more sophisticated endeavors.
In 2015, Daniel recommended attending a React meetup to explore this new technology that
was just gaining traction. There, we wrote our first lines of React code, marking the beginning
of our journey with what would become an integral part of our web technology stack. Soon
after, Daniel and I founded our own company, TouchLay. Initially, we focused on creating
interactive applications for touch devices. Over the years, we’ve developed various React full-stack
applications for numerous clients.
Throughout these years, watching Daniel establish himself as a respected voice in the React
community has always been an inspiration to me. He has already published two successful React
books and maintains popular open source libraries. In his work, he leads and consults development
teams for insurance and industrial companies as well as e-government projects using React and
Next.js.
Daniel excels at establishing guidelines and mentoring team members, whether they’re just
starting with React or looking to improve their skills. His well-structured approach shows teams
how to consistently reach their goals. What makes Daniel stand out is his passion for education and
the sharing of knowledge. His approach to every technical challenge is to thoroughly understand
the whole topic in all its detail. He never gets tired of digging deeper. Still, Daniel manages to
focus on practical solutions derived from years of real-world expertise with React.
In this book, you can now experience what has helped so many teams excel.
Let’s start your journey to bring your React game to the next level.
Georg Schelkshorn
Senior Software Engineer and Technical Advisor
Contributors

About the author


Daniel Bugl is a full-stack developer, product designer, and entrepreneur focusing on web
technologies. He has a bachelor of science degree in business informatics and information systems
and a master of science degree in data science from the Vienna University of Technology (TU
Wien). He is a contributor to many open source projects and a member of the React community.
He also co-founded and runs his own hardware/software start-up, TouchLay, which helps other
companies present their products and services. At his company, he constantly works with web
technologies, particularly making use of React, React Hooks, and Next.js. In the past couple of
years, he, together with Georg Schelkshorn and Matthias Zronek, has been a software development
consultant and full-stack developer for large enterprises and the public sector, among other things,
working on citizen services for the Austrian government.

I want to thank the people involved in the production of this book, my co-founder, Georg
Schelkshorn, my business partner, Matthias Zronek, my family and friends, and my girlfriend,
Junxian Wang.
About the reviewers
Oleksander Tymoshenko is a frontend engineer and React-Native developer who began his
tech journey at the age of 12. During university, he discovered his passion for frontend development,
specializing in React and React Native for mobile platforms. He has launched over 12 apps (using
TypeScript and Flow) and successfully led projects through the entire software development
lifecycle. His expertise lies in creating dynamic user interfaces and cross-platform mobile solutions,
with a strong focus on performance and user experience.

Ademola Adegbuyi’s tech journey began 16 years ago as a self-taught engineer driven by
curiosity. His early experimental spirit laid the groundwork for an adventurous career. At Paystack
(a Stripe company), he tackled challenges head-on and brought innovative ideas to life. Now
working as a senior frontend engineer at Cleo in London, he infuses his projects with both
innovative ideas and solid technical know-how. He’s written and reviewed articles for leading
web publications such as Smashing Magazine and now lends his expertise as a reviewer for Packt.
Ademola is always exploring new ideas, mentoring emerging talent, and pushing the limits of
what frontend engineering can achieve.

Luca Del Puppo is a senior software engineer who is passionate about JavaScript and TypeScript.
In his free time, he enjoys exploring new technologies, enhancing his skills, creating YouTube
content, and writing technical articles. Trail running is essential to him, and he loves doing it in
his cherished Dolomites.

Matthias Zronek is a senior software engineer and technical advisor with more than 20 years
of professional experience. He loves coding and is passionate about every new programming
language he learns. His current focus is on the development of full stack web frameworks to enable
frontend teams to create scalable and sustainable web applications using React and TypeScript.
In his free time, he writes highly optimized applications for PC benchmarking in C++ or tries to
reverse-engineer interesting binaries. If he is not in front of a (disassembled) computer, he spends
time with his loving wife, their newborn son, and their fascinatingly weird cat.
Georg Schelkshorn is a full-stack React enthusiast with a passion for DevOps and crafting
user-friendly interfaces. Alongside Daniel Bugl, he co-manages TouchLay, their company
specializing in interactive hardware and software solutions, as well as advising enterprises on
how to successfully implement modern web projects. Through many challenging endeavors, he
has gained a deep understanding of what is needed to make a software project work at scale.
Georg loves diving into new topics and hopes you will enjoy exploring this book’s learning journey.
Learn more on Discord
To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
Table of Contents

Preface  xxi

Part 1: Introduction to Hooks  1

Chapter 1: Introducing React and React Hooks  3

Technical requirements ������������������������������������������������������������������������������������������������������ 4


Principles of React �������������������������������������������������������������������������������������������������������������� 4
Motivation to use React Hooks �������������������������������������������������������������������������������������������� 6
Confusing classes • 6
Wrapper hell • 8
Hooks to the rescue! • 10
Setting up the development environment �������������������������������������������������������������������������� 11
Installing VS Code and extensions • 11
Setting up a project with Vite • 14
Alternatives to Vite • 18
Setting up ESLint and Prettier to enforce best practices and code style • 18
Installing the necessary dependencies • 18
Configuring Prettier • 19
Configuring the Prettier extension • 20
Creating a Prettier ignore file • 20
Configuring ESLint • 20
Adding a new script to run our linter • 22
x Table of Contents

Getting started with React Hooks ������������������������������������������������������������������������������������� 22


Starting with a class component • 23
Using Hooks instead • 26
Comparing the solutions • 28
Class component • 28
Function component with a Hook • 29
Advantages of Hooks • 30
Migrating to Hooks • 30
The Hooks mindset • 31
Rules of Hooks • 31
Summary �������������������������������������������������������������������������������������������������������������������������� 32
Questions �������������������������������������������������������������������������������������������������������������������������� 32
Further reading ����������������������������������������������������������������������������������������������������������������� 33

Chapter 2: Using the State Hook  35

Technical requirements ���������������������������������������������������������������������������������������������������� 35


Reimplementing the State Hook ��������������������������������������������������������������������������������������� 36
Resolving issues with our simple Hook implementation • 39
Using a global variable • 39
Defining multiple Hooks • 40
Adding support for multiple Hooks • 40
Can we define conditional Hooks? • 43
Comparing our reimplementation to real Hooks • 46
Potential alternative Hook APIs ���������������������������������������������������������������������������������������� 47
Named Hooks • 47
Hook factories • 47
Other alternatives • 48
Solving common problems with Hooks ���������������������������������������������������������������������������� 49
Solving conditional Hooks • 49
Always defining the Hook • 49
Splitting up components • 50
Table of Contents xi

Solving Hooks in loops • 51


Using an array • 51
Splitting up components • 52
Summary �������������������������������������������������������������������������������������������������������������������������� 52
Questions �������������������������������������������������������������������������������������������������������������������������� 53
Further reading ����������������������������������������������������������������������������������������������������������������� 53

Chapter 3: Writing Your First Application with React Hooks  55

Technical requirements ���������������������������������������������������������������������������������������������������� 55


Structuring React projects ������������������������������������������������������������������������������������������������ 56
Folder structure • 56
Defining the features • 57
Coming up with an initial structure • 57
Component structure • 58
Implementing static components ������������������������������������������������������������������������������������� 60
Implementing the user-related static components • 61
The Login component • 61
The Register component • 64
The Logout component • 65
The UserBar component • 66
Implementing posts • 68
The Post component • 69
The CreatePost component • 70
The PostList component • 71
Putting the app together • 73
Implementing stateful components with Hooks ��������������������������������������������������������������� 76
Adding Hooks for the user features • 76
Adjusting UserBar • 76
Adding validation • 79
Passing the user to CreatePost • 81
xii Table of Contents

Adding Hooks for the post features • 82


Adjusting the App component • 82
Adjusting the CreatePost component • 83
Summary �������������������������������������������������������������������������������������������������������������������������� 86
Questions �������������������������������������������������������������������������������������������������������������������������� 86
Further reading ����������������������������������������������������������������������������������������������������������������� 86

Part 2: Using Hooks With Real-World Examples  89

Chapter 4: Using the Reducer and Effect Hooks  91

Technical requirements ���������������������������������������������������������������������������������������������������� 91


Reducer Hooks versus State Hooks ������������������������������������������������������������������������������������ 92
Limitations of the State Hook • 92
Reducers • 94
Actions • 94
Defining reducers • 95
The Reducer Hook • 97
Using Reducer Hooks �������������������������������������������������������������������������������������������������������� 97
Turning a State Hook into a Reducer Hook • 98
Defining actions • 98
Implementing the reducer • 99
Defining the Reducer Hook • 100
Using Effect Hooks ����������������������������������������������������������������������������������������������������������� 101
Remember componentDidMount and componentDidUpdate? • 101
From life cycle methods to Effect Hooks • 103
Triggering an effect only when certain props change • 104
Triggering an effect only on mount • 105
Cleaning up effects • 105
Implementing an Effect Hook in our blog app • 106
Table of Contents xiii

Summary ������������������������������������������������������������������������������������������������������������������������� 107


Questions ������������������������������������������������������������������������������������������������������������������������ 108
Further reading ��������������������������������������������������������������������������������������������������������������� 108

Chapter 5: Implementing React Contexts  111

Technical requirements ���������������������������������������������������������������������������������������������������� 111


Introducing React Context ����������������������������������������������������������������������������������������������� 112
Passing down props • 112
Implementing themes via context ����������������������������������������������������������������������������������� 114
Defining the context • 114
Quick detour – absolute imports • 115
Defining the consumer • 116
Using Hooks to consume a context • 117
Defining the provider • 120
Nesting providers • 122
Alternatives to contexts ���������������������������������������������������������������������������������������������������� 124
Using context for global state ������������������������������������������������������������������������������������������� 125
Defining the context • 125
Defining the context provider • 126
Refactoring the app to use UserContext • 127
Summary ������������������������������������������������������������������������������������������������������������������������ 130
Questions ������������������������������������������������������������������������������������������������������������������������ 130
Further reading ���������������������������������������������������������������������������������������������������������������� 131

Chapter 6: Using Hooks and React Suspense for Data Fetching  133

Technical requirements ��������������������������������������������������������������������������������������������������� 133


Setting up a simple backend server ���������������������������������������������������������������������������������� 134
Creating the db.json file • 134
Installing the json-server tool • 137
Configuring the package.json scripts • 138
Configuring a proxy • 140
xiv Table of Contents

Requesting resources using an Effect and a State/Reducer Hook ������������������������������������� 141


Fetching posts from the server • 141
Quick detour: The async/await construct • 143
Creating new posts on the server • 145
Using TanStack Query to request resources and make changes ���������������������������������������� 147
Setting up the library • 147
Fetching posts using a Query Hook • 149
Creating posts using a Mutation Hook • 152
Introducing React Suspense and Error Boundaries ���������������������������������������������������������� 155
Setting up a Suspense Boundary • 155
Setting up an Error Boundary • 158
Summary ������������������������������������������������������������������������������������������������������������������������� 161
Questions ������������������������������������������������������������������������������������������������������������������������� 161
Further reading ���������������������������������������������������������������������������������������������������������������� 162

Chapter 7: Using Hooks for Handling Forms  163

Technical requirements ��������������������������������������������������������������������������������������������������� 163


Handling form submission with the Action State Hook �������������������������������������������������� 164
Introducing the Action State Hook • 165
Using the Action State Hook • 165
Simulating blocking UI ���������������������������������������������������������������������������������������������������� 167
Implementing a (purposefully slow) Comment component • 168
Implementing a CommentList component • 168
Implementing the CommentSection component • 169
Testing out the simulated blocking UI • 170
Avoiding blocking UI with the Transition Hook ��������������������������������������������������������������� 171
Using the Transition Hook • 171
Testing out the non-blocking Transition • 172
Using the Optimistic Hook to implement optimistic updates ������������������������������������������ 173
Implementing optimistic comment creation • 174
Table of Contents xv

Summary ������������������������������������������������������������������������������������������������������������������������ 178


Questions ������������������������������������������������������������������������������������������������������������������������� 179
Further reading ���������������������������������������������������������������������������������������������������������������� 179

Chapter 8: Using Hooks for Routing  181

Technical requirements ��������������������������������������������������������������������������������������������������� 181


Introducing React Router ������������������������������������������������������������������������������������������������ 182
Setting up React Router • 183
Creating a new route and using the Param Hook ������������������������������������������������������������� 185
Linking to routes using the <Link> component �������������������������������������������������������������� 188
Defining a navigation bar using <NavLink> • 190
Programmatically navigating using the Navigation Hook ������������������������������������������������ 192
Summary ������������������������������������������������������������������������������������������������������������������������ 194
Questions ������������������������������������������������������������������������������������������������������������������������ 194
Further reading ��������������������������������������������������������������������������������������������������������������� 194

Chapter 9: Advanced Hooks Provided by React  197

Technical requirements ��������������������������������������������������������������������������������������������������� 197


Overview of built-in React Hooks ����������������������������������������������������������������������������������� 198
useState • 199
useEffect • 200
useContext • 200
useReducer • 201
useActionState • 201
useFormStatus • 202
useOptimistic • 203
useTransition • 203
Using utility Hooks ��������������������������������������������������������������������������������������������������������� 204
useRef • 205
Auto-focusing an input field using a Ref Hook • 206
Changing state within a ref • 207
xvi Table of Contents

Using refs to persist mutable values across re-renders • 209


Passing refs as props • 211
Creating ref contents only once • 212
useImperativeHandle • 212
useId • 215
useSyncExternalStore • 218
useDebugValue • 220
Using Hooks for performance optimizations ������������������������������������������������������������������� 222
useDeferredValue • 223
Implementing a search without deferred values • 223
Introducing deferred values • 227
useMemo • 228
useCallback • 229
Using Hooks for advanced effects ������������������������������������������������������������������������������������ 230
useLayoutEffect • 230
useInsertionEffect • 230
Summary ������������������������������������������������������������������������������������������������������������������������ 230
Questions ������������������������������������������������������������������������������������������������������������������������� 231
Further reading ���������������������������������������������������������������������������������������������������������������� 231

Chapter 10: Using Community Hooks  233

Technical requirements �������������������������������������������������������������������������������������������������� 233


Using Hooks to manage application state ����������������������������������������������������������������������� 234
useLocalStorage • 234
useHistoryState • 238
Debouncing with Hooks �������������������������������������������������������������������������������������������������� 241
Debouncing changes in the post editor • 242
Difference between debounced and deferred values • 243
Learning about various utility Hooks ������������������������������������������������������������������������������ 244
useCopyToClipboard • 244
useHover • 246
Table of Contents xvii

Finding more community Hooks ������������������������������������������������������������������������������������� 248


Summary ������������������������������������������������������������������������������������������������������������������������ 249
Questions ������������������������������������������������������������������������������������������������������������������������ 249
Further reading ��������������������������������������������������������������������������������������������������������������� 249

Part 3: Refactoring and Migrating Existing Code  251

Chapter 11: Rules of Hooks  253

Using Hooks �������������������������������������������������������������������������������������������������������������������� 253


Order of Hooks ���������������������������������������������������������������������������������������������������������������� 254
Names of Hooks �������������������������������������������������������������������������������������������������������������� 257
Enforcing the rules of Hooks ������������������������������������������������������������������������������������������� 258
Summary ������������������������������������������������������������������������������������������������������������������������ 259
Questions ������������������������������������������������������������������������������������������������������������������������ 259
Further reading ��������������������������������������������������������������������������������������������������������������� 259

Chapter 12: Building Your Own Hooks  261

Technical requirements ��������������������������������������������������������������������������������������������������� 261


Creating a custom Theme Hook �������������������������������������������������������������������������������������� 262
Creating the custom Theme Hook • 263
Using the custom Theme Hook • 264
Creating a custom User Hook ������������������������������������������������������������������������������������������ 265
Creating the custom User Hook • 265
Using the custom User Hook • 266
Creating custom API Hooks ��������������������������������������������������������������������������������������������� 270
Extracting custom API Hooks • 271
Using custom API Hooks • 272
Creating a Debounced History State Hook ���������������������������������������������������������������������� 275
Creating the Debounced History State Hook • 275
Using the Debounced History State Hook • 277
xviii Table of Contents

Testing custom Hooks ����������������������������������������������������������������������������������������������������� 278


Setting up Vitest and the React Testing Library • 279
Testing a simple Hook • 280
Creating the Counter Hook • 280
Creating unit tests for the Counter Hook • 281
Testing the Theme Hook • 284
Testing the User Hook • 286
Testing asynchronous Hooks • 288
Running all tests • 290
Summary ������������������������������������������������������������������������������������������������������������������������� 291
Questions ������������������������������������������������������������������������������������������������������������������������� 291
Further reading ��������������������������������������������������������������������������������������������������������������� 292

Chapter 13: Migrating from React Class Components  293

Technical requirements �������������������������������������������������������������������������������������������������� 293


Handling state with React class components ������������������������������������������������������������������ 294
Designing the app structure • 294
Initializing the project • 296
Defining the app structure • 297
Defining the components • 298
Defining the Header component • 298
Defining the AddTodo component • 298
Defining the TodoList component • 299
Defining the TodoItem component • 300
Defining the TodoFilter component • 301
Starting the app • 302
Implementing dynamic code • 302
Defining a mock API • 302
Defining the StateContext • 303
Making the App component dynamic • 303
Making the AddTodo component dynamic • 307
Table of Contents xix

Making the TodoList component dynamic • 309


Making the TodoItem component dynamic • 310
Making the TodoFilter component dynamic • 311
Starting the app • 313
Migrating from React class components �������������������������������������������������������������������������� 313
Migrating the TodoItem component • 314
Migrating the TodoList component • 315
Migrating the TodoFilter component • 316
Migrating the AddTodo component • 317
Migrating the state management and App component • 319
Defining the actions • 319
Defining the reducers • 319
Migrating the App component • 322
Trade-offs of React class components vs React Hooks ����������������������������������������������������� 325
Summary ������������������������������������������������������������������������������������������������������������������������ 326
Questions ������������������������������������������������������������������������������������������������������������������������ 328
Further reading ��������������������������������������������������������������������������������������������������������������� 328

Other Books You May Enjoy  331

Index  335
Preface
Hi there – I am Daniel, an entrepreneur, software development consultant, and full-stack devel-
oper with a focus on technologies in the React ecosystem.

In my time as a software development consultant and developer for enterprises and the public
sector, I have observed a common challenge: developers often struggle with advanced React con-
cepts due to a lack of in-depth understanding. Hooks in particular are a big source of confusion
there. Often, it is hard to know what the best practices are and how to best structure and design
an app with React and React Hooks.

In this book, I want to teach you everything you need to know to build a modern and maintainable
frontend with React. I will teach Hooks from the ground up, to ensure that you understand their
limitations and where they shine. I will cover various common use cases that I have frequently
encountered in my career – such as managing application state, when and how to use React Con-
texts, data fetching, form handling, and routing. I will also teach you when and how to build your
own Hooks to keep your app maintainable and efficiently reuse logic across multiple components.
In my opinion, custom Hooks are often underutilized in projects, but they can bring immense
value, especially in larger projects.

All of these concepts will be taught with practical examples so that you can immediately see them
in action and start using them in your own projects. I sincerely hope you enjoy reading this book.
If you have any questions or feedback, feel free to reach out to me!

Who this book is for


This book is for developers who already know how to work with React and want to learn how
React Hooks and modern technologies related to them, such as form actions, Context, and Sus-
pense, work in depth. Even if you already know about Hooks, this book will teach you how they
work internally, so you can gain a deeper understanding of them. Additionally, you will learn
some tips and tricks, as well as best practices on how to effectively develop React applications.
xxii Preface

What this book covers


Chapter 1, Introducing React and React Hooks, covers the fundamental principles of React and React
Hooks, as well as setting up a modern project with React.

Chapter 2, Using the State Hook, explains how Hooks work in depth by reimplementing and using
the State Hook, learning about the limitations of Hooks along the way.

Chapter 3, Writing Your First Application with React Hooks, takes what we learned from the first two
chapters and puts it into practice by creating a blog application using React Hooks.

Chapter 4, Using the Reducer and Effect Hooks, covers these two essential Hooks, focusing on when
and how to use them in practice by implementing them in our blog app.

Chapter 5, Implementing React Contexts, explains React Context and how it can be used in an ap-
plication, as well as in combination with Hooks.

Chapter 6, Using Hooks and React Suspense for Data Fetching, covers requesting resources from a
server using Effect and State Hooks. Then, we learn how to request resources more efficiently
using TanStack Query, React Suspense, and error boundaries.

Chapter 7, Using Hooks for Handling Forms, is a deep dive into form handling with React, especially
focusing on new paradigms such as form actions, transitions, and optimistic updates.

Chapter 8, Using Hooks for Routing, introduces React Router and shows how to use Hooks to get
params from a route, as well as triggering dynamic route changes.

Chapter 9, Advanced Hooks Provided by React, gives an overview of all the built-in Hooks that React
provides out of the box, focusing on all the advanced Hooks that have not been covered yet in
the book.

Chapter 10, Using Community Hooks, gives an overview of various useful Hooks provided by the
React community, as well as information on where to find more Hooks.

Chapter 11, Rules of Hooks, teaches you the rules you need to know to start building your own Hooks.

Chapter 12, Building Your Own Hooks, covers how to build your own custom Hooks by extracting
existing logic into a Hook. By knowing when and how to create custom Hooks, you will be able
to refactor and maintain your apps in a way that scales.

Chapter 13, Migrating from React Class Components, provides a guide on how to effectively migrate
existing applications from React class components to React Hooks.
Preface xxiii

To get the most out of this book


A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com.

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• VS Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

If you are using the digital version of this book, we advise you to type the code yourself or access
the code from the book’s GitHub repository (a link is available in the next section). Doing so will
help you avoid any potential errors related to the copying and pasting of code.

Download the example code files


The code bundle for the book is hosted on GitHub at https://github.com/PacktPublishing/
Learn-React-Hooks-Second-Edition/. We also have other code bundles from our rich catalog
of books and videos available at https://github.com/PacktPublishing. Check them out!

Download the color images


We also provide a PDF file that has color images of the screenshots/diagrams used in this book.
You can download it here: https://packt.link/gbp/9781836209171.
xxiv Preface

Conventions used
There are a number of text conventions used throughout this book.

CodeInText: Indicates code words in text, database table names, folder names, filenames, file
extensions, pathnames, dummy URLs, user input, and Twitter handles. For example: “Then, we
define the componentDidMount life cycle method, where we pull data from an API.”

A block of code is set as follows:


fetchData() {
fetch(`http://my.api/${this.props.name}`)
.then(...)
}

When we wish to draw your attention to a particular part of a code block, the relevant lines or
items are set in bold:
componentDidMount() {
this.fetchData()
}
componentDidUpdate(prevProps) {
if (this.props.name !== prevProps.name) {
this.fetchData()
}
}

Any command-line input or output is written as follows:


$ npm create [email protected] .

Bold: Indicates a new term, an important word, or words that you see on the screen. For instance,
words in menus or dialog boxes appear in the text like this. For example: “A sidebar should open,
where you can see Search Extensions in Marketplace at the top. Enter an extension name here
and click on Install to install it.”
Preface xxv

Warnings or important notes appear like this.

Tips and tricks appear like this.

Get in touch
Feedback from our readers is always welcome.

General feedback: Email [email protected] and mention the book’s title in the subject of
your message. If you have questions about any aspect of this book, please email us at questions@
packtpub.com.

Errata: Although we have taken every care to ensure the accuracy of our content, mistakes do
happen. If you have found a mistake in this book, we would be grateful if you reported this to us.
Please visit http://www.packtpub.com/submit-errata, click Submit Errata, and fill in the form.

Piracy: If you come across any illegal copies of our works in any form on the internet, we would
be grateful if you would provide us with the location address or website name. Please contact us
at [email protected] with a link to the material.

If you are interested in becoming an author: If there is a topic that you have expertise in and you
are interested in either writing or contributing to a book, please visit http://authors.packtpub.
com/.
xxvi Preface

Share your thoughts


Once you’ve read Learn React Hooks, Second Edition, we’d love to hear your thoughts! Please
click here to go straight to the Amazon review page for this book and share your feedback.

Your review is important to us and the tech community and will help us make sure we’re deliv-
ering excellent quality content.
Download a free PDF copy of this book
Thanks for purchasing this book!

Do you like to read on the go but are unable to carry your print books everywhere?

Is your eBook purchase not compatible with the device of your choice?

Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.

Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical
books directly into your application.

The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free
content in your inbox daily.

Follow these simple steps to get the benefits:

1. Scan the QR code or visit the link below:

https://packt.link/free-ebook/9781836209171

2. Submit your proof of purchase.


3. That’s it! We’ll send your free PDF and other benefits to your email directly.
Part 1
Introduction to Hooks
In this part, you will learn how to set up a modern React project, as well as learning about the
basics of React and React Hooks. You will also learn why and how to use Hooks. Toward the end
of this part, you will build a blog app, which will serve as the basis for all further chapters in this
book. Along the way, you will also learn about the latest JavaScript and React features.

This part has the following chapters:

• Chapter 1, Introducing React and React Hooks


• Chapter 2, Using the State Hook
• Chapter 3, Writing Your First Application with React Hooks
1
Introducing React and
React Hooks
React is a JavaScript library used to build efficient and extensible web applications. React was
developed by Meta and is used in many large-scale web applications, such as Facebook, Instagram,
Netflix, Shopify, Airbnb, Cloudflare, and the BBC.

In this book, we are going to learn how to build complex and efficient user interfaces with React,
while keeping the code simple and extensible. Using the paradigm of React Hooks, we can greatly
simplify dealing with state and effects in web applications, ensuring the potential for growing and
extending the application later. We are also going to learn about React Context, React Suspense,
and Form Actions, as well as how they can be used with Hooks. Finally, we are going to learn how
to build our own Hooks and how to migrate existing applications from React class components
to a React Hooks-based architecture.

In this first chapter, we are going to learn about the fundamental principles of React and React
Hooks. We will start by learning what React and React Hooks are, and why we should use them.
Then, we will move on to learn about the inner workings of Hooks. Finally, you will be introduced
to the Hooks that are provided by React, and a couple of Hooks provided by the community – such
as data fetching and routing Hooks. By learning the fundamentals of React and React Hooks, we
will be better able to understand the concepts that will be introduced throughout this book.

In this chapter, we are going to cover the following main topics:

• Principles of React
• Motivation to use React Hooks
4 Introducing React and React Hooks

• Setting up the development environment


• Getting started with React Hooks

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com.

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter01.

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Principles of React
Before we start learning how to set up a React project, let’s revisit the three fundamental principles
of React. These principles allow us to easily write scalable web applications.
Chapter 1 5

React is based on three fundamental principles:

• Declarative: Instead of telling React how to do things, we tell it what we want it to do.
For example, if we change data, we don’t need to tell React which components need to
be re-rendered. That would be complex and error-prone. Instead, we just tell React that
data has changed and all components using this data will be efficiently updated and
re-rendered for us. React takes care of the details so that we can focus on the tasks at hand
to easily develop our web application.
• Component-based: React encapsulates components that manage their own state and
views and then allows us to compose them in order to create complex user interfaces.
• Learn once, write anywhere: React does not make assumptions about your technology
stack and tries to ensure that you can develop apps without rewriting existing code as
much as possible.

React’s three fundamental principles make it easy to write code, encapsulate components, and
share code across multiple platforms. Instead of reinventing the wheel, React always tries to
make use of existing JavaScript features as much as possible. As a result, we are going to learn
about software design patterns that will be applicable in many more cases than just designing
user interfaces.

We just learned that React is component-based. In React, there are two types of components:

• Function components: JavaScript functions that take the props as an argument, and
return the user interface (usually via JSX, which is an extension of the JavaScript syntax
that allows us to write HTML-like markup directly within JavaScript code)
• Class components: JavaScript classes that provide a render method, which returns the
user interface (usually via JSX)

While function components are easier to define and understand, in the past, class components
were needed to deal with state, contexts, and many more of React’s advanced features. However,
with React Hooks, we can use most of React’s advanced features without needing a class com-
ponent!

There are certain features of React that, at the time of writing, are not possible
with function components and Hooks yet. For example, defining error
boundaries still requires class components and the componentDidCatch and
getDerivedStateFromError lifecycle methods.
6 Introducing React and React Hooks

Motivation to use React Hooks


React always strives to make the developer experience as smooth as possible while ensuring
to keep it performant enough, without the developer having to worry too much about how to
optimize performance. However, throughout the years of using React, a couple of problems have
been identified.

The code snippets in the following subsections are just intended to give you an
understanding of why Hooks were needed, by giving examples of how developers
previously dealt with certain problems in React. If you are not familiar with those
old ways, do not worry, it’s not necessary to understand the old ways to follow along.
In the upcoming sections, we are going to learn how to deal with these problems in
a better way using React Hooks.

Let’s now take a look at these problems in the following subsections.

Confusing classes
In the past, we had to use class components with special functions called life cycle methods, such
as componentDidUpdate, and special state-handling methods such as this.setState, to deal with
state changes. React classes, and especially the this context, are hard to read and understand for
both developers and machines.

this is a special keyword in JavaScript that always refers to the object that it belongs to:

• In a method, this refers to the class object (instance of the class).


• In an event handler, this refers to the element that received the event.
• In a function or standing alone, this refers to the global object. For example, in a browser,
the global object is the Window object.
• In strict mode, this is undefined in a function.
• Additionally, methods such as call() and apply() can change the object that this refers
to, so it can refer to any object.

For developers, classes are hard, because this always refers to different things, so sometimes
(for example, in event handlers) we need to manually rebind it to the class object. For machines,
classes are hard, because they do not know which methods in a class will be called and how this
will be modified, making it hard to optimize performance and remove unused code.
Chapter 1 7

Additionally, classes sometimes require us to write code in multiple places at once. For example,
if we want to fetch data when the component renders or props of a component change, we need
to do this using two methods: once in componentDidMount, and once in componentDidUpdate.

To give an example, let’s define a class component that fetches data from an API:

1. First, we define a class component by extending the React.Component class:


class ExampleComponent extends React.Component {

2. Then, we define the componentDidMount life cycle method, where we pull data from an API:
componentDidMount() {
fetch(`http://my.api/${this.props.name}`)
.then(…)
}

3. However, we also need to define the componentDidUpdate life cycle method in case the
name prop changes. Additionally, we need to add a manual check here, to ensure that we
only re-fetch data if the name prop changed, and not when other props change:
componentDidUpdate(prevProps) {
if (this.props.name !== prevProps.name) {
fetch(`http://my.api/${this.props.name}`)
.then(...)
}
}
}

4. To make the code less repetitive, we could refactor our code by creating a separate method
called fetchData and fetch the data as follows:
fetchData() {
fetch(`http://my.api/${this.props.name}`)
.then(...)
}

5. Then, we could call the method in componentDidMount and componentDidUpdate:


componentDidMount() {
this.fetchData()
}
8 Introducing React and React Hooks

componentDidUpdate(prevProps) {
if (this.props.name !== prevProps.name) {
this.fetchData()
}
}

However, even then we still need to call the method in two places. Whenever we update arguments
that are passed to the method, we need to update them in two places, which makes this pattern
very prone to errors and future bugs.

Wrapper hell
Let’s assume we already implemented an authenticateUser higher-order component function
that adds authentication to one of our components, and a context called AuthenticationContext
to provide information about the authenticated user via render props. We would then use this
context as follows:

1. We start by importing the authenticateUser function to wrap our component with the
context, and the AuthenticationContext component to be able to access the context:
import authenticateUser, { AuthenticationContext } from './auth'

2. Then, we define an App component, where we make use of the AuthenticationContext.


Consumer component and the user render prop:

const App = () => (


<AuthenticationContext.Consumer>
{user =>

Render props are a way to pass props down to the children of a component. As we
can see here, the render prop allowed us to pass user down to the children of the
AuthenticationContext.Consumer component.

3. Now, we display different texts depending on whether the user is logged in or not:
user ? `${user} logged in` : 'not logged in'
}

Here, we used two JavaScript concepts:

• A ternary operator, which is an inline version of the if conditional. It looks as


follows: ifThisIsTrue ? returnThis : otherwiseReturnThis.
Chapter 1 9

• A template string, which can be used to insert variables into a string. It is defined
with backticks (`) instead of normal single quotes ('). Variables can be inserted via
the ${variableName} syntax. We can also use any JavaScript expressions within
the ${} brackets – for example, ${someValue + 1}.

4. Finally, we export the component after wrapping it with the authenticateUser context,
by making use of the higher-order component pattern:
</AuthenticationContext.Consumer>
)

export default authenticateUser(App)

Higher-order components are functions that wrap components and add functionality to
them. They were needed to encapsulate and reuse state management logic before Hooks.

In this example, we used the authenticateUser higher-order component function to add au-
thentication logic to our existing component. We then used AuthenticationContext.Consumer
to inject the user prop into our component through its render props.

As you can imagine, using many higher-order components will result in a large tree with many
sub-trees, which is an anti-pattern called wrapper hell. For example, when we want to use three
contexts, the wrapper hell looks as follows:
<AuthenticationContext.Consumer>
{user => (
<LanguageContext.Consumer>
{language => (
<StatusContext.Consumer>
{status => (
...
)}
</StatusContext.Consumer>
)}
</LanguageContext.Consumer>
)}
</AuthenticationContext.Consumer>
10 Introducing React and React Hooks

This is not very easy to read or write and is also prone to errors if we need to change something
later on. The wrapper hell makes debugging hard because we need to look at a large component
tree, with many components just acting as wrappers.

Now that we have learned about some common problems with React, let’s learn about the Hook
pattern to better deal with these problems!

Hooks to the rescue!


React Hooks are based on the same fundamental principles as React. They encapsulate state
management by using existing JavaScript features. As a result, we do not need to learn and un-
derstand many specialized React features anymore; we can simply tap into our existing JavaScript
knowledge to use Hooks.

Using Hooks, we can come up with better solutions to the previously mentioned problems. Hooks
are simply functions that can be called in function components. We also do not need to use render
props for contexts anymore, because we can simply use a Context Hook to get the data that we
need. Additionally, Hooks allow us to reuse stateful logic between components, without creating
higher-order components.

For example, the aforementioned problems with life cycle methods could be solved using an
Effect Hook, as follows:
function ExampleComponent({ name }) {
useEffect(() => {
fetch(`http://my.api/${name}`)
.then(...)
}, [name])
// ...
}

This Effect Hook will automatically trigger when the component mounts, and whenever the
name prop changes.
Chapter 1 11

The wrapper hell mentioned earlier could be solved using Context Hooks, as follows:
const user = useContext(AuthenticationContext)
const language = useContext(LanguageContext)
const status = useContext(StatusContext)

As we can see, by using Hooks, we can keep our code clean and concise, making sure that our code
stays easy to read and maintain. Writing custom Hooks also makes it easy to reuse application
logic in a project.

Now that we know which problems Hooks can solve, we can get started using them in practice.
First, however, we need to quickly set up our development environment.

Setting up the development environment


In this book, we are going to use VS Code as our code editor. Feel free to use whichever editor
you prefer, but keep in mind that the extensions used and settings configured may be slightly
different in the editor of your choice.

Let’s now install VS Code and some useful extensions, and then continue setting up all the tools
needed for our development environment.

Installing VS Code and extensions


Before we can get started developing and setting up the other tools, we need to set up our code
editor, by following these steps:

1. Download VS Code for your operating system from the official website (at the time of
writing, the URL is https://code.visualstudio.com/). We are going to use version
1.97.2 in this book.
12 Introducing React and React Hooks

2. After downloading and installing the application, open it, and you should see the fol-
lowing window:

Figure 1.1 – A fresh installation of Visual Studio Code (on macOS)

3. To make things easier later, we are going to install some extensions, so click on the Ex-
tensions icon, which is the fifth icon from the top left in the screenshot.

A sidebar should open, where you can see Search Extensions in Marketplace at the top.
Enter an extension name here and click on Install to install it. Let’s start by installing the
ESLint extension:
Chapter 1 13

Figure 1.2 – Installing the ESLint extension in Visual Studio Code

4. Make sure to install the following extensions:

• ESLint (by Microsoft)


• Prettier – Code formatter (by Prettier)

Support for JavaScript and Node.js already comes built-in with VS Code.

5. Create a folder for the projects made in this book (for example, you can call it Learn-
React-Hooks-Second-Edition). Inside this folder, create a new folder called Chapter01_1.
6. Open the empty Chapter01_1 folder in VS Code.
14 Introducing React and React Hooks

7. If you get a dialog asking Do you trust the authors of the files in this folder?, check Trust
the authors of all files in the parent folder ‘Learn-React-Hooks’ and then click on the
Yes, I trust the authors button.

Figure 1.3 – Allowing VS Code to execute files in our project folder

You can safely ignore this warning in your own projects, as you can be sure that those
do not contain malicious code. When opening folders from untrusted sources, you
can press No, I don’t trust the authors, and still browse the code. However, when
doing so, some features of VS Code will be disabled.

We have now successfully set up VS Code and are ready to start setting up our project! If you
cloned the folder from the GitHub code examples provided, a notification telling you that a Git
repository was found will also pop up. You can simply close this one, as we only want to open
the Chapter01_1 folder.

Now that VS Code is ready, let’s continue by setting up a new project with Vite.

Setting up a project with Vite


For this book, we are going to use Vite to set up our project, as it is the most popular and liked local
development server, according to The State of JS 2024 survey (https://2024.stateofjs.com/).
Chapter 1 15

Vite also makes it easy to set up a modern frontend project while still making it possible to extend
the configuration later if needed. Follow these steps to set up your project with Vite:

1. In the VS Code menu bar, go to Terminal | New Terminal to open a new terminal.
2. Inside the terminal, run the following command:
$ npm create [email protected] .

The $ symbol shows that this is a command that needs to be entered into a terminal. Enter
everything after the $ symbol into the terminal and confirm with Return/Enter to run the
command. Make sure there is a period at the end of the command to create the project in
the current folder instead of creating a new folder.

To keep the instructions in this book working even when new versions are
released, we pin all packages to a fixed version. Please follow the instructions
with the given versions. After finishing this book, when starting new projects
on your own, you should always try using the latest versions, but keep in
mind that changes might be needed to get them working. Consult the
documentation of the respective packages and follow the migration path
from the book version to the latest version.

3. When asked whether create-vite should be installed, simply type y and press the Return/
Enter key to proceed.
4. If you are asked about the current directory not being empty, select the option Remove
existing files and continue, then press Return/Enter to confirm it.
5. When asked for a package name, confirm the default suggestion by pressing Return/Enter.
6. When asked about the framework, use the arrow keys to select React and press Return/
Enter.
7. When asked about the variant, select JavaScript.

For simplicity and to cater to a wider audience, we are only using plain
JavaScript in this book. It should be noted that, nowadays, TypeScript
is widely used in many projects, so you may want to consider adopting
TypeScript in your own projects later. However, learning about TypeScript
is outside of the scope of this book.
16 Introducing React and React Hooks

8. Edit package.json and make sure that the versions of dependencies and devDependencies
are as follows:
"dependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"@eslint/js": "9.19.0",
"@types/react": "19.0.8",
"@types/react-dom": "19.0.3",
"@vitejs/plugin-react": "4.3.4",
"eslint": "9.19.0",
"eslint-plugin-react": "7.37.4",
"eslint-plugin-react-hooks": "5.0.0",
"eslint-plugin-react-refresh": "0.4.18",
"globals": "15.14.0",
"vite": "6.1.0"
}

9. Now our project is set up and we can run npm install in the terminal to install the de-
pendencies.
10. Afterward, run npm run dev to start the dev server, as shown in the following screenshot:

Figure 1.4 – The terminal after setting up a project with Vite and before starting the
dev server
Chapter 1 17

For simplicity of setup, we just used npm directly. If you prefer yarn or pnpm,
you can instead run yarn create vite or pnpm create vite respectively.

11. In the terminal, you will see a URL telling you where your app is running. You can either
hold Ctrl (Cmd on macOS) and click on the link to open it in your browser, or manually
enter the URL in a browser. Open the link in a browser now.
12. To test whether your app is interactive, click the button with the text count is 0, and it
should increase the count every time it is pressed.

Figure 1.5 – Our first React app running with Vite


18 Introducing React and React Hooks

Alternatives to Vite
Alternatives to Vite are bundlers, such as webpack, Rollup, and Parcel. These are highly configu-
rable but often do not offer a great experience for dev servers. They first must bundle all our code
together before serving it to the browser. Instead, Vite natively supports the ECMAScript Module
(ESM) standard. Furthermore, Vite requires very little configuration to get started. A downside
of Vite is that it can be hard to configure certain more complex scenarios with it. An upcoming
bundler that is promising is Rolldown (https://rolldown.rs); however, it is still very new at
the time of writing.

Now that our boilerplate project is up and running, let’s spend some time setting up tools that
will enforce best practices and a consistent code style.

Setting up ESLint and Prettier to enforce best practices and


code style
Now that our React app is set up, we are going to set up ESLint to enforce coding best practices
with JavaScript and React. We are also going to set up Prettier to enforce a code style and auto-
matically format our code.

Installing the necessary dependencies


First, we are going to install all the necessary dependencies.

1. In the terminal, click on the Split Terminal icon at the top right of the terminal pane to
create a new terminal pane (alternatively, right-click on the terminal pane and select Split
Terminal). This will keep our app running while we run other commands.
2. Click on this newly opened pane to focus it. Then, enter the following command to install
Prettier and the Prettier config for ESLint:
$ npm install --save-dev --save-exact [email protected] eslint-config-
[email protected]

The --save-dev flag in npm saves those dependencies as dev dependencies, which
means that they will only be installed for development. They will not be installed
and included in a deployed app. The --save-exact flag ensures that the versions
are pinned to the exact version provided by the book.

After the dependencies are installed, we need to configure Prettier and ESLint. We will start with
configuring Prettier.
Chapter 1 19

Configuring Prettier
Prettier will format the code for us and replace the default code formatter for JavaScript in VS
Code. It will allow us to spend more time writing code, automatically formatting it for us properly
when we save the file. Follow these steps to configure Prettier:

1. Right-click below the files list in the left sidebar of VS Code (if it is not opened, click the
Files icon) and click on New file... to create a new file. Call it .prettierrc.json (do not
forget the period at the beginning of the filename!).
2. The newly created file should open automatically; start writing the following configura-
tion into it. We first create a new object and set the trailingComma option to all to make
sure objects and arrays that span over multiple lines always have a comma at the end,
even for the last element. This reduces the number of touched lines when committing a
change via Git:
{
"trailingComma": "all",

3. Then, we set the tabWidth option to two spaces:


"tabWidth": 2,

4. Set printWidth to 80 characters per line to avoid long lines in our code:
"printWidth": 80,

5. Set the semi option to false to avoid semicolons where not necessary:
"semi": false,

6. Finally, we enforce the use of single quotes instead of double quotes:


"jsxSingleQuote": true,
"singleQuote": true
}

These settings for Prettier are just an example of a coding style convention. Of course,
you are free to adjust these to your own preferences. There are many more options,
all of which can be found in the Prettier docs (https://prettier.io/docs/en/
options.html).
20 Introducing React and React Hooks

Configuring the Prettier extension


Now that we have a configuration file for Prettier, we need to make sure the VS Code extension
is properly configured to format the code for us:

1. Open the VS Code settings by going to File | Preferences... | Settings on Windows/Linux,


or Code | Settings... | Settings on macOS.
2. In the newly opened settings editor, click on the Workspace tab. This ensures that we
save all our settings in a .vscode/settings.json file in our project folder. When other
developers open our project, they will automatically be using those settings as well.
3. Search for editor format on save and check the checkbox to enable formatting code
on save.
4. Search for editor default formatter and select Prettier - Code formatter from the list.
5. To verify that Prettier works, open the .prettierrc.json file, add some extra spaces to the
beginning of a line, and save the file. You should notice that Prettier reformatted the code
to adhere to the defined code style. It will reduce the number of spaces for indentation to 2.

Now that Prettier is set up properly, we do not need to worry about formatting our code manu-
ally anymore. Feel free to just type in code as you go and save the file to get it formatted for you!

Creating a Prettier ignore file


To improve performance and avoid running Prettier on files that should not be automatically for-
matted, we can ignore certain files and folders by creating a Prettier ignore file. Follow these steps:

1. Create a new file called .prettierignore in the root of our project, similar to how we
created the .prettierrc.json file.
2. Add the following contents to it, to ignore the transpiled source code:
dist/

The node_modules/ folder is automatically ignored by Prettier.

Now that we have successfully set up Prettier, we are going to configure ESLint to enforce coding
best practices.

Configuring ESLint
While Prettier focuses on the style and formatting of our code, ESLint focuses on the actual code,
avoiding common mistakes or unnecessary code. Let’s configure it now:
Chapter 1 21

1. Open the automatically created eslint.config.js file and add the following import to it:
import prettierConfig from 'eslint-config-prettier'

2. Scroll down to the bottom of the file, and add the Prettier config at the end of the array,
as follows:
'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
},
},
prettierConfig,
]

3. Additionally, disable the react/prop-types rule, as follows:


'react-refresh/only-export-components': [
'warn',
{ allowConstantExport: true },
],
'react/prop-types': 'off',
},
},
prettierConfig,
]

Since React 19, prop type checks are completely removed and will be silently
ignored. The only way to add type checks to props is to use a full type-checking
solution, such as TypeScript. Since we are focusing on learning plain React
with Hooks in this book, using TypeScript is out of scope. However, if you
haven’t yet, I strongly recommend learning TypeScript on your own after
finishing this book.

4. Save the files and run npx eslint src in the terminal to run the linter. You will see that
there is no output, which means that everything was linted successfully and there were
no errors!
22 Introducing React and React Hooks

The npx command allows us to execute commands provided by npm packages, in a


similar context as running them in package.json scripts would do. It can also run
remote packages without installing them permanently. In the case that the package
is not installed yet, it will ask you whether it should do this.

Adding a new script to run our linter


In the previous section, we have been calling the linter by running npx eslint src manually.
We are now going to add a lint script to our package.json:

1. In the Terminal, run the following command to define a lint script in our package.json file:
$ npm pkg set scripts.lint="eslint src"

2. Now, run npm run lint in the terminal. This should execute eslint src successfully,
just like npx eslint src did:

Figure 1.6 – The linter running successfully, with no errors

Now that we have successfully set up our development environment, let’s move on to learning
about using React class components versus React Hooks in practice!

Example code

The example code for this section can be found in the Chapter01/Chapter01_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Getting started with React Hooks


As we learned earlier in this chapter, React Hooks solve many problems, especially in larger web
applications. Hooks were added in React 16.8, and they allow us to use state, and various other
React features, without writing a class. In this section, we will start by defining a class compo-
nent, and then we will write the same component as a function component using Hooks. We will
then discuss the advantages of Hooks and how to migrate from classes to a Hook-based solution.
Chapter 1 23

Starting with a class component


Let’s start by creating a traditional React class component, which lets us enter a name; this is
then displayed in our app:

1. Copy the Chapter01_1 folder to a new Chapter01_2 folder, as follows:


$ cp -R Chapter01_1 Chapter01_2

On macOS, it is important to run the command with a capitalized -R flag, not


-r. The -r flag deals differently with symlinks and causes the node_modules/
folder to break. The -r flag only exists for historical reasons and should not
be used on macOS. Always prefer using the -R flag instead.

2. Open the new Chapter01_2 folder in VS Code.


3. Remove the src/assets/ folder and all its contents.
4. Remove the src/App.css and src/index.css files.
5. Open the src/main.jsx file and remove the following import:
import './index.css'

6. Additionally, adjust the import of the App component from a default import to a named
import, as follows:
import { App } from './App.jsx'

In most cases, it’s preferable to use named exports/imports over default


exports/imports. Using named exports/imports makes the code less
error-prone when refactoring. For example, let’s imagine you have a Login
component and you copy and paste it to a new Register component but
forget to rename the component to Register. With default imports, it will
still be possible to import it as Register, although the component is called
Login internally. However, when debugging in React dev tools or trying to
find the component in your project, you will see it named Login, which can
be confusing, especially in large-scale projects. When dealing with functions,
using named exports is even more useful because it allows you to move them
around in different files easily.
24 Introducing React and React Hooks

7. Open the src/App.jsx file and remove all existing code from it.
8. Next, we import React, as follows:
import React from 'react'

9. We then start defining a class component:


export class App extends React.Component {

10. Next, we have to define a constructor method, where we set the initial state object,
which will be an empty string. Here, we also need to make sure to call super(props), to
let the React.Component constructor know about the props object:
constructor(props) {
super(props)
this.state = { name: '' }
}

11. Now, we define a method to set the name variable, by using this.setState. As we will be
using the method to handle input from a text field, we need to use evt.target.value to
get the value from the input field:
handleChange(evt) {
this.setState({ name: evt.target.value })
}

12. Then, we define the render method, where we are going to display an input field and
the name:
render() {

13. To get the name variable from the this.state object, we are going to use destructuring:
const { name } = this.state

The previous statement is the equivalent of doing the following:


const name = this.state.name

14. Then, we display the currently entered name state variable:


return (
<div>
<h1>My name is: {name}</h1>
Chapter 1 25

15. We display an input field, passing the handler method to it:


<input type='text' value={name} onChange={this.handleChange}
/>
</div>
)
}
}

If we were to run this code now, we would get the following error when entering text,
because passing the handler method to onChange changes the this context:
Uncaught TypeError: Cannot read properties of undefined (reading
'setState')

Or, on some browsers, you may get the following error instead:
TypeError: undefined is not an object (evaluating 'this.setState')

16. So, now we need to adjust the constructor method and rebind the this context of our han-
dler method to the class. Edit src/App.jsx and add the following line in the constructor:
constructor(props) {
super(props)
this.state = { name: '' }
this.handleChange = this.handleChange.bind(this)
}

17. Run the dev server, by opening a terminal (Terminal | New Terminal menu option in VS
Code), and executing the following command:
$ npm run dev

18. Open the link to the dev server in your browser, and you should see the component being
rendered. Try entering some text now and it should work!

Alternatively, since ES6, it is possible to use arrow functions as class methods to


avoid having to rebind the this context.
26 Introducing React and React Hooks

Finally, our component works! As you can see, there is a lot of code required to get state handling
to work properly with class components. We also had to rebind the this context; otherwise, our
handler method would not work. This is not very intuitive, and is easy to miss while developing,
causing a frustrating developer experience.

Example code

The example code for this section can be found in the Chapter01/Chapter01_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Using Hooks instead


After using a traditional class component to create our app, we are going to write the same app
using Hooks. As before, our app is going to let us enter a name, which we then display in the app.

It is only possible to use Hooks in React function components. You cannot use Hooks
in a React class component.

Follow these steps to get started:

1. Copy the Chapter01_2 folder to a new Chapter01_3 folder, as follows:


$ cp -R Chapter01_2 Chapter01_3

2. Open the new Chapter01_3 folder in VS Code.


3. Open the src/App.jsx file and remove all existing code from it.
4. First, we import the useState Hook, as follows:
import { useState } from 'react'

5. We start with the function definition. In our case, we do not pass any arguments, because
our component does not have any props:
export function App() {

The next step would be to get the name variable from the component state. However, we
cannot use this.state in function components. We have already learned that Hooks are
just JavaScript functions, but what does that really mean? It means that we can simply
use Hooks from function components, just like any other JavaScript function!
Chapter 1 27

To use state via Hooks, we call useState(), with our initial state as the argument. This
function returns an array with two elements:

• The current state


• A setter function to set the state

6. We can use destructuring to store these two elements in separate variables, as follows:
const [name, setName] = useState('')

The previous code is equivalent to the following:


const nameHook = useState('')
const name = nameHook[0]
const setName = nameHook[1]

7. Now, we define the input handler function, where we make use of the setName setter
function:
function handleChange(evt) {
setName(evt.target.value)
}

As we are not dealing with classes now, there is no need to rebind this
anymore!

8. Finally, we render our user interface, by returning it from the function:


return (
<div>
<h1>My name is: {name}</h1>
<input type='text' value={name} onChange={handleChange} />
</div>
)
}

And that’s it – we have successfully used Hooks for the first time! As you can see, the useState
Hook is a simple replacement for this.state and this.setState.
28 Introducing React and React Hooks

Let’s run our app by executing npm run dev in the Terminal and opening the URL in a browser:

Figure 1.7 – Our first React app using Hooks!

After implementing the same app with a class component and a function component, let’s com-
pare the solutions.

Example code

The example code for this section can be found in the Chapter01/Chapter01_3
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Comparing the solutions


Let’s compare our two solutions to see the differences between class components and function
components using Hooks.

Class component
The class component makes use of the constructor method to define state and needs to rebind
this to be able to pass the handler method to the input field. The full class component code
looks as follows:
import React from 'react'

export class App extends React.Component {


Chapter 1 29

constructor(props) {
super(props)
this.state = { name: '' }
this.handleChange = this.handleChange.bind(this)
}

handleChange(evt) {
this.setState({ name: evt.target.value })
}

render() {
const { name } = this.state
return (
<div>
<h1>My name is: {name}</h1>
<input type='text' value={name} onChange={this.handleChange} />
</div>
)
}
}

As we can see, the class component needs a lot of boilerplate code to initialize the state object
and handler functions.

Now, let’s take a look at the function component.

Function component with a Hook


The function component makes use of the useState Hook instead, so that we do not need to deal
with this or a constructor method. The full function component code looks as follows:
import { useState } from 'react'

export function App() {


const [name, setName] = useState('')

function handleChange(evt) {
setName(evt.target.value)
}
30 Introducing React and React Hooks

return (
<div>
<h1>My name is: {name}</h1>
<input type='text' value={name} onChange={handleChange} />
</div>
)
}

As we can see, Hooks make our code much more concise and easier to reason about. We do not
need to worry about how things work internally anymore; we can simply use state, by accessing
the useState function!

Advantages of Hooks
Let’s remind ourselves about the first principle of React:

Declarative: Instead of telling React how to do things, we tell it what we want it


to do. As a result, we can easily design our applications, and React will efficiently
update and render just the right components when the data changes.

As we have learned in this chapter, Hooks allow us to write code that tells React what we want.
With class components, however, we need to tell React how to do things. As a result, Hooks are
much more declarative than class components, making them a better fit in the React ecosystem.

Hooks being declarative also means that React can do various optimizations on our code, since
it is easier to analyze functions and function calls rather than classes and their complex this
behavior. Additionally, Hooks make it easier to abstract and share common stateful logic between
components. By using Hooks, we can avoid render props and higher-order components.

We can see that Hooks not only make our code more concise and are easier to reason about for
developers but they also make the code easier to optimize for React.

Migrating to Hooks
Now, you might be wondering whether that means class components are deprecated, and we
need to migrate everything to Hooks now. Of course not – Hooks are completely opt-in. You can
try Hooks in some of your components without rewriting any of your other code. The React team
also does not plan on removing classes at the moment.
Chapter 1 31

There is no rush to migrate everything to Hooks right now. It is recommended that you gradually
adopt Hooks in certain components where they will be most useful. For example, if you have
many components that deal with similar logic, you can extract the logic to a Hook. You can also
use function components with Hooks side by side with class components.

Hooks are backward-compatible and provide a direct API to various React concepts that you
already know about: props, state, context, refs, and the life cycle. Additionally, Hooks offer new
ways to combine these concepts and encapsulate their logic in a much better way that does not
lead to wrapper hell or similar problems.

The Hooks mindset


The main goal of Hooks is to decouple stateful logic from rendering logic. Hooks allow us to
define logic in separate functions and reuse them across multiple components. With Hooks, we
do not need to change our component hierarchy to implement stateful logic. There is no need to
define a separate component that provides the state logic to multiple components anymore, we
can simply use a Hook instead!

However, Hooks require a completely different mindset from traditional React development. We
should not think about the life cycle of components anymore. Instead, we should think about data
flow. For example, we can tell Hooks to trigger when certain props or values from other Hooks
change. We are going to learn more about this concept in Chapter 4, Using the Reducer and Effect
Hooks. We should also not split components based on life cycle methods anymore. Instead, we
can use Hooks to deal with common functionalities, such as fetching data or setting up a data
subscription.

Rules of Hooks
Hooks are very flexible. However, there are certain limitations to using Hooks, which we should
always keep in mind:

• Hooks can only be used in function components and inside other Hooks, not in class
components or arbitrary functions
• The order of Hook definitions matters and needs to stay the same; thus, we cannot put
Hooks in if conditionals, loops, or nested functions

Thankfully, Vite already configured an ESLint plugin for us that ensures that the rules of Hooks
are not violated.
32 Introducing React and React Hooks

We are going to discuss these limitations in more detail, as well as how to work around them,
throughout this book.

Summary
In this first chapter of the book, we started by learning about the fundamental principles of React,
and which types of components it provides. We then moved on to learn about common problems
with class components, using existing features of React, and how they break the fundamental
principles. Next, we implemented a simple application using class components and function
components with Hooks, in order to be able to compare the differences between the two solutions.
As we found out, function components with Hooks are a much better fit for React’s fundamental
principles; they do not suffer from the same problems as class components, and they make our
code much more concise and easy to understand! The React team now even recommends using
function components instead of class components, making function components the state-of-
the-art way to write React code. After reading this chapter, the basics of React and React Hooks
are clear. We can now move on to learn about Hooks in detail.

In the next chapter, we are going to gain an in-depth knowledge of how the State Hook works,
by reimplementing it from scratch. By doing so, we are going to get a grasp on how Hooks work
internally, and what their limitations are. Afterward, we are going to create a small blog appli-
cation using the State Hook!

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. What are React’s three fundamental principles?


2. What are the two types of components in React?
3. What are the problems with class components in React?
4. What is the problem when using higher-order components in React?
5. Which tool can we use to set up a React project, and what is the command that we need
to run to use it?
6. What do we need to do if we get the following error with class components: Uncaught
TypeError: Cannot read properties of undefined (reading 'setState')?
7. How do we use React state with Hooks?
Chapter 1 33

8. What are the advantages of using function components with Hooks, in comparison to
class components?
9. Do we need to replace all class components with function components using Hooks when
updating React?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• Information about function components: https://react.dev/reference/react/


Component
• RFC for React Hooks: https://github.com/reactjs/rfcs/blob/main/text/0068-react-
hooks.md
• Destructuring: https://exploringjs.com/es6/ch_destructuring.html
• Template strings: https://developer.mozilla.org/en-US/docs/Web/JavaScript/
Reference/Template_literals
• Ternary operator: https://developer.mozilla.org/en-US/docs/Web/JavaScript/
Reference/Operators/Conditional_operator

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
2
Using the State Hook
After learning about the principles of React and giving an introduction to Hooks, we are now go-
ing to learn about the State Hook in depth. We will start by learning how the State Hook works
internally by reimplementing it ourselves. Doing so will teach us about the limitations of Hooks
and why they exist. Then, we will learn about possible alternative Hook APIs and their associated
problems. Finally, we will learn how to solve the common problems that result from the limita-
tions of Hooks. By the end of this chapter, you will know how to use the State Hook to implement
stateful function components in React.

In this chapter, we are going to cover the following main topics:

• Reimplementing the State Hook


• Potential alternative Hook APIs
• Solving common problems with Hooks

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out the official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to the official website: https://code.visualstudio.com.
36 Using the State Hook

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• VS Code v1.97.2

While installing a newer version should not be an issue, please note that certain steps might work
differently on a newer version. If you are having an issue with the code and steps provided in this
book, please try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter02.

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Reimplementing the State Hook


In order to get a better understanding of how Hooks work internally in React, we are going to
reimplement the useState function from scratch. However, we are not going to implement it as
an actual React Hook but as a simple JavaScript function—just to get an idea of what Hooks are
actually doing.

This reimplementation is not exactly how React Hooks work internally. The actual
implementation is similar, and thus, has similar constraints. However, the real
implementation is more extensive than what we will be implementing here.

We are now going to start reimplementing the State Hook:

1. Copy the Chapter01_3 folder to a new Chapter02_1 folder by executing the following
command:
$ cp -R Chapter01_3 Chapter02_1

2. Open the new Chapter02_1 folder in VS Code.

First, we need to define a function to (re)render the app, which we can use to simulate
React rerendering when the Hook state changes. If we used actual React Hooks, this would
be dealt with internally.
Chapter 2 37

3. Open src/main.jsx and remove the following code:


createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)

Replace it with the following:


const root = createRoot(document.getElementById('root'))

export function renderApp() {


root.render(
<StrictMode>
<App />
</StrictMode>,
)
}

renderApp()

In this code snippet, we first create a root for our React application to be rendered in. Then,
we define a function to render the app into the root. Finally, we call the renderApp()
function to initially render the app.

4. Now, open the src/App.jsx file and remove the following line:
import { useState } from 'react'

Replace it with the following line:


import { renderApp } from './main.jsx'

5. Now, we define our own useState function. As we already know, the useState function
takes initialState as an argument:
function useState(initialState) {

6. Then, we define a value, where we will store our state. At first, this value will be set to
initialState:

let value = initialState


38 Using the State Hook

7. Next, we define the setState function, where we will set the new value, and force the
rerendering of our app:
function setState(nextValue) {
value = nextValue
renderApp()
}

8. Finally, we return the value and the setState function as an array:


return [value, setState]
}

9. Start the dev server (keep it running) and then open the link in your browser:
$ npm run dev

If you try to enter text into the input field now, you will notice that when the component
rerenders, the state gets reset, so it is not possible to enter any text in the field. We are
going to solve this problem in the next section.

The reason that we use an array and not an object is that we usually want to rename the value and
setState variables. Using an array makes it easy to rename the variables through destructuring.
For example, if we want to have state for username, we could do the following:
const [username, setUsername] = useState('')

While renaming in destructuring is possible with objects too, it would be more verbose:
const { state: username, setState: setUsername } = useState('')

As we can see, Hooks are simple JavaScript functions that deal with side effects, such as setting
a stateful value.

Our Hook function uses a closure to store the current value. The closure is an environment where
variables exist and are stored. In our case, the function provides the closure, and the value variable
is stored within that closure. The setState function is also defined within the same closure, which
is why we can access the value variable within that function. Outside of the useState function,
we cannot directly access the value variable unless we return it from the function.
Chapter 2 39

Resolving issues with our simple Hook implementation


The issue of not being able to enter any text into the input field is due to the reinitialization of
the value variable every time the component gets rendered because we call useState each time
we render the component.

In the following section, we are going to solve this problem by using a global variable and then
turning the simple value into an array, allowing us to define multiple Hooks.

Using a global variable


As we have learned, the value is stored within the closure that is defined by the useState function.
Every time the component rerenders, the closure gets reinitialized, which means that the value
variable will be set to the initialState again. To solve this, we need to store the value in a global
variable, outside of the function. That way, the value variable will be in the closure outside of the
function, which means that when the function gets called again, the value will not be reinitialized.

We can define the global variable as follows:

1. First, edit src/App.jsx and add the following line above the useState function definition:
let value
function useState(initialState) {

2. Then, remove the following first line in the function definition:


let value = initialState

Replace it with the following code snippet:


if (value === undefined) {
value = initialState
}

3. Try entering some text into the input field again; you will see that our Hook function
works now!

Now, our useState function uses the global value variable instead of defining the value variable
within its closure, so it will not get reinitialized when the function gets called again. While our
Hook function currently works fine, if we wanted to add another Hook, we would run into an-
other problem: all Hooks write to the same global value variable! Let’s take a closer look at this
problem by adding a second Hook to our component.
40 Using the State Hook

Defining multiple Hooks


Let’s say we want to create a second field for the last name of the user. We can achieve it by fol-
lowing these steps:

1. Edit src/App.jsx and start by defining a new Hook at the beginning of the App component,
after the current Hook:
export function App() {
const [name, setName] = useState('')
const [lastName, setLastName] = useState('')

2. Then, define a function to handle changing the last name:


function handleLastNameChange(evt) {
setLastName(evt.target.value)
}

3. Next, show the lastName value after the first name:


return (
<div>
<h1>My name is: {name} {lastName}</h1>

4. Finally, add another input field for the last name:


<input type='text' value={name} onChange={handleChange} />
<input type='text' value={lastName}
onChange={handleLastNameChange} />

5. Try entering the first name and last name now.

You will notice that our reimplemented Hook function uses the same value for both states, so we
are always changing both fields at once. Let’s attempt to fix that now.

Adding support for multiple Hooks


To add support for multiple Hooks, we need to store an array of Hook values instead of a single
global variable. We are now going to refactor the value variable to a values array with the fol-
lowing steps:

1. Edit src/App.jsx and remove the following line of code:


let value
Chapter 2 41

Replace it with the following code snippet:


let values = []
let currentHook = 0

2. Then, edit the first line of the useState function, where we now initialize the value at the
currentHook index of the values array:

function useState(initialState) {
if (values[currentHook] === undefined) {
values[currentHook] = initialState
}

3. We also need to update the setter function so that only the corresponding state value
is updated. Here, we need to first store the currentHook value in a separate hookIndex
variable, because the currentHook value will change later. This ensures that a copy of the
currentHook variable is created within the closure of the useState function. Otherwise,
the useState function would access the currentHook variable from the outer closure,
which gets modified with each call to useState:
let hookIndex = currentHook
function setState(nextValue) {
values[hookIndex] = nextValue
renderApp()
}

4. Edit the return statement of the useState function as follows:


const value = values[currentHook++]
return [value, setState]
}

Using values[currentHook++], we pass the current value of currentHook as an index


to the values array and then increase the currentHook value by one. This means that
currentHook will be increased after returning from the function.

If we wanted to first increment a value and then use it, we could use the
arr[++indexToBeIncremented] syntax, which first increments and then
passes the result to the array.
42 Using the State Hook

5. We still need to reset the currentHook counter when we start rendering our component.
Add the following line right after the component definition:
export function App() {
currentHook = 0

6. Try entering the first name and last name again.

Finally, our simple reimplementation of the useState Hook works! The following screenshot
highlights this:

Figure 2.1 – Our custom Hook reimplementation works!

As we can see, using a global array to store our Hook values solved the problems that we had
when defining multiple Hooks.

Example code

The example code for this section can be found in the Chapter02/Chapter02_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

After resolving the problems we had with our custom Hook implementation, let’s find out more
about the limitations of Hooks in general.
Chapter 2 43

Can we define conditional Hooks?


What if we wanted to add a checkbox that toggles the use of the first name field? Let’s find out
by implementing such a checkbox:

1. Copy the Chapter02_1 folder to a new Chapter02_2 folder, as follows:


$ cp -R Chapter02_1 Chapter02_2

2. Open the new Chapter02_2 folder in VS Code.


3. Edit src/App.jsx and add a new Hook to the App component, which will store the state
of the checkbox:
export function App() {
currentHook = 0
const [enableFirstName, setEnableFirstName] = useState(false)

4. Then, adjust the Hook for the name state to only be used when the first name is enabled:
// eslint-disable-next-line react-hooks/rules-of-hooks
const [name, setName] = enableFirstName ? useState('') : ['', ()
=> {}]
const [lastName, setLastName] = useState('')

We need to disable ESLint for this line; otherwise, it will yell at us, telling us that Hooks
cannot be used conditionally. For the purposes of this demonstration, I want to show
what happens when you ignore this warning, though.

We also define a fallback to an empty string ('') and a function that does nothing (() =>
{}) when the Hook is not defined.

5. Next, define a handler function for changing the checkbox state:


function handleEnableChange(evt) {
setEnableFirstName(evt.target.checked)
}

6. Finally, render the checkbox:


return (
<div>
<h1>
My name is: {name} {lastName}
44 Using the State Hook

</h1>
<input
type='checkbox'
value={enableFirstName}
onChange={handleEnableChange}
/>

7. Start the dev server and then open the link in your browser:
$ npm run dev

Here, we either use the Hook or, if the first name is disabled, we return the initial state and an
empty setter function, so that editing the input field will not work.

If we now try out this code, we are going to notice that editing the last name still works, but
editing the first name does not work, which is what we wanted. As we can see in the following
screenshot, only editing the last name works now:

Figure 2.2 – State of the app before checking the checkbox

When we click the checkbox, something strange happens:

• The checkbox gets checked


• The first name input field gets enabled
• The value of the last name field is now the value of the first name field
Chapter 2 45

We can see the result of clicking the checkbox in the following screenshot:

Figure 2.3 – State of the app after clicking the checkbox

We can see that the last name state is now in the first name field. The value gets swapped because
the order of Hooks matters. As we know from our implementation, we use the currentHook index
to find out where the state of each Hook is stored. However, when we insert an additional Hook
in between two existing Hooks, the order gets messed up.

Before checking the checkbox, the values array was as follows:


• [false, '']
• Hook order: enableFirstName, lastName

Then, we entered some text in the lastName field:


• [false, 'Hook']
• Hook order: enableFirstName, lastName

Next, we clicked the checkbox, which activated another Hook:


• [true, 'Hook', '']
• Hook order: enableFirstName, name, lastName

As we can see, inserting a new Hook in between two existing Hooks makes the name Hook steal
the state from the next Hook (lastName) because it now has the same index that the lastName
Hook previously had. Now, the lastName Hook does not have a value, which causes it to set the
initial value (an empty string).
46 Using the State Hook

As a result, toggling the checkbox puts the value of the lastName field into the name field and
leaves the lastName field empty.

Example code

The example code for this section can be found in the Chapter02/Chapter02_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

After learning that Hooks always need to be called in the same order, let’s compare our custom
Hook implementation to real React Hooks.

Comparing our reimplementation to real Hooks


Our simple Hook implementation already gives us an idea of how Hooks work internally. However,
in reality, Hooks do not use global variables. Instead, they store state within the React component.
They also deal with the Hook counter internally, so we do not need to manually reset the count
in function components. Additionally, real Hooks automatically trigger rerenders of components
when the state changes. To be able to do this, however, Hooks need to be called from a React func-
tion component. React Hooks cannot be called outside of React or inside React class components.

By reimplementing the useState Hook, we have learned the following:

• Hooks are functions that access React features


• Hooks deal with side effects that persist across rerenders
• The order of Hook definitions matters

The final point is especially important because it means that we cannot conditionally define
Hooks. We should always have all Hook definitions at the beginning of a function component
and never nest them within if statements, ternaries, or similar constructs.

So, we have also learned the following:

• React Hooks need to be called inside React function components or other Hooks
• React Hooks cannot be defined conditionally or in loops

There are some additional limitations of React Hooks, resulting from the limitations we have
learned about:

• React Hooks cannot be defined after a conditional return statement


Chapter 2 47

• React Hooks cannot be defined in event handlers


• React Hooks cannot be defined inside try/catch/finally blocks
• React Hooks cannot be defined in the functions passed to useMemo, useReducer, and
useEffect (we will learn more about these three Hooks throughout the book, but just
keep this limitation in mind for now)

We are now going to look at alternative Hook APIs that would allow conditional Hooks, but they
come with their own downsides.

Potential alternative Hook APIs


Sometimes, it would be nice to define Hooks conditionally or in loops, but why did the React team
decide to implement Hooks like this? What are the alternatives? Let’s go through some of them
to get a feeling for the trade-offs involved in making this decision.

Named Hooks
We could give each Hook a name, and then store the Hooks in an object instead of an array. How-
ever, this would not make such a nice API, and we would also always have to think of unique
names for Hooks:
// NOTE: Not the actual React Hook API
const [name, setName] = useState('nameHook', '')

Additionally, there are unresolved questions: What would happen when a conditional is set to
false or an item is removed from a loop? Would we clear the Hook state? If we do not clear the
Hook state, we might be causing memory leaks. If we do clear it, we might be unintentionally
discarding user input.

Even if we solved these problems, there would still be the problem of name collisions. If we, for
example, create a Hook and call it nameHook, then we cannot call any other Hook nameHook in our
component anymore, or we will cause a name collision. This is even the case for Hook names from
libraries, so we need to make sure to avoid name collisions with Hooks defined by libraries as well!

Hook factories
Alternatively, we could create a Hook factory function, which uses Symbol internally to give each
Hook a unique key name:
function createUseState() {
const keyName = Symbol()
48 Using the State Hook

return function useState() {


// …use unique key name to handle hook state…
}
}

Then, we could use the factory function as follows:


// NOTE: Not the actual React Hook API
const useNameState = createUseState()
export function App () {
const [name, setName] = useNameState('')
// …
}

However, this means that we will need to instantiate each Hook twice: once outside of the com-
ponent, and once inside the function component. This creates more room for error. For example,
if we create two Hooks and copy and paste the boilerplate code, then we might make a mistake
in the name of our Hook resulting from the factory function, or we might make a mistake when
using the Hook inside the component.

This approach also makes it much harder to create custom Hooks, forcing us to write wrapper
functions. Additionally, it is harder to debug these wrapped functions than it is to debug a simple
function.

Other alternatives
There were many proposed alternative APIs for React Hooks, but each of them suffered from sim-
ilar problems: either making the API harder to use, less flexible, harder to debug, or introducing
the possibility of name collisions.

In the end, the React team decided that the simplest API was to keep track of Hooks by counting
the order in which they were called. This approach comes with its own downsides, such as not
being able to call Hooks conditionally or in loops. However, this approach makes it very easy
to create custom Hooks, and it is simple to use and debug. We also do not need to worry about
naming Hooks, name collisions, or writing wrapper functions. The final approach for Hooks lets
us use Hooks just like any other function!

Now that we have learned about the various proposals and the final Hook implementation, let’s
learn how to solve common problems resulting from the limitations of the chosen official API.
Chapter 2 49

Solving common problems with Hooks


As we found out, implementing Hooks with the official API also has its own trade-offs and lim-
itations. We are now going to learn how to overcome these common problems, which stem from
the limitations of React Hooks.

We will take a look at solutions that can be used to overcome these two problems:

• Solving conditional Hooks


• Solving Hooks in loops

Solving conditional Hooks


So, how do we implement conditional Hooks? Instead of making the Hook conditional, we can
just always define the Hook and use it whenever we need it. If this is not an option, we need to
split up our components, which is usually better anyway!

Always defining the Hook


For simple cases, such as the first and last name example that we had previously, we can just
always keep the Hook defined, as follows:

1. Copy the Chapter02_2 folder to a new Chapter02_3 folder, as follows:


$ cp -R Chapter02_2 Chapter02_3

2. Open the new Chapter02_3 folder in VS Code.


3. Edit src/App.jsx and remove the following two lines:
// eslint-disable-next-line react-hooks/rules-of-hooks
const [name, setName] = enableFirstName ? useState('') : ['', ()
=> {}]

Replace them with the following:


const [name, setName] = useState('')

4. Now, we need to move the conditional down to where the first name gets rendered instead:
return (
<div>
<h1>
My name is: {enableFirstName ? name : ''} {lastName}
</h1>
50 Using the State Hook

If you want to re-add the feature that the first name field cannot even be
edited when it is not enabled, just add the following attribute to the <input>
field: disabled={!enableFirstName}.

5. Run the dev server and then open the link in your browser:
$ npm run dev

Now, our example works fine! Always defining the Hook is usually a good solution for simple
cases. In more complex cases, it might not be feasible to always define the Hook. In that case, we
would need to create a new component, define the Hook there, and then conditionally render
the component.

Example code

The example code for this section can be found in the Chapter02/Chapter02_3
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Splitting up components
Another way to solve conditional Hooks is to split up one component into multiple components
and then conditionally render the components. For example, let’s say we want to fetch user in-
formation from a database after the user logs in.

We cannot do the following, as using an if conditional could change the order of the Hooks:
function UserInfo({ username }) {
// NOTE: Do NOT do this
if (username) {
const info = useFetchUserInfo(username)
return <div>{info}</div>
}
return <div>Not logged in</div>
}

Instead, we have to create a separate component for when the user is logged in, as follows:
// NOTE: Do this instead
function LoggedInUserInfo({ username }) {
const info = useFetchUserInfo(username)
Chapter 2 51

return <div>{info}</div>
}

function UserInfo({ username }) {


if (username) {
return <LoggedInUserInfo username={username} />
}
return <div>Not logged in</div>
}

Using two separate components for the non-logged-in and logged-in state makes sense anyway
because we want to stick to the principle of having one functionality per component. Usually,
not being able to have conditional Hooks is not much of a limitation if we stick to best practices.

Solving Hooks in loops


Sometimes, you may have a case where you would want to define Hooks in loops – for example, if
you have a way to dynamically add new input fields and you need a State Hook for each of them.

To solve problems where we would want to have Hooks in loops, we can either use a single State
Hook containing an array or we can again split up our components. For example, let’s say we
want to display all the users that are online.

Using an array
We could simply use an array that contains all users, as follows:
function OnlineUsers({ users }) {
const [userInfos, setUserInfos] = useState([])
// ... fetch & keep userInfos up to date ...
return (
<div>
{users.map((username) => {
const user = userInfos.find((u) => u.username === username)
return <UserInfo key={username} {...user} />
})}
</div>
)
}
52 Using the State Hook

However, this might not always make sense. For example, we might not want to update the user
state through the OnlineUsers component because we would have to select the correct user state
from the array and then modify the array. This might work but it is quite tedious.

Splitting up components
A better solution would be to use the Hook in the UserInfo component instead. That way, we can
keep the state for each user up to date, without having to deal with array logic:
function OnlineUsers({ users }) {
return (
<div>
{users.map((username) => (
<UserInfo key={username} username={username} />
))}
</div>
)
}

function UserInfo({ username }) {


const info = useFetchUserInfo(username)
// ... keep user info up to date ...
return <div>{info}</div>
}

As we can see, using one component for each functionality keeps our code simple and concise,
and also avoids the limitations of React Hooks.

Summary
In this chapter, we started out by reimplementing the useState function, making use of global
state and closures. We then learned that in order to support multiple Hooks, we need to keep
track of them using an array. By using a state array, however, we were forced to keep the order
of Hooks consistent across function calls. This limitation made conditional Hooks and Hooks in
loops impossible. We then learned about potential alternatives to the Hook API, their trade-offs,
and why the final API was chosen. Finally, we learned how to solve the common problems that
stem from the limitations of Hooks. We now have a solid understanding of the inner workings
and limitations of Hooks. Along the way, we also learned about the State Hook in depth.
Chapter 2 53

In the next chapter, we are going to create a blog application using the State Hook and learn how
to combine multiple Hooks.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. Which problems did we run into while developing our own reimplementation of the
useState Hook? How did we solve these problems?
2. Why are conditional Hooks not possible in the React implementation of Hooks?
3. What do we need to watch out for when using Hooks?
4. What are the common problems of alternative API ideas for Hooks?
5. How do we implement conditional Hooks?
6. How do we implement Hooks in loops?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• More information on the flaws of alternative Hook APIs: https://overreacted.io/why-


do-hooks-rely-on-call-order/
• Official comment on alternative Hook APIs: https://github.com/reactjs/rfcs/
pull/68#issuecomment-439314884
• Official documentation on the limitations and rules of Hooks: https://react.dev/
reference/rules/rules-of-hooks
• More information on how Symbol works: https://developer.mozilla.org/en-US/docs/
Web/JavaScript/Reference/Global_Objects/Symbol
54 Using the State Hook

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
3
Writing Your First Application
with React Hooks
After learning about the State Hook in-depth, we are now going to make use of it by creating a
blog application from scratch. In this chapter, we are first going to learn how to structure React
apps in a way that scales well. Then, we are going to define the components that we are going
to need to cover the basic features of a blog application. Finally, we are going to use Hooks to
introduce state to our application! Throughout this chapter, we are also going to learn about
JSX and various JavaScript features. At the end of this chapter, we are going to have a basic blog
application, where we can log in, register, and create posts.

The following topics will be covered in this chapter:

• Structuring React projects


• Implementing static React components
• Implementing stateful components with Hooks

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out the official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to the official website: https://code.visualstudio.com.
56 Writing Your First Application with React Hooks

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter03.

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Structuring React projects


After learning about the principles of React, how to use the State Hook, and how Hooks work in-
ternally, we are now going to make use of the real State Hook to develop a blog application. In this
section, we are going to structure the folders in a way that will allow us to scale the project later.

Folder structure
There are many ways that projects can be structured, and different structures can do well for dif-
ferent projects. Usually, it’s a good idea to create a src/ folder for all source code, to distinguish it
from assets and configuration files. Inside this folder, one possible structure is to group the files
by features. Another popular way to structure projects is to group the files by routes. For some
projects, it might make sense to additionally separate by file type, such as src/api/ and src/
components/. However, for our project, we are mainly going to focus on the user interface (UI).
So, we are going to group our files by features in the src/ folder.
Chapter 3 57

It is a good idea to start with a simple structure at first, and only nest more deeply
when you actually need it. Do not spend too much time thinking about the file
structure when starting a project because, usually, you do not know upfront how
files should be grouped, and it may change later anyway. However, do try to avoid
generic names for folders and files, such as utils, common, or shared. Use a term
as specific as possible and broaden it when the structure evolves.

Defining the features


We first have to think about which features we are going to implement in our blog application.
At the bare minimum, we want to implement the following features:

• Registering users
• Logging in/out
• Viewing a single post
• Creating a new post
• Listing posts

Coming up with an initial structure


From our defined features, we can abstract a couple of feature groups:

• User (registering, logging in/logging out)


• Post (creating, viewing, listing)

We could now just keep it very simple and create all the components in the src/ folder, without
any nesting. However, since we already have a quite clear picture of the features that a blog ap-
plication is going to need, we can already come up with a folder structure:
• src/
• src/user/
• src/post/

Let’s set up the initial folder structure now:

1. Copy the Chapter01_3 folder to a new Chapter03_1 folder by executing the following
command:
$ cp -R Chapter01_3 Chapter03_1
58 Writing Your First Application with React Hooks

2. Open the new Chapter03_1 folder in VS Code.


3. Inside the Chapter03_1 folder, create new src/user/ and src/post/ folders.

Component structure
The idea of components in React is to have each component deal with a single task or UI element.
We should try to make components as fine-grained as possible, in order to be able to reuse code.
If we find ourselves copying and pasting code from one component to another, it might be a good
idea to break out this common code into a separate component that we can reuse.

Usually, when developing software, we start with a UI mock-up. For our blog, this would look
as follows:

Figure 3.1 – An initial mock-up of our blog application


Chapter 3 59

When splitting components, we use the single responsibility principle, which states that every
module should have responsibility over a single encapsulated part of the functionality.

In the mock-up, we can draw boxes around each component and subcomponent, and give them
names. Keep in mind that each component should have exactly one responsibility. We start with
the fundamental components that make up this app:

Figure 3.2 – Mapping out the fundamental components in our mock-up

We mapped out a Logout component for the logout (which will be replaced with Login/Register
components in the logged-out state), a CreatePost component to render a form to create new
posts, and a Post component for the actual posts.
60 Writing Your First Application with React Hooks

Now that we mapped out the fundamental components, we are going to look at which components
logically belong together, therefore, forming a group. To do so, we now map out the container
components, which we need in order to group the components together:

Figure 3.3 – Mapping out the container components in our mock-up

We mapped out a PostList component, which is used to group posts together, and then a UserBar
component to deal with login/logout and registration. Finally, we mapped out an App component
to group everything else together and define the structure of our app.

Now that we are done with structuring our React project, we can move on to implementing the
static components.

Implementing static components


Before we start adding state to our blog application via Hooks, we are going to model the basic
features of our application as static React components. Doing this means that we must deal with
the static view structure of our application.
Chapter 3 61

It makes sense to deal with the static structure first, as it will avoid having to move dynamic code
to different components later. Furthermore, it is easier to deal only with HTML (and CSS) first—
helping us get started with projects quickly. Then, we can move on to implementing dynamic
code and handling state.

Doing this step by step, instead of implementing everything at once, helps us to quickly get started
with new projects without having to think about too much at once, and reduces the amount of
restructuring we will have to do later!

Implementing the user-related static components


We are going to start with the simplest feature in terms of static components, that is, imple-
menting user-related functionality. As we have seen from our mock-up, we are going to need
four components here:

• A Login component, which we are going to show when the user is not logged in yet
• A Register component, which we are also going to show when the user is not logged in yet
• A Logout component, which is going to be shown after the user is logged in
• A UserBar component, which will display the other components conditionally depending
on the user’s login state

We are going to start by defining the first three components, which are all standalone compo-
nents. Finally, we will define the UserBar component, which depends on the other components
being defined.

The Login component


First, we will define the Login component, where we will show two fields: a Username field and
a Password field. Additionally, we will show a Login button. Let’s get started:

1. Inside the previously set up Chapter03_1 folder, create a new file for our component:
src/user/Login.jsx.
2. In the newly created src/user/Login.jsx file, define a component, which does not accept
any props for now:
export function Login() {

3. Render a <form> that prevents the default action of submitting the form and refreshing
the page:
return (
<form onSubmit={(e) => e.preventDefault()}>
62 Writing Your First Application with React Hooks

Here, we are using an anonymous function (also called arrow function) to define the
onSubmit handler. Anonymous functions are defined as follows:

• If they do not have any arguments, we could write () => { ... }, instead of
function () { ... }
• With arguments, we could write (arg1, arg2) => { ... }, instead of function
(arg1, arg2) { ... }

If we do not use brackets, { }, the result from the statement in the function body will also
automatically be returned from the function, which is usually not an issue with event
handlers, though.

4. Then, render two fields to input the username and password, and a button to submit the
login form:
<label htmlFor='login-username'>Username: </label>
<input type='text' name='username' id='login-username' />
<br />
<label htmlFor='login-password'>Password: </label>
<input type='password' name='password' id='login-password' />
<br />
<input type='submit' value='Login' />
</form>
)
}

Using semantic HTML such as <form> and <label> makes your app easier to
navigate for people using accessibility assistance software, such as screen readers.
Additionally, when using semantic HTML, keyboard shortcuts, such as submitting
forms by pressing the Enter/Return key, automatically work. We used the htmlFor
and id attributes to make sure screen readers know which input field the label
belongs to. The id prop needs to be unique across the whole page, but for the name
prop, it is enough to be unique within the form.

The static Login component is now implemented, so let’s render it to see what it looks like.

Rendering the Login component


Follow these steps to render the Login component:
Chapter 3 63

1. First, edit src/App.jsx and remove all existing code from it.
2. Then, import the Login component, as follows:
import { Login } from './user/Login.jsx'

3. Define and export the App component, which simply renders the Login component for now:
export function App() {
return <Login />
}

If we are only returning a single component, we can omit the brackets in the return state-
ment. Instead of writing return (<Login />), we can simply write return <Login />.

4. Run the dev server by opening a terminal (the Terminal | New Terminal menu option in
VS Code) and executing the following command:
$ npm run dev

5. Open the link to the dev server in your browser, and you should see the Login component
being rendered. If you change the code, it should refresh automatically, so you can keep
the dev server running throughout this chapter.

Figure 3.4 – The first component of our blog application: a login with username and password

As we can see, the static Login component renders fine in React.


64 Writing Your First Application with React Hooks

The Register component


The static Register component will be very similar to the Login component, with an additional
field to repeat the password. One might get the idea to merge them into one component if they
are so similar and add a prop to toggle the additional field. However, in this case, it is better to
have each component deal with only one functionality. Later, we are going to extend the static
components with dynamic code; then, Register and Login will have vastly different logic and
we will need to split them up again.

Nevertheless, let’s start working on the code for the Register component:

1. Create a new src/user/Register.jsx file.


2. Define a form with Username and Password fields, similar to the Login component:
export function Register() {
return (
<form onSubmit={(e) => e.preventDefault()}>
<label htmlFor='register-username'>Username: </label>
<input type='text' name='username' id='register-username' />
<br />
<label htmlFor='register-password'>Password: </label>
<input type='password' name='password' id='register-password'
/>
<br />

Please note that you should prefer spacing via CSS rather than using the
<br /> HTML tag. However, we are focusing on the UI structure and integration
with Hooks in this book, so we simply use HTML whenever possible.

3. Next, add a Repeat password field:


<label htmlFor='register-password-repeat'>Repeat password:
</label>
<input
type='password'
name='password-repeat'
id='register-password-repeat'
/>
<br />
Chapter 3 65

4. Finally, add a Register button:


<input type='submit' value='Register' />
</form>
)
}

5. Again, we can edit src/App.jsx to show our new component, as follows:


import { Register } from './user/Register.jsx'

export function App() {


return <Register />
}

As we can see, the Register component looks very similar to the Login component, but with an
additional field and different text on the button.

The Logout component


Next, we will define the Logout component, which is going to display the name of the currently
logged-in user, and a button to log out:

1. Create a new file called src/user/Logout.jsx.


2. Edit the src/user/Logout.jsx file and define a component that takes a username property:
export function Logout({ username }) {

Here, we use destructuring to extract the username key from the props object. React
passes all component props as the first argument to a function in a single object. Using
destructuring on the first argument is similar to doing const { username } = this.
props in a class component.

3. Inside it, return a form that shows the currently logged-in user and a Logout button:
return (
<form onSubmit={(e) => e.preventDefault()}>
Logged in as: <b>{username}</b>
<input type='submit' value='Logout' />
</form>
)
}
66 Writing Your First Application with React Hooks

4. We can now replace the Register component with the Logout component in src/App.
jsx to see our newly defined component (do not forget to pass the username prop to it!):

import { Logout } from './user/Logout.jsx'

export function App() {


return <Logout username='Daniel Bugl' />
}

Now that the Logout component is defined, we can move on to the UserBar component.

The UserBar component


Now, it is time to put our user-related components together into a UserBar component, where
we are going to conditionally show either the Login and Register components or the Logout
component, depending on whether the user is already logged in or not.

Let’s get started implementing the UserBar component:

1. Create a new src/user/UserBar.jsx file.


2. Inside it, import the Login, Logout, and Register components:
import { Login } from './Login.jsx'
import { Logout } from './Logout.jsx'
import { Register } from './Register.jsx'

3. Define the UserBar component and a variable for the username. For now, we just set it
to a static value:
export function UserBar() {
const username = ''

4. Then, we check whether the user is logged in or not. If the user is logged in, we display
the Logout component, and pass the username to it:
if (username) {
return <Logout username={username} />
}

5. Otherwise, we show the Login and Register components. Here, we can use React.
Fragment (shorthand syntax: <> and </>) instead of a <div> container. This keeps our
UI tree clean, as the components will simply be rendered side by side instead of being
wrapped in another element:
Chapter 3 67

return (
<>
<Login />
<hr />
<Register />
</>
)
}

6. Edit src/App.jsx and show the UserBar component, as follows:


import { UserBar } from './user/UserBar.jsx'

export function App() {


return <UserBar />
}

As you can see, the UserBar component is successfully rendering the Login and Register
components:

Figure 3.5 – The UserBar component when no user is logged in yet


68 Writing Your First Application with React Hooks

1. You can try editing the static username variable to see it render the Logout component
instead. Edit src/user/UserBar.jsx and adjust it as follows:
export function UserBar() {
const username = 'Daniel Bugl'

After making this change, the UserBar component renders the Logout component:

Figure 3.6 – The UserBar component after defining the username

Later in this chapter, we are going to add Hooks to our application, so that we can log in and have
the state change dynamically, without having to edit the code!

Implementing posts
After implementing all the user-related components, we can now move on to implementing posts
in our blog app. We are going to define the following components:

• A Post component to display a single post


• A CreatePost component for creating new posts
• A PostList component to display a list of all posts
Chapter 3 69

The Post component


We have already thought about which elements a post has when creating the mock-up. A post
should have a title, content, and an author (the user who wrote the post).

Let’s implement the Post component now:

1. Create a new src/post/Post.jsx file.


2. Inside it, render all props in a way that resembles the mock-up:
export function Post({ title, content, author }) {
return (
<div>
<h3>{title}</h3>
<div>{content}</div>
<br />
<i>
Written by <b>{author}</b>
</i>
</div>
)
}

3. As always, we can test our component by editing the src/App.jsx file:


import { Post } from './post/Post.jsx'

export function App() {


return (
<Post
title='React Hooks'
content='The greatest thing since sliced bread!'
author='Daniel Bugl'
/>
)
}

Now that the static Post component has been implemented, we can move on to the CreatePost
component.
70 Writing Your First Application with React Hooks

The CreatePost component


We need to implement a form to create a new post. Here, we pass username as a prop to the com-
ponent, as the author should always be the currently logged-in user. Then, we show the author
and provide an input field for the title and a <textarea> element for the content of the blog post.

Let’s implement the CreatePost component now:

1. Create a new src/post/CreatePost.jsx file.


2. Inside it, define the component according to the mock-up:
export function CreatePost({ username }) {
return (
<form onSubmit={(e) => e.preventDefault()}>
<div>
Author: <b>{username}</b>
</div>
<div>
<label htmlFor='create-title'>Title:</label>
<input type='text' name='title' id='create-title' />
</div>
<textarea name='content' />
<input type='submit' value='Create' />
</form>
)
}

3. As always, we can test our component by editing the src/App.jsx file, as follows:
import { CreatePost } from './post/CreatePost.jsx'

export function App() {


return <CreatePost username='Daniel Bugl' />
}

As we can see, the CreatePost component renders fine. We can now move on to the PostList
component.
Chapter 3 71

The PostList component


After implementing the other post-related components, we can now implement the most im-
portant part of our blog app: the feed of blog posts. For now, the feed is simply going to show a
list of blog posts.

Let’s start implementing the PostList component now:

1. Create a new src/post/PostList.jsx file.


2. First, we import Fragment and the Post component:
import { Fragment } from 'react'
import { Post } from './Post.jsx'

3. Then, we define the PostList function component, accepting a posts array as a prop. If
posts is not defined, we set it to an empty array by default:

export function PostList({ posts = [] }) {

4. Next, we render all posts by using the .map function and the spread syntax:
return (
<div>
{posts.map((post, index) => (
<Post {...post} key={`post-${index}`} />
))}
</div>
)
}

We return the <Post> component for each post and pass all the keys from the post object
to the component as props. We do this by using the spread syntax, which has the same
effect as listing all the keys from the object manually as props, like so:
<Post
title={post.title}
author={post.author}
content={post.content}
/>
72 Writing Your First Application with React Hooks

If we are rendering a list of elements, we have to give each element a unique


key prop. React uses this key prop to efficiently compute the difference
between two lists when the data has changed. It is best practice to use a
unique ID for the key prop, such as a database ID, so that React can keep
track of items changing in a list. In this case, however, we do not have such
an ID, so we simply fall back to using the index.

We used the map function, which applies a function to all the elements of an array. This is
similar to using a for loop and storing all the results, but it is more concise, declarative, and
easier to read! Alternatively, we could do the following instead of using the map function:
let renderedPosts = []
let index = 0
for (let post of posts) {
renderedPosts.push(<Post {...post} key={`post-${index}`} />)
index++
}

return (
<div>
{renderedPosts}
</div>
)

However, using this style is not recommended with React.

5. In the mock-up, we have a horizontal line after each blog post. We can implement this
without an additional <div> container element, by using Fragment, as follows:
{posts.map((post, index) => (
<Fragment key={`post-${index}`}>
<Post {...post} />
<hr />
</Fragment>
))}

Using Fragment instead of an additional <div> container keeps the DOM tree clean and
reduces the amount of nesting.
Chapter 3 73

The key prop always has to be added to the uppermost parent element that
is rendered within the map function. In this case, we had to move the key
prop from the Post component to the Fragment.

6. Again, we test our component by editing the src/App.jsx file:


import { PostList } from './post/PostList.jsx'

const posts = [
{
title: 'React Hooks',
content: 'The greatest thing since sliced bread!',
author: 'Daniel Bugl',
},
{
title: 'Using React Fragments',
content: 'Keeping the DOM tree clean!',
author: 'Daniel Bugl',
},
]

export function App() {


return <PostList posts={posts} />
}

Now, we can see that our app lists all the posts that we defined in the posts array.

As we can see, listing multiple posts via the PostList component works fine. We can now move
on to putting the app together.

Putting the app together


After implementing all components to reproduce the mock-up, we only have to put everything
together in the App component. Then, we will have successfully reproduced the mock-up!

Let’s start modifying the App component and putting our app together:

1. Edit src/App.jsx and remove all the current code.


74 Writing Your First Application with React Hooks

2. First, import the UserBar, CreatePost, and PostList components:


import { UserBar } from './user/UserBar.jsx'
import { CreatePost } from './post/CreatePost.jsx'
import { PostList } from './post/PostList.jsx'

3. Then, define some mock data for the app:


const username = 'Daniel Bugl'
const posts = [
{
title: 'React Hooks',
content: 'The greatest thing since sliced bread!',
author: 'Daniel Bugl',
},
{
title: 'Using React Fragments',
content: 'Keeping the DOM tree clean!',
author: 'Daniel Bugl',
},
]

4. Next, define the App component and return a container with some padding:
export function App() {
return (
<div style={{ padding: 8 }}>

5. Now, render the UserBar and CreatePost components, passing the username prop to the
CreatePost component:

<UserBar />
<br />
<CreatePost username={username} />

6. Finally, display the PostList component, passing the posts prop to it:
<hr />
<PostList posts={posts} />
</div>
)
}
Chapter 3 75

After saving the file, the browser should automatically refresh, and we can now see the full UI:

Figure 3.7 – Full implementation of our static blog app, according to the mock-up

As we can see, now, all the static components that we defined earlier are rendered together in
one App component.
76 Writing Your First Application with React Hooks

Example code

The example code for this section can be found in the Chapter03/Chapter03_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Our app now looks just like the mock-up, so, we can now move on to making all the components
dynamic using Hooks.

Implementing stateful components with Hooks


Now that we have implemented the static structure of our application, we are going to add State
Hooks to it to be able to handle state and dynamic interactions!

First, let’s create a new folder for the stateful implementation:

1. Copy the Chapter03_1 folder to a new Chapter03_2 folder, as follows:


$ cp -R Chapter03_1 Chapter03_2

2. Open the new Chapter03_2 folder in VS Code.

Adding Hooks for the user features


To add Hooks for the user features, we are going to have to replace the static username variable
with a Hook. Then, we need to adjust the value when we log in, register, and log out.

Adjusting UserBar
When we created the UserBar component, we statically defined a username variable. We are now
going to replace it with a State Hook!

Let’s start modifying the UserBar component to make it dynamic:

1. Edit src/user/UserBar.jsx and import the useState Hook, as follows:


import { useState } from 'react'

2. Remove the following line of code:


const username = 'Daniel Bugl'
Chapter 3 77

Replace it with a State Hook, using an empty username as the default value:
const [username, setUsername] = useState('')

3. Then, pass the setUsername function to the Logout component:


if (username) {
return <Logout username={username} setUsername={setUsername}
/>
}

For simplicity and to make it easier to follow where the state is being handled,
we are passing the username and setUsername function from the State Hook
directly down to the other components. In real-world projects, it would be
better to use specific names for the handlers instead, such as onLogout. This
reduces coupling between components.

4. Also, pass the setUsername function to the Login and Register components, respectively:
return (
<>
<Login setUsername={setUsername} />
<hr />
<Register setUsername={setUsername} />
</>
)
}

Now, the UserBar component handles setting the username dynamically. However, we
still need to modify the other components to add the handlers.

5. Edit src/user/Logout.jsx and define a handleSubmit function, as follows:


export function Logout({ username, setUsername }) {
function handleSubmit(e) {
e.preventDefault()
setUsername('')
}
78 Writing Your First Application with React Hooks

In React 19, Form Actions were introduced as an advanced way of handling


form submissions. We are going to learn more about Form Actions in
Chapter 7, Using Hooks for Handling Forms. In this chapter, we will be focusing
on using the State Hook and the traditional way of handling forms using an
onSubmit handler function.

6. Then, replace the existing onSubmit handler with the newly defined function:
return (
<form onSubmit={handleSubmit}>

7. Edit src/user/Login.jsx and define a handleSubmit function, as follows:


export function Login({ setUsername }) {
function handleSubmit(e) {
e.preventDefault()
const username = e.target.elements.username.value
setUsername(username)
}

return (
<form onSubmit={handleSubmit}>

As we can see, we can directly access the value of the username field from the form by
using e.target.elements. The key of the form element is equivalent to the name prop on
the <input> element.

8. Edit src/user/Register.jsx and define a handleSubmit function, as follows:


export function Register({ setUsername }) {
function handleSubmit(e) {
e.preventDefault()
const username = e.target.elements.username.value
setUsername(username)
}

return (
<form onSubmit={handleSubmit}>
Chapter 3 79

You can now try registering, logging in, and logging out, and see how the state changes across
components.

Adding validation
When trying out the login and register features, you may have noticed that there is no valida-
tion going on. For simple validation, such as required fields, we can directly use HTML features.
HTML validation will prevent the user from submitting the form if a field is invalid and show
a popup telling the user what’s wrong with it. However, for more complex validation, such as
checking whether the repeated password is the same, we will need to use a State Hook to keep
track of the error state of the form.

Let’s get started implementing validation now:

1. Edit src/user/Login.jsx and add the required prop to the following input fields:
<input type='text' name='username' id='login-username'
required />

<input type='password' name='password' id='login-password'
required />

2. Edit src/user/Register.jsx and add the required prop as well:


<input type='text' name='username' id='register-username'
required />

<input type='password' name='password' id='register-password'
required />

<input
type='password'
name='password-repeat'
id='register-password-repeat'
required
/>

3. In the src/user/Register.jsx file, also import the useState function:


import { useState } from 'react'
80 Writing Your First Application with React Hooks

4. Then, add a new State Hook to keep track of the error state:
export function Register({ setUsername }) {
const [invalidRepeat, setInvalidRepeat] = useState(false)

This kind of state is called local state, as it is only needed within one component.

5. In the handleSubmit function, check whether the password and password-repeat fields
are the same. If not, set the error state and return from the function:
function handleSubmit(e) {
e.preventDefault()
if (
e.target.elements.password.value !==
e.target.elements['password-repeat'].value
) {
setInvalidRepeat(true)
return
}

Early returns from functions, if a certain condition is not met, are usually
preferable over nesting if clauses. Returning early keeps the function easy
to read and avoids problems where code is accidentally executed.

6. After the if clause, if the passwords are the same, reset the error state and process the
registration:
setInvalidRepeat(false)
const username = e.target.elements.username.value
setUsername(username)
}

7. At the end of the form, before the Register button, we insert an error message if the error
state got triggered:
<br />
{invalidRepeat && (
<div style={{ color: 'red' }}>Passwords must
match.</div>
)}
Chapter 3 81

<input type='submit' value='Register' />


</form>

If we try registering now but do not properly repeat the password, we can see the following error
message:

Figure 3.8 – Validation and an error message implemented using Hooks

Now that we have successfully implemented validation, we can move on to passing the username
to the CreatePost component.

Passing the user to CreatePost


As you might have noticed, the CreatePost component still uses the hardcoded username. To
be able to access the username there, we need to move the Hook from the UserBar component
up into the App component:

1. Edit src/user/UserBar.jsx and cut/remove the following Hook definition:


export function UserBar() {
const [username, setUsername] = useState('')

2. Then, adjust the function definition to accept these two as props:


export function UserBar({ username, setUsername }) {

3. Remove the following useState import:


import { useState } from 'react'

4. Now, edit src/App.jsx and import the useState function there:


import { useState } from 'react'

5. Remove the following line of code:


const username = 'Daniel Bugl'
82 Writing Your First Application with React Hooks

6. Inside the App function component, add the Hook we removed earlier:
export function App() {
const [username, setUsername] = useState('')

This kind of state is called global state, as it is needed across multiple components through-
out the blog app, which is also why we moved the State Hook up into the App component.

7. Then, pass down the username value and setUsername function to the UserBar component:
return (
<div style={{ padding: 8 }}>
<UserBar username={username} setUsername={setUsername} />

In Chapter 5, Implementing React Contexts, we are going to learn a better solu-


tion to provide the logged-in state to other components. For now, we are
just going to pass down the value and function, though.

8. Finally, make sure the CreatePost component is only rendered when the user is logged
in (username is defined):
<br />
{username && <CreatePost username={username} />}

Now that the user features are fully implemented, we can move on to using Hooks to implement
the post features!

Adding Hooks for the post features


After implementing the user features, we are now going to implement the dynamic creation of
posts. We do so by first adjusting the App component, then we modify the CreatePost component
to be able to insert new posts.

Adjusting the App component


Similar to the username state, we are going to define posts as a global state in the App component
and provide it to other components from there.
Chapter 3 83

Let’s get started adjusting the App component:

1. Edit src/App.jsx and rename the current posts array to defaultPosts:


const defaultPosts = [
{
title: 'React Hooks',
content: 'The greatest thing since sliced bread!',
author: 'Daniel Bugl',
},
{
title: 'Using React Fragments',
content: 'Keeping the DOM tree clean!',
author: 'Daniel Bugl',
},
]

2. Then, define a new Hook for the posts state inside of the App function:
export function App() {
const [posts, setPosts] = useState(defaultPosts)

3. Now, pass setPosts as a prop to the CreatePost component:


{username && (
<CreatePost username={username} setPosts={setPosts} />
)}

After providing the state to the CreatePost component, let’s continue by adjusting it.

Adjusting the CreatePost component


We now need to use the setPosts function to insert a new post when the Create button is pressed,
as follows:

1. Edit src/post/CreatePost.jsx and adjust the function definition to accept the setPosts
prop:
export function CreatePost({ username, setPosts }) {
84 Writing Your First Application with React Hooks

2. Next, define a handleSubmit function, in which we first gather all the values we need:
function handleSubmit(e) {
e.preventDefault()
const form = e.target
const title = form.elements.title.value
const content = form.elements.content.value
const newPost = { title, content, author: username }

Here, we shortened the { title: title } object assignment to { title },


which has the same effect.

3. Then, we insert the new post into the array:


setPosts((posts) => [newPost, ...posts])

Here, we are using a function to get the current value of the State Hook, then returning a
new value with the new post inserted into the array.

4. Finally, we reset the form to clear all input fields:


form.reset()
}

5. We still need to assign the newly defined function to the onSubmit handler, as follows:
return (
<form onSubmit={handleSubmit}>

Now, we can log in and create a new post, and it will be inserted at the beginning of the feed!
Chapter 3 85

Figure 3.9 – The first version of our blog app using Hooks, after inserting a new post

Example code

The example code for this section can be found in the Chapter03/Chapter03_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.
86 Writing Your First Application with React Hooks

Summary
In this chapter, we developed our own blog application from scratch! We started with a mock-up
and then created static components to resemble it. Afterward, we implemented Hooks to allow for
dynamic behavior. Throughout the chapter, we learned how to deal with local and global state
using Hooks. Furthermore, we learned how to use multiple Hooks, and in which components
to define Hooks and store state. We also learned how to solve common use cases, such as form
validation and submission.

In the next chapter, Chapter 4, Using the Reducer and Effect Hooks, we are going to learn about the
Reducer Hook, which allows us to deal with certain state changes more easily. Furthermore, we
are going to learn about the Effect Hook, which allows us to run code with side effects.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. What are good ways to structure folders in React?


2. Which principle should we use when splitting up React components?
3. What does the map function do?
4. How does destructuring work, and when do we use it?
5. How does the spread operator work, and when do we use it?
6. How do we deal with form validation and submission?
7. Where should local State Hooks be defined?
8. What is global state?
9. Where should global State Hooks be defined?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following link:

• Official docs on Thinking in React: https://react.dev/learn/thinking-in-react


Chapter 3 87

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
Part 2
Using Hooks With
Real-World Examples
In this part, you will learn about various Hooks and how to use them. We start by covering the
Reducer and Effect Hooks for managing application state. Then, we will cover React Contexts and
data fetching using TanStack Query, React Hooks and React Suspense. Next, we will learn how
to handle form submission with React form actions and the Action State Hook. We’ll continue
by learning about using React Router and Hooks to handle various routing scenarios. Then, we’ll
get an overview of all the built-in Hooks provided by React, learning about the advanced built-
in Hooks that haven’t been covered yet in the other chapters. Finally, we’ll wrap up this part by
using various Hooks provided by the React community, as well as learning where to find more
useful Hooks.

This part has the following chapters:

• Chapter 4, Using the Reducer and Effect Hooks


• Chapter 5, Implementing React Contexts
• Chapter 6, Using Hooks and React Suspense for Data Fetching
• Chapter 7, Using Hooks for Handling Forms
• Chapter 8, Using Hooks for Routing
• Chapter 9, Advanced Hooks Provided by React
• Chapter 10, Using Community Hooks
4
Using the Reducer and Effect
Hooks
After developing our own blog application using the State Hook, we are now going to learn about
two other very important Hooks that are provided by React—the Reducer and Effect Hooks. We
will first learn when we should use a Reducer Hook instead of a State Hook. Then, we will learn
how to turn our existing State Hook into a Reducer Hook to get a grasp on the concept in practice.
Finally, we are going to learn about Effect Hooks and what they are used for and implement them
in our blog application.

The following topics will be covered in this chapter:

• Reducer Hooks versus State Hooks


• Using Reducer Hooks
• Using Effect Hooks

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com.
92 Using the Reducer and Effect Hooks

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

While installing a newer version should not be an issue, please note that certain steps might work
differently on a newer version. If you are having an issue with the code and steps provided in this
book, please try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter04.

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided in the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code examples.

Reducer Hooks versus State Hooks


In the previous chapter, we learned about dealing with local and global states. We used State
Hooks for both cases, which is fine for simple state changes. However, if our state logic becomes
more complicated, we are going to need to ensure that we keep the state consistent. To do so,
we should use a Reducer Hook, instead of multiple State Hooks, because it is harder to maintain
synchronicity between multiple State Hooks that depend on each other. As an alternative, we
could keep all states in one State Hook, but then we have to make sure that we don’t accidentally
overwrite parts of our state.

Limitations of the State Hook


The State Hook already supports passing complex objects and arrays to it, and it can handle their
state changes perfectly fine. However, we are always going to have to change the state directly,
which means that we need to use a lot of destructuring to make sure that we are not overwriting
other parts of the state. For example, imagine that we have a State Hook like the following:
const [config, setConfig] = useState({
filter: 'all',
expandPosts: true,
})
Chapter 4 93

Now, let’s say we want to change the filter, as follows:


setConfig({
filter: {
author: 'Daniel Bugl',
fromDate: '2024-10-02',
},
})

If we simply did this, we would be removing the expandPosts setting from our state object! So,
we need to use the spread operator, as follows:
setConfig(config => ({
...config,
filter: {
author: 'Daniel Bugl',
fromDate: '2024-10-02',
}
}))

Now, if we wanted to change the fromDate filter to a different date, we would need to use the
spread operator twice, to avoid accidentally removing the author filter:
setConfig(config => ({
...config,
filter: {
...config.filter,
fromDate: '2024-10-03',
}
}))

But what happens if we do this when the filter state is still a string, as it was in the original
object (filter: 'all')? We are going to get the following result:
{
filter: {
'0': 'a',
'1': 'l',
'2': 'l',
94 Using the Reducer and Effect Hooks

fromDate: '2024-10-03',
},
expandPosts: true
}

What? Why are there suddenly three new keys—'0', '1', and '2'? The answer is, the spread
operator also works on strings, which are spread in a way where each letter gets a key, based on
its index in the string.

As you can imagine, using the spread operator and changing the state object directly can become
very tedious for larger state objects. Additionally, we always need to make sure that we don’t
introduce any bugs, and we need to check for bugs in multiple places across our app.

Reducers
Instead of changing the state directly, we could make a function that deals with state changes.
Such a function is called a reducer, and works as follows:
const newState = reducer(currentState, action)

As you can see, instead of directly changing the state object, we are calling a function that takes
the current state and an action object and returns a new state object. Before we define the function,
let’s first take a closer look at actions.

Actions
Actions are objects that have a type property that contains the action name, and optionally some
additional info about the action.

Let’s revisit our state object from earlier:


{
filter: 'all',
expandPosts: true,
}

If we wanted to change the expandPosts state, we would use a TOGGLE_EXPAND action, which
does not need any additional info. The action would then look as follows:
{ type: 'TOGGLE_EXPAND' }
Chapter 4 95

If we would like to change the filter instead, we would use a CHANGE_FILTER action, which addi-
tionally contains information about the filter that should be changed. For example, we could use
the following actions to change the filter in different ways:
{ type: 'CHANGE_FILTER', all: true }
{ type: 'CHANGE_FILTER', fromDate: '2024-10-02' }
{ type: 'CHANGE_FILTER', author: 'Daniel' }
{ type: 'CHANGE_FILTER', fromDate: '2024-10-03' }

The second, third, and fourth actions would change the filter state from a string to an object
and then set the respective key. If the object already exists, it would simply adjust the keys that
were defined in the action. After each of these actions, the state would change as follows:
{ filter: 'all' }
{ filter: { fromDate: '2024-10-02' } }
{ filter: { fromDate: '2024-10-02', author: 'Daniel' } }
{ filter: { fromDate: '2024-10-03', author: 'Daniel' } }

Now let’s imagine that we applied the following action:


{ type: 'CHANGE_FILTER' all: true }

After this action, the filter would go back to the 'all' string, as it was in its initial state.

If you have worked with the Redux library before, you will already be familiar with
the concepts of state, actions, and reducers.

Defining reducers
A reducer function for the actions we defined could look as follows:
function reducer(state, action) {
switch (action.type) {

Here, we defined the function and are deciding what to do to the state based on the action.type.
First, we handle the TOGGLE_EXPAND action:
case 'TOGGLE_EXPAND':
return { ...state, expandPosts: !state.expandPosts }
96 Using the Reducer and Effect Hooks

Now, the CHANGE_FILTER function is handled, where we reset the filter to the string 'all' if the
action defined the all: true filter:
if (action.all) {
return { ...state, filter: 'all' }
}

If the filter is still a string, we initialize an empty object; otherwise, we reuse the existing object:
let filter = typeof state.filter === 'string' ? {} : state.filter

Now we can set the fromDate and author filters, depending on which ones were defined in the
action:
if (action.fromDate) {
filter.fromDate = action.fromDate
}
if (action.author) {
filter.author = action.author
}

Finally, the state is returned with the new filter:


return { ...state, filter }
}

In the case where an action type is unknown, we throw an error:


default:
throw new Error('unknown action')
}
}

Throwing an error in the default case is different from what we would do with Redux
reducers, where we would simply return the current state in the default case. React
Reducer Hooks do not store all states in one global object and we are only going to
handle certain actions for certain state objects, so we can throw an error for unknown
action types.
Chapter 4 97

While we are still using some spread operators in the reducer function, it is not as deeply nested.
Additionally, all the state handling is in one place and we are only changing one part of the state
at a time through actions, making the code much easier to maintain and less error-prone.

The Reducer Hook


Now that we have a reducer function, we just need to define an initial state:
const initialState = { filter: 'all' }

With the reducer function and the initial state, we can create a Reducer Hook:
const [state, dispatch] = useReducer(reducer, initialState)

The current state can now be accessed via the state object returned from the Hook. Using the
dispatch function allows us to invoke the reducer function that we passed to the Reducer Hook.
Actions can be dispatched via the dispatch function. For example:
dispatch({ type: 'TOGGLE_EXPAND' })

Dispatching an action will call the reducer function with the current state and the dispatched
action, and set the returned state as the new state of the Reducer Hook.

If we want to add additional info to the action, we can simply add it to the object:
dispatch({ type: 'CHANGE_FILTER', fromDate: '2024-10-03' })

As we can see, dealing with state changes using actions and reducers is much easier to read and
maintain than having to adjust the state object directly.

Now that we have learned about Reducer Hooks and when to use them instead of State Hooks,
let’s get started with using them.

Using Reducer Hooks


After learning about actions, reducers, and the Reducer Hook, we are going to use it in our blog
app. Any existing State Hook can be turned into a Reducer Hook when the state or state changes
become too complex.

If there are multiple setState functions that are always called at the same time, it
is a good hint that they should be grouped together in a single Reducer Hook.
98 Using the Reducer and Effect Hooks

Global state is usually a good candidate for using a Reducer Hook, rather than a State Hook, be-
cause changes to it can happen anywhere in the app. It is much easier to deal with state changes
when they only get processed in one function and the components dispatch actions instead of
directly modifying the state. Having all the state-changing logic in one place makes it easier to
maintain and fix bugs, without introducing new ones by forgetting to update the logic everywhere.

Turning a State Hook into a Reducer Hook


In our blog app, we have two global State Hooks:

• The username state – containing the username of the currently logged-in user
• The posts state – containing all the posts in our feed

The username state is quite simple, it only contains a string of the username. So, at the moment,
it does not make sense to turn this into a Reducer Hook, as the state changes are straightforward:

• On login/register: Set the username


• On logout: Clear the username

For the posts state, however, we already needed to use the spread operator to avoid accidentally
removing posts from the feed when creating a new post. So, it seems like a good candidate for a
Reducer Hook, especially considering that it might be extended in the future (fetching new posts,
updating posts, deleting posts, etc.).

Now, let’s get started with replacing the posts State Hook with a Reducer Hook.

Defining actions
We start by defining actions for our Reducer Hook. For now, we are only going to consider a
CREATE_POST action:

{
type: 'CREATE_POST',
post: {
title: 'React Hooks',
content: 'The greatest thing since sliced bread!',
author: 'Daniel Bugl',
},
}

Next, we are going to implement the reducer function.


Chapter 4 99

Implementing the reducer


For now, we are going to place our reducer in a src/reducers.js file. Later, if we have many
reducers, it might make sense to create a separate src/reducers/ folder, with separate files for
each reducer function.

Let’s get started with implementing the reducer function:

1. Copy the Chapter03_2 folder to a new Chapter04_1 folder by executing the following
command:
$ cp -R Chapter03_2 Chapter04_1

2. Open the new Chapter04_1 folder in VS Code.


3. Create a new src/reducers.js file, in which we define and export the postsReducer
function:
export function postsReducer(state, action) {

4. We use a switch statement to handle the different action types:


switch (action.type) {

5. Next, handle the CREATE_POST action, inserting the new post (from action.post) into
the beginning of the array, as follows:
case 'CREATE_POST':
return [action.post, ...state]

6. For now, this will be the only action type that we handle, so we can define the default
statement now, throwing an error when we encounter an unknown action type:
default:
throw new Error('Unknown action type')
}
}

After defining the reducer function, we can now use it to define a Reducer Hook.
100 Using the Reducer and Effect Hooks

Defining the Reducer Hook


To define a Reducer Hook, follow these steps:

1. Edit src/App.jsx and import the useReducer and postsReducer functions:


import { useState, useReducer } from 'react'
import { postsReducer } from './reducers.js'

2. Remove the following State Hook:


export function App() {
const [posts, setPosts] = useState(defaultPosts)

Replace it with a Reducer Hook:


export function App() {
const [posts, dispatch] = useReducer(postsReducer, defaultPosts)

3. Then, instead of setPosts, pass the dispatch function to the CreatePost component,
and remove the setPosts prop:
{username && <CreatePost username={username}
dispatch={dispatch} />}

4. Next, edit src/post/CreatePost.jsx and replace the setPosts prop with the dispatch
prop:
export function CreatePost({ username, dispatch }) {

5. Instead of imperatively adding the new post, we dispatch an action inside the handleSubmit
function now:
function handleSubmit(e) {
e.preventDefault()
const form = e.target
const title = form.elements.title.value
const content = form.elements.content.value
const newPost = { title, content, author: username }
dispatch({ type: 'CREATE_POST', post: newPost })
form.reset()
}
Chapter 4 101

For more complex actions, it might make sense to define functions that will create the
action object, so-called action creators. For example, a createPostAction(post)
function could create and return the CREATE_POST action object. Action creators can
help ensure a consistent structure of action objects, make it easier for us to create
them, and allow for this structure to be adjusted easily in the future.

Now the posts state uses a Reducer Hook instead of a State Hook, but it works the same way as
before! If we want to add more logic for managing posts later, such as deleting and editing posts,
it will be much easier to do so now.

Example code

The example code for this section can be found in the Chapter04/Chapter04_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

After learning about the Reducer Hook, let’s continue learning about the Effect Hook.

Using Effect Hooks


The Effect Hook is an important Hook to synchronize your components with external systems,
such as external APIs or the browser APIs. However, it is often overused in React code. If there is
no external system involved, you should not use an Effect Hook.

In the case of our blog, we are going to implement a way to check whether the user has an admin
role in the Logout component. For simplicity, and to focus on the Effect Hook itself, we are simply
going to mock this check, but imagine that this is being done by an external API.

Remember componentDidMount and componentDidUpdate?


If you have worked with older React versions before, you have probably used the componentDidMount
and componentDidUpdate life cycle methods. For example, if we wanted to set the title of a web
page to a given prop using React class components, we would need to add the following life cycle
method:
import React from 'react'

class App extends React.Component {


102 Using the Reducer and Effect Hooks

componentDidMount() {
const { title } = this.props
document.title = title
}

render() {
return <div>Example App</div>
}
}

This works fine. However, when the title prop updates, the change does not get reflected in
the title of our web page. To solve this problem, we need to define the componentDidUpdate life
cycle method, as follows:
import React from 'react'

class App extends React.Component {


componentDidMount() {
const { title } = this.props
document.title = title
}

componentDidUpdate(prevProps) {
const { title } = this.props
if (title !== prevProps.title) {
document.title = title
}
}

render() {
return <div>Example App</div>
}
}

You might have noticed that we are doing the exact same thing twice; therefore, we could create
a new method to deal with updates to the title, and then call it from both life cycle methods. In
the following code block, the updated code is highlighted in bold:
Chapter 4 103

import React from 'react'

class App extends React.Component {


updateTitle() {
const { title } = this.props
document.title = title
}

componentDidMount() {
this.updateTitle()
}

componentDidUpdate(prevProps) {
if (this.props.title !== prevProps.title) {
this.updateTitle()
}
}

render() {
return <div>Example App</div>
}
}

However, we still need to call this.updateTitle() twice. When we update the code later on,
and, for example, pass an argument to this.updateTitle(), we always need to remember to
pass it to both calls to the function. If we forget to update one of the life cycle methods, we might
introduce bugs. Additionally, we need to add an if condition to componentDidUpdate, in order
to avoid calling this.updateTitle() when the title prop did not change.

From life cycle methods to Effect Hooks


In the world of Hooks, the componentDidMount and componentDidUpdate life cycle methods are
combined in the useEffect Hook, which—when not specifying a dependency array—triggers
on every re-render. We will learn more about the dependency array in the next sub-section.
104 Using the Reducer and Effect Hooks

So, instead of using a class component, we can now define a function component with an Effect
Hook, which would do the same thing as before:
import { useEffect } from 'react'

function App({ title }) {


useEffect(() => {
document.title = title
})
return <div>Example App</div>
}

And that’s all we need to do! The Effect Hook will call the provided function every time when the
component re-renders.

Since React 19, it is possible to change the title (or any metadata tags) of a web page
by defining a <title> (or <link> or <meta>) element in any component. These
elements will then automatically be hoisted up into the <head> section.

Triggering an effect only when certain props change


If we want to make sure that our effect function only gets called when the title prop changes,
we can specify which values should trigger the changes, as a second argument to the useEffect
Hook—the dependency array:
useEffect(() => {
document.title = title
}, [title])

The dependency array is not just restricted to props; we can use any value that is available from
inside the component body here, even variables defined inside the component and values from
other Hooks, such as a State Hook or a Reducer Hook:
const [title, setTitle] = useState('')
useEffect(() => {
document.title = title
}, [title])
Chapter 4 105

As we can see, using an Effect Hook is much more straightforward than dealing with life cycle
methods. All we need to specify is which values the Effect Hook should depend on. Whenever
any of these values change, the effect function automatically gets called again.

Triggering an effect only on mount


If we want to replicate the behavior of only adding a componentDidMount life cycle Hook, with-
out triggering when the props change, we can do this by passing an empty array as the second
argument to the useEffect Hook:
useEffect(() => {
document.title = title
}, [])

Passing an empty array means that our effect function will only trigger once when the compo-
nent mounts, and it will not trigger when props change. However, instead of thinking about the
mounting of components, with Hooks, we should think about the dependencies of the effects.
In this case, the effect does not have any dependencies, which means it will only trigger once. If
an effect has dependencies specified, it will trigger again when any of the dependencies change.

Cleaning up effects
Sometimes effects need to be cleaned up when the component unmounts. To do so, we can return a
function from the Effect Hook. This returned function works similarly to the componentWillUnmount
life cycle method:
useEffect(() => {
const updateInterval = setInterval(
() => console.log('fetching update'),
updateTime,
)
return () => clearInterval(updateInterval)
}, [updateTime])

The highlighted code is called the cleanup function. The cleanup function will be called when the
component unmounts and before running the effect again. This avoids bugs when, for example,
the updateTime prop changes. In that case, the previous effect will be cleaned up and an interval
with the new updateTime will be defined.
106 Using the Reducer and Effect Hooks

Implementing an Effect Hook in our blog app


Now that we have learned how the Effect Hook works, we are going to use it in our blog app, to
implement a way to check the user role when the user is already logged in.

Let’s implement an Effect Hook by following these steps:

1. Copy the Chapter04_1 folder to a new Chapter04_2 folder by executing the following
command:
$ cp -R Chapter04_1 Chapter04_2

2. Open the new Chapter04_2 folder in VS Code.


3. Edit src/user/Logout.jsx and import the useState and useEffect functions:
import { useState, useEffect } from 'react'

4. Then, define a function that simulates an external API that checks the role of a user:
function getRole(username) {

5. For the sake of simplicity, we say that any user called admin has the admin role. All others
have the user role:
if (username === 'admin') return 'admin'
return 'user'
}

6. Now, we need to define a State Hook for the role:


export function Logout({ username, setUsername }) {
const [role, setRole] = useState('user')

7. Next, define an Effect Hook that sets the role by calling the “external API” and using its
response:
useEffect(() => {
setRole(getRole(username))
}, [username])
Chapter 4 107

It is best practice to list all values (and functions) that are used within an
Effect Hook in the dependency array. This ensures that there are no acciden-
tal bugs when values that seem static at the moment become dynamic later
on. Thankfully, the React Hooks ESLint plugin (which is already set up in
our project) will warn us if we forget to add a dependency.

8. Finally, we display the role if a user has a special role:


return (
<form onSubmit={handleSubmit}>
Logged in as: <b>{username}</b>
{role !== 'user' ? ` (role: ${role})` : ''}

9. Start the blog app by executing the following command:


$ npm run dev

Try logging in with the username admin. You will see that the role is now being displayed next
to the username!

Example code

The example code for this section can be found in the Chapter04/Chapter04_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Summary
In this chapter, we first learned about actions, reducers, and Reducer Hooks. We also learned when
we should use Reducer Hooks instead of State Hooks. Then, we replaced our existing global State
Hook for the posts state with a Reducer Hook. Next, we learned about Effect Hooks, and how they
can be used instead of componentDidMount and componentDidUpdate life cycle methods. Finally,
we implemented role verification in our blog app by using an Effect Hook.

In the next chapter, we are going to learn about React Context, and how to use it with Hooks.
Then, we are going to add Context Hooks to our app, to avoid having to pass down props over
multiple layers of components.
108 Using the Reducer and Effect Hooks

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. What are the common problems with State Hooks?


2. What are actions?
3. What are reducers?
4. When should we use a Reducer Hook instead of a State Hook?
5. Which steps are needed to turn a State Hook into a Reducer Hook?
6. How could we create actions more easily?
7. What is the equivalent of Effect Hooks in class components?
8. What are the advantages of using an Effect Hook, versus class components?
9. What is the dependency array and how does it work?
10. How can cleanup functions be used with Effect Hooks?

Further reading
If you are interested in learning more about the concepts that we have learned in this chapter,
take a look at the following links:

• Official docs regarding the Reducer Hook: https://react.dev/reference/react/


useReducer
• Official docs and tips for using Effect Hooks: https://react.dev/reference/react/
hooks#effect-hooks
• More information on when an Effect Hook should not be used: https://react.dev/
learn/you-might-not-need-an-effect
• More information about Redux, a library that offers a more extensive version of actions
and reducers: https://redux.js.org
Chapter 4 109

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
5
Implementing React Contexts
In the previous chapters, we learned about the State Hook, the Reducer Hook, and the Effect Hook.
We developed a small blog application using these Hooks. As we noticed during the development
of our blog app, we had to pass down the username state from the App component to the UserBar
component, and from the UserBar component to the Login, Register, and Logout components.
To avoid having to pass down the state like this, we are now going to learn about React Context
and Context Hooks.

We are going to begin by learning what React Context is, and what providers and consumers
are, by implementing themes as an example for contexts. Then, we are going to use Hooks as a
context consumer and discuss when contexts should be used. Finally, we are going to implement
global state using contexts.

The following topics will be covered in this chapter:

• Introducing React Context


• Implementing themes via context
• Alternatives to contexts
• Using context for global state

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out the official website: https://nodejs.org/.
112 Implementing React Contexts

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to the official website: https://code.visualstudio.com.

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• VS Code v1.97.2

While installing a newer version should not be an issue, please note that certain steps might work
differently on a newer version. If you are having an issue with the code and steps provided in this
book, please try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter05.

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Introducing React Context


In the previous chapters, we passed down the username state and setUsername function from
the App component to the UserBar component; and then from the UserBar component to the
Logout, Login, and Register components. React Context provides a solution to this cumbersome
way of passing down props over multiple levels of components, by allowing us to share values
between components without having to explicitly pass them down via props. As we are going to
see, contexts are perfect for sharing global state across the whole application.

Passing down props


Before learning about React Context in depth, let’s recap what we implemented in the earlier
chapters to get a feeling for the problem that contexts solve. You do not need to edit any code
at this point; these steps are just a recap of what we have already done. Just read along for the
following steps:
Chapter 5 113

1. In src/App.jsx, we defined the username state and setUsername function using a State
Hook:
export function App() {
const [posts, dispatch] = useReducer(postsReducer, defaultPosts)
const [username, setUsername] = useState('')

2. Then, we passed the username state and setUsername function to the UserBar component:
return (
<div style={{ padding: 8 }}>
<UserBar username={username} setUsername={setUsername} />

3. In the src/user/UserBar.jsx file, we defined a UserBar component that takes the


username state as a prop and then passes it down to the Logout component. We also
passed down the setUsername function to the Logout, Login, and Register components:
export function UserBar({ username, setUsername }) {
if (username) {
return <Logout username={username} setUsername={setUsername} />
} else {
return (
<>
<Login setUsername={setUsername} />
<hr />
<Register setUsername={setUsername} />
</>
)
}
}

4. Finally, we used the setUsername function and the username state in the Logout, Login,
and Register components:
export function Login({ setUsername }) {
function handleSubmit(e) {
e.preventDefault()
const username = e.target.elements.username.value
setUsername(username)
}
114 Implementing React Contexts

React Context allows us to skip steps 2 and 3 and jump straight from step 1 to step 4. As you can
imagine, in larger apps, context becomes even more useful because we might have to pass down
props over many levels.

In the next section, we are first going to learn how context works by implementing a theme system
for our blog. Then, we will use React Context to deal with the username global state in our blog app.

Implementing themes via context


React Context is used to share values across a tree of React components. Usually, we want to
share global values, such as the username state, the theme of our app, or the chosen language (if
the app supports multiple languages).

React Context consists of three parts:

• The context itself, which defines a default value and allows us to provide and consume
values
• The provider, which provides (sets) the value
• The consumer, which consumes (uses) the value

Defining the context


First, we have to define the context. The way this works has not changed since Hooks were intro-
duced. We simply use the createContext(defaultValue) function from React to create a new
context object. In this case, we will set the default value to { primaryColor: 'maroon' }, so our
default primary color, when no provider is defined, will be maroon.

Now, let’s get started defining the context:

1. Copy the Chapter04_2 folder to a new Chapter05_1 folder by executing the following
command:
$ cp -R Chapter04_2 Chapter05_1

2. Open the new Chapter05_1 folder in VS Code.


3. To keep our project clean as it grows, we are now extending the folder structure by group-
ing by base primitives first, then grouping by features within that folder. Create a new
src/contexts/ folder now.
4. Also, create a new src/components/ folder.
5. Move the src/post/ and src/user/ folders into the src/components/ folder.
Chapter 5 115

6. Edit src/App.jsx and adjust the imports, as follows:


import { UserBar } from './components/user/UserBar.jsx'
import { CreatePost } from './components/post/CreatePost.jsx'
import { PostList } from './components/post/PostList.jsx'

7. Create a new src/contexts/ThemeContext.js file. Inside it, import the createContext


function:
import { createContext } from 'react'

8. Now, define the context with the aforementioned default value:


export const ThemeContext = createContext(
{ primaryColor: 'maroon' }
)

When the context is consumed but no provider is defined, then it will return this default
value.

Note how we are exporting ThemeContext here because we are going to need to
import it later to create the provider and consume it using a Context Hook.

That’s all we need to do to define a context with React.

Quick detour – absolute imports


If we were to import the context in a component now, we would have to import from ../../
contexts/ThemeContext.js. Besides the fact that it becomes hard to read when files are deeply
nested, it can cause issues when organizing files into subfolders later. To avoid these problems,
we can use absolute imports. Absolute imports allow us to import from the root of a project.
They are implemented using resolve aliases in Vite. Basically, we can tell Vite to resolve a special
character, such as an @ symbol, to an absolute path to the src folder. This means that we can
import the context from @/contexts/ThemeContext.js instead.

Let’s get started configuring absolute imports now:

1. Edit vite.config.js and import the path utils:


import path from 'node:path'
116 Implementing React Contexts

2. In the config object, add a resolve alias, as follows:


export default defineConfig({
plugins: [react()],
resolve: {
alias: [{ find: '@', replacement:
path.resolve(import.meta.dirname, 'src') }],
},
})

3. Additionally, we can improve autocompletion in our code editor or IDE by creating a


jsconfig.json file. This file will tell the editor about our absolute import configuration
and let us easily import files from it. Create a new jsconfig.json file now.
4. Inside it, add the following config:
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules"]
}

Now that we can use absolute imports, let’s continue defining the consumer.

Defining the consumer


To use a context, we need a consumer. Let’s first revisit the traditional way of defining a consumer
before we get started using Hooks:

1. Edit src/components/post/Post.jsx and import ThemeContext there:


import { ThemeContext } from '@/contexts/ThemeContext.js'

2. Wrap the whole component with a ThemeContext.Consumer component and a render


function as its children prop, to make use of the context value:
export function Post({ title, content, author }) {
return (
Chapter 5 117

<ThemeContext.Consumer>
{(theme) => (

The render function allows us to pass values down to the children of a component.

3. Inside the render function, we can now make use of the context value to set the color of
the title of a blog post, as follows:
<div>
<h3 style={{ color: theme.primaryColor }}>
{title}
</h3>
<div>{content}</div>
<br />
<i>
Written by <b>{author}</b>
</i>
</div>
)}
</ThemeContext.Consumer>
)
}

Using contexts like this works, but as we have learned in the first chapter, using components
with render functions in this way clutters the React tree and makes our app harder to debug and
maintain.

Using Hooks to consume a context


A better way to consume contexts is using a Context Hook! That way, we can use context values
like any other value.

Follow these steps to change the consumer to a Context Hook:

1. Edit src/components/post/Post.jsx and add the following import:


import { useContext } from 'react'

2. Then, define a Context Hook, as follows:


export function Post({ title, content, author }) {
const theme = useContext(ThemeContext)
118 Implementing React Contexts

3. Next, remove the following highlighted parts of the code:


return (
<ThemeContext.Consumer>
{(theme) => (
<div>
<h3 style={{ color: theme.primaryColor }}>{title}</h3>
<div>{content}</div>
<br />
<i>
Written by <b>{author}</b>
</i>
</div>
)}
</ThemeContext.Consumer>
)
}

As you can see, using a Context Hook allows us to directly consume the value from the
context and simply render the post without needing a wrapper component.

4. Start the blog app by executing the following command:


$ npm run dev

We can see that the title of blog posts is now maroon colored:
Chapter 5 119

Figure 5.1 – Using a Context Hook to change the theme of our app
120 Implementing React Contexts

As we can see, the theme context successfully provides the color for the post title.

Defining the provider


Contexts use the default value that is passed to createContext when there is no provider defined.
For example, let’s imagine our component uses ThemeContext and is rendered as follows:
<Component />

Then, primaryColor will be set to maroon (we defined this earlier). This can be used as a fallback,
for example, when the component is not embedded in the app but instead embedded in an in-
teractive style guide (such as Storybook).

When there is a provider defined, they will use the value of the provider. Let’s render the com-
ponent like this instead:
<ThemeContext.Provider value={{ primaryColor: 'black' }}>
<Component />
</ThemeContext.Provider>

Then, primaryColor will be set to black.

If there are multiple providers in the tree, the components will use the value from the closest
parent provider. For example, let’s say we render components like this:
<ThemeContext.Provider value={{ primaryColor: 'black' }}>
<OtherComponent />
<ThemeContext.Provider value={{ primaryColor: 'red' }}>
<Component />
</ThemeContext.Provider>
</ThemeContext.Provider>

Then, primaryColor in the component will be set to red, as that is the provider closest to the
component in the tree. However, OtherComponent in this example will still have primaryColor
set to black.

As we can see, a context without a provider is just a static value; the provider (especially in com-
bination with other Hooks, such as a State Hook for the value) is what allows us to dynamically
change the value of a context.
Chapter 5 121

Let’s define the provider now. Follow these steps to get started:

1. Edit src/App.jsx and import ThemeContext:


import { ThemeContext } from './contexts/ThemeContext.js'

2. Then, wrap the contents of the App component with the ThemeContext.Provider com-
ponent, and provide a value:
export function App() {
const [posts, dispatch] = useReducer(postsReducer, defaultPosts)
const [username, setUsername] = useState('')
return (
<ThemeContext.Provider value={{ primaryColor: 'black' }}>
<div style={{ padding: 8 }}>
<UserBar username={username} setUsername={setUsername} />
<br />
{username && <CreatePost username={username}
dispatch={dispatch} />}
<hr />
<PostList posts={posts} />
</div>
</ThemeContext.Provider>
)
}

As we can see, the post titles are now back to being rendered in black. If we want to change the
value of our context, we can simply adjust the value prop that is passed to the provider compo-
nent. We could also, for example, use a State Hook to dynamically change the value of a context.
We will try this later when we use a context for the global state.

If we do not pass a value prop to the provider, the default value of a context will not
be used! If we define a provider without a value prop, then the value of the context
will be undefined.

Now that we have defined a single provider for the context, let’s move on to defining multiple
nested providers.
122 Implementing React Contexts

Nesting providers
With React Context, it is also possible to define multiple providers for the same context. Using this
technique, we can override the context value in certain parts of our app. For example, let’s say we
want to implement a featured posts section for our blog app. Then, we could do the following:

1. Edit src/App.jsx and define a new featuredPosts array:


const featuredPosts = [
{
title: 'React Context',
content: 'Manage global state with ease!',
author: 'Daniel Bugl',
},
]

2. Now, inside the App component, render a new PostList component rendering the
featuredPosts array, but wrap it inside another ThemeContext.Provider:

export function App() {


const [posts, dispatch] = useReducer(postsReducer, defaultPosts)
const [username, setUsername] = useState('')
return (
<ThemeContext.Provider value={{ primaryColor: 'black' }}>
<div style={{ padding: 8 }}>
<UserBar username={username} setUsername={setUsername} />
<br />
{username && <CreatePost username={username}
dispatch={dispatch} />}
<hr />
<ThemeContext.Provider value={{ primaryColor: 'salmon' }}>
<PostList posts={featuredPosts} />
</ThemeContext.Provider>
<PostList posts={posts} />
</div>
</ThemeContext.Provider>
)
}
Chapter 5 123

You will now see that the featured post has a different color from the other posts:

Figure 5.2 – Implementing featured posts by overriding context values with nested providers
124 Implementing React Contexts

Example code

The example code for this section can be found in the Chapter05/Chapter05_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Alternatives to contexts
We should be careful not to use React Context too often, as it makes reusing components more
difficult. We should only use context when we need to access data in many components, which
are at different nesting levels. Additionally, we need to make sure that we only use contexts for
non-frequently changing data. Frequently changing values of contexts, especially contexts that
are used high up in the component tree, may cause large parts of the component tree to re-render,
resulting in performance problems. That is why, for frequently changing values, we should use
a state management solution such as Jotai, Redux, or MobX instead. These state management
solutions allow us to access small parts of the state in a fine-grained way and thus reduce the
amount of re-renders. Good candidates for contexts are features such as theming and translation
(i18n) systems.

If we only want to avoid having to pass down props, in some cases, we can pass down the rendered
component instead of the data. For example, let’s say we have a Page component that renders a
Header component, which renders a Profile component, which then renders an Avatar com-
ponent. We get a headerSize prop passed to the Page component, which we need in the Header
component, but also in the Avatar component:
function Page({ headerSize }) {
return <Header size={headerSize} />
}

function Header({ size }) {


// ... makes use of size ...
return <Profile size={size} />
}

function Profile({ size }) {


// ... does not use size, only passes it down ...
return <Avatar size={size} />
Chapter 5 125

function Avatar({ size }) {


// ... makes use of size ...
}

Instead of passing down props through multiple levels, we could do the following:
function Page({ headerSize }) {
const profile = (
<Profile>
<Avatar size={headerSize} />
</Profile>
)
return <Header size={headerSize} profile={profile} />
}

Now, only the Page component needs to know about the props, and there is no need to pass them
down further in the tree. In this case, contexts are not necessary.

Such a pattern is called inversion of control, and it can make your code much cleaner than passing
down props or using a context. However, we should not always use this pattern either because it
makes the higher-level component more complicated.

Now, let’s continue by learning about using context for global state.

Using context for global state


After learning how to use React Context to implement themes in our blog app, we are now going
to use a context to avoid having to manually pass down the username and setUsername props.
The user state is a global state, which means it is used across the whole app. It also does not
change frequently. As such, it is a good candidate for using a context. Like we did before, we start
by defining the context.

Defining the context


To define the context, we need to use the createContext function again. In this case, we set the
default value to an array with an empty string and a no-op function (a function that does nothing).
Later, when defining the provider, we will provide this array by using the result from the State
Hook. Remember, the State Hook returns an array like this: [value, setValue].
126 Implementing React Contexts

Now, let’s get started defining the context:

1. Copy the Chapter05_1 folder to a new Chapter05_2 folder by executing the following
command:
$ cp -R Chapter05_1 Chapter05_2

2. Open the new Chapter05_2 folder in VS Code.


3. Create a new src/contexts/UserContext.js file. Inside it, import the createContext
function:
import { createContext } from 'react'

4. Now, define the context with the aforementioned default value, which imitates the return
value of a State Hook, but with an empty string and a no-op function (a function that
does nothing):
export const UserContext = createContext(
['', () => {}]
)

When the context is consumed but no provider is defined, then it will return this default
value.

Now, let’s continue with defining the context provider.

Defining the context provider


We have already created a State Hook for the username state. We can now use the result from this
State Hook and pass it to the context provider so that any component in our app can make use of it.

Let’s get started defining the context provider now:

1. Edit src/App.jsx and import UserContext, as follows:


import { UserContext } from './contexts/UserContext.js'

2. Then, wrap the result of the App component with UserContext.Provider:


export function App() {
const [posts, dispatch] = useReducer(postsReducer, defaultPosts)
const [username, setUsername] = useState('')
return (
<UserContext.Provider value={[username, setUsername]}>
<ThemeContext.Provider value={{ primaryColor: 'black' }}>
Chapter 5 127

3. Now, we can remove the following props we previously passed down:


<div style={{ padding: 8 }}>
<UserBar username={username} setUsername={setUsername} />
<br />
{username && <CreatePost username={username}
dispatch={dispatch} />}
<hr />
<ThemeContext.Provider value={{ primaryColor: 'salmon' }}>
<PostList posts={featuredPosts} />
</ThemeContext.Provider>
<PostList posts={posts} />
</div>

4. Do not forget to add the closing tag for UserContext.Provider:


</ThemeContext.Provider>
</UserContext.Provider>
)
}

Of course, it is also possible to use a similar pattern to pass the result of a Reducer
Hook into a context.

The context provider now provides the username value and the setUsername function to the rest
of our app.

Refactoring the app to use UserContext


Now that we have a context provider, we can refactor the rest of our app to use the context in-
stead of props.

Follow these steps to get started:

1. First, edit src/components/user/UserBar.jsx and add imports to the useContext func-


tion and UserContext:
import { useContext } from 'react'
import { UserContext } from '@/contexts/UserContext.js'
128 Implementing React Contexts

2. Then, remove the props passed to the component:


export function UserBar({ username, setUsername }) {

3. Next, define the Context Hook and get the username value from it:
const [username] = useContext(UserContext)

4. We can now remove the props passed down to the other components:
if (username) {
return <Logout username={username} setUsername={setUsername} />
}

return (
<>
<Login setUsername={setUsername} />
<hr />
<Register setUsername={setUsername} />
</>
)
}

5. Edit src/components/user/Login.jsx and add an import of useContext and UserContext:


import { useContext } from 'react'
import { UserContext } from '@/contexts/UserContext.js'

6. Then, remove the props from the component:


export function Login({ setUsername }) {

7. Add the Context Hook:


const [, setUsername] = useContext(UserContext)

If we do not need the first element of an array, we can skip it when destruc-
turing by simply putting a comma without specifying the name of the first
variable.
Chapter 5 129

8. Edit src/components/user/Logout.jsx and add an import of useContext and


UserContext:

import { useState, useEffect, useContext } from 'react'


import { UserContext } from '@/contexts/UserContext.js'

9. Then, remove the props from the component:


export function Logout({ username, setUsername }) {

10. Add the Context Hook:


const [username, setUsername] = useContext(UserContext)

11. Edit src/components/user/Register.jsx and add an import of useContext and


UserContext:

import { useState, useContext } from 'react'


import { UserContext } from '@/contexts/UserContext.js'

12. Then, remove the props from the component:


export function Register({ setUsername }) {

13. Add the Context Hook:


const [, setUsername] = useContext(UserContext)

14. Edit src/components/post/CreatePost.jsx and add an import of useContext and


UserContext:

import { useContext } from 'react'


import { UserContext } from '@/contexts/UserContext.js'

15. Then, remove the username prop from the component:


export function CreatePost({ username, dispatch }) {

16. Add the Context Hook, as follows:


const [username] = useContext(UserContext)

17. Start the app, as follows:


$ npm run dev
130 Implementing React Contexts

Now, the app works the same way as before, but our code is a lot cleaner and more concise, all
thanks to React Context and the Context Hook!

Example code

The example code for this section can be found in the Chapter05/Chapter05_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Summary
In this chapter, we first learned about React Context as an alternative to passing down props over
multiple levels of React components. We then learned about context providers and consumers,
and the new way to define consumers via Hooks. We used what we learned in practice, by im-
plementing theme support in our blog app. Next, we learned when it does not make sense to use
contexts, and when we should use inversion of control instead. Finally, we used a context for the
global username state in our blog app.

In the next chapter, we are going to learn how to request data from a server, using React and
Hooks. Then, we are going to learn about React Suspense, so that we do not have to wait for the
requests to finish before rendering our app.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. What problem do contexts avoid?


2. What are the three parts that contexts consist of?
3. Are all parts required to be defined in order to use contexts?
4. What is the advantage of using Hooks instead of traditional context consumers?
5. What is an alternative to contexts and when should we use it?
6. How can we implement dynamically changing contexts?
7. When does it make sense to use contexts for state?
Chapter 5 131

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• Official docs on React Context: https://react.dev/learn/passing-data-deeply-with-


context
• List of HTML color codes (if you want to adjust your theme): https://www.rapidtables.
com/web/color/html-color-codes.html
• Example of contexts for local state used by react-aria: https://react-spectrum.adobe.
com/react-aria/advanced.html#contexts
• Example of contexts for global state used by react-i18next: https://react.i18next.
com/latest/i18nextprovider

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
6
Using Hooks and React
Suspense for Data Fetching
In the previous chapter, we learned how to use React Context as an alternative to manually passing
down props. We learned about context providers, consumers and the Context Hook.

In this chapter, we are going to first set up a simple backend server from a JSON file using the
json-server tool. Then, we are going to fetch data from our server using an Effect Hook in
combination with a State Hook. Next, we are going to do the same using TanStack Query, a popular
data fetching library for React that makes use of Hooks. Finally, we will learn about React Suspense,
which can be used to defer rendering until the content has finished loading.

The following topics will be covered in this chapter:

• Setting up a simple backend server


• Requesting resources using an Effect and a State Hook
• Using TanStack Query to request resources and make changes
• Introducing React Suspense and Error Boundaries

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.
134 Using Hooks and React Suspense for Data Fetching

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter06

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Setting up a simple backend server


Before we can implement requests, we need to implement a server. Since we are focusing on the
user interface in this book, we are going to set up a dummy server, which will allow us to test
out requests. We are going to use the json-server tool to create a mock Representational State
Transfer (REST) Application Programming Interface (API) from a JSON file.

Creating the db.json file


To be able to use the json-server tool, we first need to create a db.json file, which is going to
contain the database for the server. The json-server tool will then create a REST API that allows
us to access and modify the db.json file, as follows:

• GET requests, to view data from the file


• POST requests, to insert new data into the file
• PUT and PATCH requests, to adjust existing data in the file
• DELETE requests, to remove data from the file
Chapter 6 135

The structure of the REST API is inferred from a JSON object in the db.json file. For all modifying
actions (POST, PUT, PATCH, and DELETE), the updated file will be automatically saved by the tool.

We can use our existing structure for posts, which we defined as defaultPosts in our App
component, but we need to provide an additional id value, so that we can query posts from the
database later. Additionally, we give each post a featured value. This will be important later to
distinguish between featured and regular posts when we implement the request.
[
{
"id": "1",
"title": "React Hooks",
"content": "The greatest thing since sliced bread!",
"author": "Daniel Bugl",
"featured": false
},
{
"id": "2",
"title": "Using React Fragments",
"content": "Keeping the DOM tree clean!",
"author": "Daniel Bugl",
"featured": false
},
{
"id": "3",
"title": "React Context",
"content": "Manage global state with ease!",
"author": "Daniel Bugl",
"featured": true
}
]

For the users, we need to come up with a way to store usernames and passwords. For simplicity,
we just store the password in plain text (never do this in a production environment!):
[
{
"id": "1",
"username": "Daniel Bugl",
136 Using Hooks and React Suspense for Data Fetching

"password": "hunter2"
}
]

Now all that is left to do is to combine these two arrays into a single JSON object, by storing the
posts array under a posts key, and the users array under a users key.

Let’s get started creating the JSON file for our backend server now:

1. Copy the Chapter05_2 folder to a new Chapter06_1 folder by executing the following
command:
$ cp -R Chapter05_2 Chapter06_1

2. Open the new Chapter06_1 folder in VS Code.


3. Create a new server/ folder, directly inside the Chapter06_1 folder.
4. Create a server/db.json file with the following contents:
{
"posts": [
{
"id": "1",
"title": "React Hooks",
"content": "The greatest thing since sliced bread!",
"author": "Daniel Bugl",
"featured": false
},
{
"id": "2",
"title": "Using React Fragments",
"content": "Keeping the DOM tree clean!",
"author": "Daniel Bugl",
"featured": false
},
{
"id": "3",
"title": "React Context",
"content": "Manage global state with ease!",
"author": "Daniel Bugl",
"featured": true
Chapter 6 137

}
],
"users": [
{
"id": "1",
"username": "Daniel Bugl",
"password": "hunter2"
}
]
}

That’s all we need to automatically create a simple backend with a REST API using the json-server
tool. Let’s continue by setting up the tool.

Installing the json-server tool


Now, we are going to install and start our backend server by using the json-server tool:

1. First, install the json-server tool, as follows:


$ npm install --save-exact [email protected]

2. Now, start the backend server by executing the following command:


$ npx json-server server/db.json

The npx command executes commands that were installed locally in a project. We
need to use npx here, because we did not globally install the json-server tool (via
npm install -g json-server).

We executed the json-server tool, and it is watching the server/db.json file that we created
earlier.

By default, the json-server tool defines the following routes for each key in the JSON object:
GET /posts
GET /posts/:id
POST /posts
PUT /posts/:id
PATCH /posts/:id
DELETE /posts/:id
138 Using Hooks and React Suspense for Data Fetching

Now, we can go to http://localhost:3000/posts/1 in order to see our post object:

Figure 6.1 – The json-server tool serving a post via its REST API!

As we can see, the tool created a full REST API from the database JSON file for us! Now, let’s
continue by configuring package.json scripts so that the json-server tool is started together
with our frontend.

Configuring the package.json scripts


Let’s get started adjusting the package.json file:

1. Edit package.json and define a new script called dev:server, by inserting it in the scripts
section. We also make sure to change the port to be adjacent to the Vite default port
(which is 5173):
"scripts": {
"dev:server": "json-server server/db.json --port 5174",

2. Then, we rename the dev script to dev:client:


"scripts": {
"dev:server": "json-server server/db.json --port 5174",
"dev:client": "vite",

3. Save the package.json file, otherwise running npm install later will overwrite our
changes.
4. If it is still running, quit the json-server tool by pressing Ctrl+C.
Chapter 6 139

5. Next, we install a tool called concurrently, which lets us start the server and client at
the same time:
$ npm install --save-dev --save-exact [email protected]

6. Now, we edit package.json again and define a new dev script by using the concurrently
command and then passing the server and client commands as arguments to it:
"scripts": {
"dev": "concurrently \"npm run dev:server\" \"npm run
dev:client\"",

7. Try executing the following command now:


$ npm run dev

You will see that this command is starting both the server and the client:

Figure 6.2 – The concurrently tool running our server and client in parallel
140 Using Hooks and React Suspense for Data Fetching

Now that we have both the client and the server running, let’s move on to configuring a proxy to
avoid having to deal with cross-site requests.

Configuring a proxy
For security reasons, browsers have restrictions on making requests to different domains. This
restriction is called cross-origin resource sharing (CORS) and it prevents us from being able
to make requests to URLs with a different origin. The origin consists of the domain and port. In
our case, the domain is the same (localhost), but the port is different (5173 vs 5174). It would
be best to stay on the same domain and port to make requests from a frontend to a backend. So,
we need to configure a proxy that will forward requests from http://localhost:5173/api/ to
http://localhost:5174/.

Now, let’s get started configuring the proxy:

1. Edit vite.config.js and define a proxy config that will be bound to the /api path:
export default defineConfig({
plugins: [react()],
resolve: {
alias: [
{ find: '@', replacement: path.resolve(import.meta.dirname,
'src') },
],
},
server: {
proxy: {
'/api': {

2. Set the target to our backend server running at http://localhost:5174, and rewrite the
path to remove the /api from it before forwarding the request to our server:
target: 'http://localhost:5174',
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
})

This proxy configuration will link /api to our backend server.


Chapter 6 141

3. Quit the server and client if they are already running. Then, start them again with the
following command:
$ npm run dev

4. Now, access the API by opening http://localhost:5173/api/posts/1 in your browser.

As we can see, the post object is still being served properly, but now from the /api path through
the proxy defined in Vite!

Example code

The example code for this section can be found in the Chapter06/Chapter06_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Now, let’s move on to requesting resources using an Effect and a State/Reducer Hook.

Requesting resources using an Effect and a State/


Reducer Hook
Before learning how to use a library to implement requests using Hooks, we are going to implement
them manually, using an Effect Hook to trigger the request, and a State/Reducer Hook to store
the result.

Fetching posts from the server


We are now going to implement a way to fetch posts by using an Effect Hook. Then, we will store
it by extending the already defined Reducer Hook. Let’s get started:

1. Copy the Chapter06_1 folder to a new Chapter06_2 folder by executing the following
command:
$ cp -R Chapter06_1 Chapter06_2

2. Open the new Chapter06_2 folder in VS Code.


3. First, edit src/reducers.js and define a new FETCH_POSTS action, which is simply going
to return the new list of posts from the action:
export function postsReducer(state, action) {
switch (action.type) {
142 Using Hooks and React Suspense for Data Fetching

case 'CREATE_POST':
return [action.post, ...state]
case 'FETCH_POSTS':
return action.posts
default:
throw new Error('Unknown action type')
}
}

4. Now, edit src/App.jsx and import the useEffect function:


import { useState, useReducer, useEffect } from 'react'

5. Remove the featuredPosts and defaultPosts arrays.


6. Adjust the default value of the Reducer Hook to be an empty array:
export function App() {
const [posts, dispatch] = useReducer(postsReducer, [])
const [username, setUsername] = useState('')

7. Then, define an Effect Hook in the App component, as follows:


useEffect(() => {

8. Inside the Hook, we call fetch to make a request to the /api/posts endpoint:
fetch('/api/posts')

9. Parse the JSON response to get back a posts array:


.then((response) => response.json())

10. Now, dispatch the FETCH_POSTS action with the posts array returned from the server:
.then((posts) => dispatch({ type: 'FETCH_POSTS', posts }))

11. Pass an empty array to the Effect Hook dependency array to make sure it only triggers
when the component mounts:
}, [])
Chapter 6 143

12. We still need to separate featured posts from non-featured posts, so let’s use filter to
split up the array into two arrays, as follows:
const featuredPosts = posts.filter((post) => post.featured).
reverse()
const regularPosts = posts.filter((post) => !post.featured).
reverse()

We reverse the order here to make sure the newest posts are shown first. If we had a
createdAt property, we could use that instead to sort the posts properly.

13. Pass regularPosts instead of posts to the PostList component to ensure that featured
posts are not rendered twice:
<PostList posts={regularPosts} />

14. Start the client and server, as follows:


$ npm run dev

15. Now, go to http://localhost:5173/ in your browser.

As we can see, the app still works the same way as before! To verify that the posts are really com-
ing from our database, make a change to db.json, then refresh the page. You will see that the
change is visible in the app!

In development mode, you will see two GET requests. This is due to React rendering
components twice in strict mode to help you spot side effects that may happen when
re-rendering components (for example, forgetting to clean up timeouts/intervals).

In production mode, the component will only be rendered once and thus only one
GET request will be sent.

Quick detour: The async/await construct


Regular functions are defined as follows:
function doSomething() {
// ...
}
144 Using Hooks and React Suspense for Data Fetching

Regular anonymous functions are defined as follows:


() => {
// ...
}

Asynchronous functions are defined by adding the async keyword:


async function doSomething() {
// ...
}

Asynchronous anonymous functions are also possible:


async () => {
// ...
}

Within async functions, we can use the await keyword to wait for promises to resolve before
continuing. Instead of having to do the following:
function fetchPosts() {
return fetch('/api/posts')
.then((response) => response.json())
}

We can now write the same function like this using async/await:
async function fetchPosts() {
const response = await fetch('/api/posts')
const posts = await response.json()
return posts
}

In the previous section, we used the Promise API to work with the result of an asynchronous
function by using the .then() function inside the Effect Hook. Effect Hooks do not support
passing an async function to them to prevent race conditions. However, it would be possible to
define an async function inside the Hook and then call it immediately. So, we could also define
the Hook as follows:
useEffect(() => {
async function fetchPosts() {
Chapter 6 145

const response = await fetch('/api/posts')


const posts = await response.json()
dispatch({ type: 'FETCH_POSTS', posts })
}
void fetchPosts()
}, [])

The void operator shows that we did not just accidentally call an async function
without await. In this case, we want to call the asynchronous function but do not
care about waiting for it to finish.

As you can see, the async/await construct can make our code easier to read in some cases. You
can choose either pattern (then or async/await) depending on which one makes the code more
readable. However, it is best practice not to mix both in the same function. Of course, instead of
using dispatch and the Reducer Hook, we could also call setPosts here, if we had a State Hook
instead.

Now that posts are successfully loaded from the database, let’s implement a way to create posts
via the backend server.

Creating new posts on the server


For creating posts, we simply need to adjust the submit handler function to use fetch to perform
a POST request. Let’s get started doing that now:

1. Edit src/components/post/CreatePost.jsx and make the handleSubmit function async:


async function handleSubmit(e) {

2. Inside the function, after collecting the values, create a fetch request to /api/posts:
e.preventDefault()
const form = e.target
const title = form.elements.title.value
const content = form.elements.content.value
const newPost = { title, content, author: username, featured:
false }

const response = await fetch('/api/posts', {


146 Using Hooks and React Suspense for Data Fetching

3. Make sure this is a POST request and set the header so that our backend server knows we
will be sending a JSON object:
method: 'POST',
headers: { 'Content-Type': 'application/json' },

4. Now, we can pass our post object as the request body, by turning it into a JSON string:
body: JSON.stringify(newPost),
})

5. If the response wasn’t a success, throw an error:


if (!response.ok) {
throw new Error('Unable to create post')
}

6. Otherwise, we dispatch the CREATE_POST action to show the new post on the client-side
and reset the form:
dispatch({ type: 'CREATE_POST', post: newPost })
form.reset()
}

7. Create a new post using the frontend, and then check the server/db.json file.

As we can see, the new post was successfully inserted into the database:

Figure 6.3 – We successfully inserted a new post into the database


Chapter 6 147

Example code

The example code for this section can be found in the Chapter06/Chapter06_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Now that we have successfully implemented fetching and creating posts by directly using the
Fetch API and an Effect Hook, we can move on to learning about using a library to request re-
sources and make changes.

Using TanStack Query to request resources and


make changes
In the previous section, we used an Effect Hook to trigger the request, and a Reducer Hook to
update the state, using the result from the request. Instead of manually implementing requests
like this, we can instead use the TanStack Query library. This library not only allows us to easily
fetch resources, but also caches the result for us and provides ways to invalidate the state. Inval-
idation allows us to, for example, re-fetch posts from the server after creating a new post, instead
of having to manually dispatch an action.

Setting up the library


Before we can get started using it, we need to install and set up the library. TanStack Query is a
library used to manage state of server data. It consists of 3 parts:

• A Query Client, which manages the cache and invalidation.


• A Query Client Provider, which wraps your application to provide the query client to all
components.
• A collection of hooks, such as Query and Mutation Hooks. The Query Hook is used for
fetching and subscribing to data, while the Mutation Hook is used when you need to
modify data on the server.

Let’s get started setting up TanStack Query now:

1. Copy the Chapter06_2 folder to a new Chapter06_3 folder by executing the following
command:
$ cp -R Chapter06_2 Chapter06_3
148 Using Hooks and React Suspense for Data Fetching

2. Open the new Chapter06_3 folder in VS Code.


3. Install the TanStack Query library, as follows:
$ npm install --save-exact @tanstack/[email protected]

4. Additionally, install the ESLint plugin as a dev dependency:


$ npm install --save-dev --save-exact @tanstack/eslint-plugin-
[email protected]

5. Edit eslint.config.js and import the plugin there:


import pluginQuery from '@tanstack/eslint-plugin-query'

6. Then, add the plugin, as follows:


export default [
...pluginQuery.configs['flat/recommended'],
{ ignores: ['dist'] },

7. Now we can get started setting up TanStack Query itself. First, create a new src/api.js
file, which will contain the Query Client.
8. Edit src/api.js and import and create the Query Client:
import { QueryClient } from '@tanstack/react-query'

export const queryClient = new QueryClient()

We are creating a single instance of the query client here to ensure that all parts of our
app use the same query client (and thus the same cache).

9. Now, edit src/App.jsx, remove the useReducer, useEffect and postsReducer imports:
import { useState, useReducer, useEffect } from 'react'
import { postsReducer } from './reducers.js'

Replace them with imports of the queryClient and the QueryClientProvider:


import { QueryClientProvider } from '@tanstack/react-query'
import { queryClient } from './api.js'

10. Inside the App component, remove the Hooks related to fetching posts:
export function App() {
const [posts, dispatch] = useReducer(postsReducer, [])
Chapter 6 149

const [username, setUsername] = useState('')

useEffect(() => {
fetch('/api/posts')
.then((response) => response.json())
.then((posts) => dispatch({ type: 'FETCH_POSTS', posts }))
}, [])

const featuredPosts = posts.filter((post) => post.featured).


reverse()
const regularPosts = posts.filter((post) => !post.featured).
reverse()

11. Wrap the app with a QueryClientProvider, as follows:


return (
<QueryClientProvider client={queryClient}>
<UserContext.Provider value={[username, setUsername]}>

</UserContext.Provider>
</QueryClientProvider>
)
}

12. Remove the dispatch prop from the CreatePost component:


{username && <CreatePost />}

At this point in the chapter, the featuredPosts and regularPosts arrays are not
defined anymore, causing ESLint errors. Ignore these errors for now, we will be
fixing them soon.

Now we are ready to use TanStack Query!

Fetching posts using a Query Hook


Now that the library is set up, we can start using it. We will start by fetching posts using a Query
Hook. To do this, we are going to create a new PostFeed component, which will handle the fetching
logic, while keeping PostList as a UI component that renders a list of components. We are also
going to define a function that will fetch posts for us in the src/api.js file.
150 Using Hooks and React Suspense for Data Fetching

Let’s get started fetching posts using a Query Hook now:

1. Edit src/api.js and define a new function that accepts a featured prop and then fetches
posts for us:
export async function fetchPosts({ featured }) {

2. We make a call to the API, passing the featured prop as a query param. This will cause
json-server to filter the posts by their featured value for us:

const res = await fetch(`/api/posts?featured=${featured}`)

3. Parse the response as JSON and return it:


return await res.json()
}

4. Create a new src/components/post/PostFeed.jsx file.


5. Inside it, import the useQuery function, the PostList component and the fetchPosts
function:
import { useQuery } from '@tanstack/react-query'
import { fetchPosts } from '@/api.js'
import { PostList } from './PostList.jsx'

6. Then, define the component, which accepts a featured prop to toggle whether to render
featured components or regular components:
export function PostFeed({ featured = false }) {

7. Define a Query Hook, from which we use the data and isLoading values:
const { data, isLoading } = useQuery({

8. For each Query Hook, we need to define a queryKey. The queryKey is used to cache the
results of a query:
queryKey: ['posts', featured],

If we, for example, fetch with the same queryKey in another component, we will get the
cached result instead of making another request. React Query will always try to first get
the result from the cache (if it exists for a given queryKey), and if it does not exist in the
cache yet, it will make a request in the background for us and cache it.
Chapter 6 151

This is very useful as it allows us to fetch data further down in the component tree, directly
wherever we need it – avoiding prop drilling without compromising performance.

The queryKey can also be a source of bugs, when it is accidentally reused


for different requests. For example, we need to add the featured prop to
the queryKey here, otherwise only either featured or regular posts would
get fetched and returned twice. If you are getting weird results or outdated
data returned from Query Hooks, make sure to check your query keys and
ensure that you have a unique key for each request and that all parameters
passed to the query function are also added to the query key.

9. Next, we define the queryFn – a function that will be called when the query is executed.
In this case, we simply call the fetchPosts function with the featured prop:
queryFn: () => fetchPosts({ featured }),
})

10. If the Query Hook is in a loading state, we show a loading message:


if (isLoading) {
return <div>Loading posts...</div>
}

11. Similarly, if fetching the data did not work, we show an error message:
if (!data) {
return <div>Could not load posts!</div>
}

12. Otherwise, everything is fine, and we can render the PostList:


return <PostList posts={data} />
}

13. Edit src/App.jsx and remove the following PostList import:


import { PostList } from './components/post/PostList.jsx'

Replace it with an import of the PostFeed component:


import { PostFeed } from './components/post/PostFeed.jsx'
152 Using Hooks and React Suspense for Data Fetching

14. Inside the App component, replace the PostList components with PostFeed components,
as follows:
<hr />
<ThemeContext.Provider value={{ primaryColor: 'salmon'
}}>
<PostFeed featured />
</ThemeContext.Provider>
<PostFeed />

After implementing a way to fetch posts, let’s continue by using a Mutation Hook to create a
new post.

Creating posts using a Mutation Hook


Fetching posts required us to make a request to get data from the server when the component
mounts. However, for creating posts, we want to make a request to the server when the user
presses a button. To implement such a behavior, we need a Mutation Hook instead of a Query Hook.

Let’s get started implementing post creation using a Mutation Hook now:

1. Edit src/api.js and define a new function to create a post:


export async function createPost(post) {

2. Inside it, we make a POST request, similarly to what we did before:


const res = await fetch('/api/posts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(post),
})

3. If there was a problem with making the request, we throw an error:


if (!res.ok) {
throw new Error('Unable to create post')
}

4. Otherwise, if the request was successful, we return the result:


return await res.json()
}
Chapter 6 153

5. Edit src/components/post/CreatePost.jsx and import the useMutation and createPost


functions, as well as the queryClient:
import { useMutation } from '@tanstack/react-query'
import { createPost, queryClient } from '@/api.js'

6. Remove the dispatch prop from the component, as we will not need it anymore:
export function CreatePost({ dispatch }) {

7. Inside the component, add a new Mutation Hook, passing the createPost function as
the mutationFn:
const createPostMutation = useMutation({
mutationFn: createPost,

8. Add an onSuccess handler, which will invalidate all queries that start with the 'posts'
query key:
onSuccess: () => {
queryClient.invalidateQueries(['posts'])
},
})

When a query key is invalidated, all Query Hooks that use it are automatically re-executed
to fetch the new data, and the components are re-rendered to show it. In this case, we
invalidate all query keys that start with 'posts', so we will be invalidating both ['posts',
true] for the featured posts feed, and ['posts', false] for the regular posts feed.

9. Replace the whole handleSubmit function with the following:


async function handleSubmit(e) {
e.preventDefault()
const form = e.target
const title = form.elements.title.value
const content = form.elements.content.value
const newPost = { title, content, author: username, featured:
false }
154 Using Hooks and React Suspense for Data Fetching

10. Then, call the mutate function from the Mutation Hook, and reset the form after successfully
executing the mutation:
createPostMutation.mutate(newPost, {
onSuccess: () => form.reset()
})
}

11. Additionally, we can improve the user experience for the component now. For example, we
can use the isPending state to disable the submit button while the mutation is pending:
<input
type='submit'
value='Create'
disabled={createPostMutation.isPending}
/>

12. If there is an error during the mutation, we can also show the error message in red at the
end of the form:
{createPostMutation.isError && (
<div style={{ color: 'red' }}>
{createPostMutation.error.toString()}
</div>
)}
</form>

13. Try running the app, and you will see that it still works the same way as before, but now
using TanStack Query!

When inserting a new post, you may notice that it gets added to the end now.
Unfortunately, we cannot control how json-server inserts new posts into the array.
If you want to add this behavior again, I suggest adding a createdAt timestamp to
all posts and then use the _sort query param provided by the json-server tool to
sort the posts by this timestamp. Doing so is left as an exercise for you.

With the new structure of our app, we can further improve it a bit by using React Suspense and
Error Boundaries!
Chapter 6 155

Example code

The example code for this section can be found in the Chapter06/Chapter06_3
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Introducing React Suspense and Error Boundaries


In the previous section, we used the isLoading state from TanStack Query to show a loading
message when the posts are still being fetched. While this works fine, handling loading states
like that can get a bit messy. A better way to model loading states is to use React Suspense. React
Suspense is a special component that can display a fallback until its children have finished loading.
To use React Suspense, data fetching frameworks and libraries need to support it. Thankfully,
TanStack Query supports Suspense. Frameworks like Relay and Next.js support it as well.

Setting up a Suspense Boundary


To use Suspense, we need to define a Suspense Boundary with a fallback. If any child component
within the boundary is fetching data, the fallback will be rendered in place of the boundary,
replacing all the child components of it. When all data is fetched successfully, all child components
will be rendered. This allows us to write code that assumes data is always there, and to handle
the edge case further up in the tree.

Let’s now get started setting up a Suspense Boundary for the post feed:

1. Copy the Chapter06_3 folder to a new Chapter06_4 folder by executing the following
command:
$ cp -R Chapter06_3 Chapter06_4

2. Open the new Chapter06_4 folder in VS Code.


3. Edit src/App.jsx and import Suspense:
import { useState, Suspense } from 'react'
156 Using Hooks and React Suspense for Data Fetching

4. Adjust the App component to render the post feed within a Suspense Boundary, providing
a loading message as fallback:
<Suspense fallback={<strong>Loading posts...</strong>}>
<ThemeContext.Provider value={{ primaryColor: 'salmon'
}}>
<PostFeed featured />
</ThemeContext.Provider>
<PostFeed />
</Suspense>

5. Now, we need to adjust the PostFeed component to use the Suspense Query Hook instead.
Edit src/components/post/PostFeed.jsx and adjust the import as follows:
import { useSuspenseQuery } from '@tanstack/react-query'

6. Then, adjust the Hook, as follows:


export function PostFeed({ featured = false }) {
const { data } = useSuspenseQuery({

7. We can now remove the following code from the component:


if (isLoading) {
return <div>Loading posts...</div>
}

if (!data) {
return <div>Could not load posts!</div>
}

8. Start the app, as follows:


$ npm run dev

You will see that instead of getting two loading messages (one for featured posts and one for
regular posts), we now only see one loading message from the Suspense Boundary!
Chapter 6 157

Figure 6.4 – Loading messages before and after using React Suspense

The loading messages may be disappearing too quickly for you to see, because we are running
the backend locally, so there is no network delay. This is not a realistic scenario. In production,
we would have latency on every request that we make. We can use the DevTools to simulate a
slower network connection; let’s do that now:

1. In Google Chrome, open the inspector by right clicking on the website and pressing Inspect.
2. The inspector will open, inside it, go to the Network tab.
3. At the top of the Network tab, click on the No throttling dropdown.
4. Select the 3G preset. See the following screenshot for reference:

Figure 6.5 – Simulating slow networks in Google Chrome DevTools


158 Using Hooks and React Suspense for Data Fetching

5. Refresh the page. You will now see the app slowly loading the posts.
6. Do not forget to set it back to No throttling to avoid having to wait so long for requests.

Next, let’s move on to setting up an Error Boundary.

Setting up an Error Boundary


As we have learned, a Suspense Boundary can provide a fallback while components are fetching
data. However, you may have noticed that we also removed the error handling code. To provide
a fallback when an error happens in a child component, we can use an Error Boundary. Error
Boundaries work similar to Suspense Boundaries, but they react to error states rather than load-
ing states.

Let’s now get started setting up an Error Boundary:

1. First, install the react-error-boundary package:


$ npm install --save-exact [email protected]

2. Then, we create a component that will be rendered as a fallback when an error happens.
3. Create a new src/FetchErrorNotice.jsx file. Inside it, define a component that takes a
resetErrorBoundary function:

export function FetchErrorNotice({ resetErrorBoundary }) {

The resetErrorBoundary function can be used to reset the operation that caused the
error. In our case, it will retry the request to fetch posts.

4. Render an error message and a button that triggers the reset function:
return (
<div>
<strong>There was an error fetching data.</strong>
<br />
<button onClick={resetErrorBoundary}>Try again</button>
</div>
)
}
Chapter 6 159

5. Now, edit src/App.jsx and import the ErrorBoundary, QueryErrorResetBoundary and


FetchErrorNotice:

import { ErrorBoundary } from 'react-error-boundary'


import {
QueryClientProvider,
QueryErrorResetBoundary,
} from '@tanstack/react-query'
import { FetchErrorNotice } from './FetchErrorNotice.jsx'

6. Inside the App component, wrap the Suspense Boundary with an Error Boundary, which
is in turn wrapped by the QueryErrorResetBoundary, which provides the reset function
to retry queries:
<QueryErrorResetBoundary>
{({ reset }) => (
<ErrorBoundary
onReset={reset}
fallbackRender={FetchErrorNotice}
>
<Suspense fallback={<strong>Loading posts...</
strong>}>
<ThemeContext.Provider value={{ primaryColor:
'salmon' }}>
<PostFeed featured />
</ThemeContext.Provider>
<PostFeed />
</Suspense>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>

7. If it is currently running, stop the app.


8. Then, start only the client, as follows:
$ npm run dev:client
160 Using Hooks and React Suspense for Data Fetching

9. Open the app in your browser, you will see the loading message. Wait a while until the
request times out. Then, you will see the error message and the retry button:

Figure 6.6 – The Error Boundary being triggered by a request timeout

10. Now, without quitting the client, additionally start the server, as follows:
$ npm run dev:server

11. Press the Try again button. You will see the loading message again and then the list of
posts!

As we can see, Error Boundaries allow us to manage error states by displaying a fallback component
and functionality to reset the operation that caused the error.
Chapter 6 161

Example code

The example code for this section can be found in the Chapter06/Chapter06_4
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Summary
In this chapter, we first learned how to set up a simple API server from a JSON file. Then, we learned
how to fetch and create posts using Effect and State/Reducer Hooks. Next, we implemented the
same functionality using the TanStack Query library, which simplified our code and allowed us
to leverage its caching abilities. Finally, we learned how to deal with loading states using React
Suspense and error states using Error Boundaries.

In the next chapter, we are going to learn about form handling in depth, by using Form Actions
and Hooks, such as the useActionState Hook to handle form states and the useOptimistic Hook
to implement optimistic updates.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. How can we easily create a full REST API from a JSON file for mocking purposes?
2. What are the advantages of using a proxy to access our backend server?
3. Which combinations of Hooks can be used to implement data fetching?
4. Which advantages does TanStack Query have over our simple data fetching implemen-
tation?
5. Which Hook in TanStack Query is used for fetching data?
6. Which Hook in TanStack Query is used for making changes to the server?
7. What role does the query key play in the TanStack Query library?
8. What is a Suspense Boundary used for?
9. What are Error Boundaries used for?
162 Using Hooks and React Suspense for Data Fetching

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• Official documentation of the json-server tool: https://github.com/typicode/json-


server
• Official documentation of the concurrently tool: https://github.com/open-cli-tools/
concurrently
• Official documentation of TanStack Query for React: https://tanstack.com/query/
latest/docs/framework/react/overview
• More information on cross-origin resource sharing (CORS): https://developer.
mozilla.org/en-US/docs/Web/HTTP/CORS
• More information on the proxy setting in the Vite config: https://vite.dev/config/
server-options#server-proxy
• More information on React strict mode: https://react.dev/reference/react/
StrictMode
• Blog article about fetching data with React Hooks, without a library: https://www.
robinwieruch.de/react-hooks-fetch-data/
• More information about Suspense: https://react.dev/reference/react/Suspense
• More information about Error Boundaries: https://github.com/bvaughn/react-error-
boundary

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
7
Using Hooks for
Handling Forms
In the previous chapter, we learned how to use Hooks for Data Fetching and React Suspense for
showing a fallback while waiting on the data to finish loading.

In this chapter, we are going to learn how to use Hooks to handle forms and form state in React.
We have already implemented a form for the CreatePost component earlier. However, instead of
manually handling the form submission, we can use React Form Actions, which not only make
it easier to deal with form submission, but also allow us to use Hooks that access the form state.
Additionally, we are going to learn about the Optimistic Hook to implement optimistic updates,
that is, showing the preliminary result on the client-side before the server-side finishes processing.

The following topics will be covered in this chapter:

• Handling form submission with the Action State Hook


• Simulating blocking UI
• Avoiding blocking UI with the Transition Hook
• Using the Optimistic Hook to implement optimistic updates

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.
164 Using Hooks for Handling Forms

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter07

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Handling form submission with the Action State


Hook
React 19 introduced a new feature called Form Actions. As we have seen in the previous chapters,
performing data mutations in response to user actions is a common use case in web applications.
Often, these data mutations require making an API request and handling the response, which
means dealing with loading and error states. For example, when we made the CreatePost com-
ponent, we created a form that inserts a new post into the database upon submission. In this
case, React Query already helped us out a lot by simplifying loading and error states. However,
with React Form Actions there is now a native way to deal with these states, by using the Action
State Hook.
Chapter 7 165

Introducing the Action State Hook


The Action State Hook is defined as follows:
const [state, action, isPending] = useActionState(actionFn, initialState)

Let’s break it down a bit to get a better understanding of it. To define an Action State Hook, we
need to provide at least a function to it as an argument. This function will be called when the
form is submitted, and has the following signature:
function actionFn(currentState, formData) {

The action function gets the current state of the action as the first argument, and the form data
(as a FormData object) as the second argument. Anything returned from the action function will
be the new state of the Action State Hook.

The FormData API is a web standard used to represent form fields and their values.
It can be used to handle form submission and sent over the network, for example,
using fetch(). It is an iterable object (can be iterated over with a for … of loop) and
provides getter and setter functions to access the values. More information can be
found here: https://developer.mozilla.org/en-US/docs/Web/API/FormData.

Additionally, it is possible to provide an initialState for the Action State Hook.

The Hook then returns the current state of the action, the action itself (to be passed to the <form>
element), and the isPending state to check whether the action is currently pending (while the
actionFn is being executed).

Using the Action State Hook


Now, let’s get started refactoring the CreatePost component to use the Action State Hook:

1. Copy the Chapter06_4 folder to a new Chapter07_1 folder by executing the following
command:
$ cp -R Chapter06_4 Chapter07_1

2. Open the new Chapter07_1 folder in VS Code.


166 Using Hooks for Handling Forms

3. Edit src/components/post/CreatePost.jsx and import the useActionState function:


import { useContext, useActionState } from 'react'

4. Inside the CreatePost component, remove the whole handleSubmit function.


5. Replace it with the following Action State Hook:
const [error, submitAction, isPending] = useActionState(

In this case, we are going to use the state of the action to store an error state. If there was
an error, we will return it from the action function. Otherwise, we return nothing, so the
error state is undefined.

6. Define the action function, as follows:


async (currentState, formData) => {

In this case, we are not going to make use of the currentState passed to the function,
but we need to define it anyway as we need the second argument to get the formData.

7. Now, get the title and content from the form by making use of the FormData API:
const title = formData.get('title')
const content = formData.get('content')

The FormData API uses the name prop to identify input fields.

8. Next, create the post object and call the mutation:


const newPost = { title, content, author: username, featured:
false }
try {
await createPostMutation.mutateAsync(newPost)

Since we now have an async function, we can use the mutateAsync method from the
mutation to be able to await the response.

9. If there was an error, return it:


} catch (err) {
return err
}
},
)
Chapter 7 167

We do not need to manually reset the form anymore. When using form
actions, all uncontrolled fields in the form will be reset automatically once
the form action function completes successfully.

10. Adjust the <form> element to pass the action to it instead of an onSubmit handler:
return (
<form action={submitAction}>

11. Adjust the submit button and error message, as follows:


<input type='submit' value='Create' disabled={isPending} />
{error && <div style={{ color: 'red' }}>{error.toString()}</
div>}

12. Start the app, as follows:


$ npm run dev

13. On the blog app, log in and create a new post, you will see that it works the same way as
before, but we are now using the Action State Hook for form submission!

After learning how to handle form submission with the Action State Hook, let’s move on to learn-
ing about blocking UI.

Example code

The example code for this section can be found in the Chapter07/Chapter07_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Simulating blocking UI
Before we learn about the Transition Hook, let’s first introduce the problem that it attempts to
solve: blocking UI. When certain components are computationally intensive, rendering them
may cause the whole user interface to be unresponsive. This can result in a bad user experience,
as users cannot do anything else while the components are rendering.

We are now going to implement a comment section in our blog to simulate blocking UI.
168 Using Hooks for Handling Forms

Implementing a (purposefully slow) Comment component


We start by implementing a Comment component, which we make slow on purpose to simulate
a computationally expensive component.

Let’s get started implementing the Comment component:

1. Copy the Chapter07_1 folder to a new Chapter07_2 folder by executing the following
command:
$ cp -R Chapter07_1 Chapter07_2

2. Open the new Chapter07_2 folder in VS Code.


3. Create a new src/components/comment/ folder.
4. Create a new src/components/comment/Comment.jsx file. Inside it, define and export a
Comment component, which accepts content and author props:

export function Comment({ content, author }) {

5. In the component, we simulate a computationally intensive operation by delaying the


rendering by 1ms:
let startTime = performance.now()
while (performance.now() - startTime < 1) {
// do nothing for 1 ms
}

6. Now, render the comment, as follows:


return (
<div style={{ padding: '0.5em 0' }}>
<span>{content}</span>
<i> ~ {author}</i>
</div>
)
}

Implementing a CommentList component


Now, we are going to implement a CommentList component, which will render 1000 comments:

1. Create a new src/components/comment/CommentList.jsx file.


Chapter 7 169

2. Inside it, import the Comment component:


import { Comment } from './Comment.jsx'

3. Then, define and export a CommentList component, which generates 1000 comments:
export function CommentList() {
const comments = Array.from({ length: 1000 }, (_, i) => ({
id: i,
content: `Comment #${i}`,
author: 'test',
}))

4. Render the comments:


return (
<div>
{comments.map((comment) => (
<Comment {...comment} key={comment.id} />
))}
</div>
)
}

Implementing the CommentSection component


Lastly, we are going to implement a CommentSection component, which will allow us to show/
hide comments of a post by pressing a button.

Let’s get started implementing the CommentSection component:

1. Create a new src/components/comment/CommentSection.jsx file


2. Inside it, import the useState function from React and the CommentList component:
import { useState } from 'react'
import { CommentList } from './CommentList.jsx'

3. Next, define and export the CommentSection component, in which we define a State Hook
to toggle the comment list on and off:
export function CommentSection() {
const [showComments, setShowComments] = useState(false)
170 Using Hooks for Handling Forms

4. Then, define a handleClick function which will toggle the comment list:
function handleClick() {
setShowComments((prev) => !prev)
}

5. Render a button and conditionally render the CommentList component:


return (
<div>
<button onClick={handleClick}>
{showComments ? 'Hide' : 'Show'} comments
</button>
{showComments && <CommentList />}
</div>
)
}

6. Finally, edit src/components/post/Post.jsx and import the CommentSection compo-


nent there:
import { CommentSection } from '@/components/comment/CommentSection.
jsx'

7. Render it at the end of the post, as follows:


<i>
Written by <b>{author}</b>
</i>
<br />
<br />
<CommentSection />
</div>
)
}

Testing out the simulated blocking UI


Now we can test the comment section and see how it causes the UI to block. Follow these steps:

1. Run the project, as follows:


$ npm run dev
Chapter 7 171

2. Open the frontend in your browser by going to http://localhost:5173/


3. Click on one of the Show comments buttons.
4. You will see that after pressing the button, the whole UI is unresponsive. Try pressing one
of the other Show comments buttons – it does not work.

As we can see, rendering computationally expensive components can cause the whole UI to be-
come unresponsive. To fix this, we need to use Transitions – which we are going to learn about
in the next section.

Example code

The example code for this section can be found in the Chapter07/Chapter07_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Avoiding blocking UI with the Transition Hook


The Transition Hook lets you handle asynchronous operations by updating state without blocking
the UI. This is especially useful for rendering computationally expensive component trees, such
as rendering tabs and their (potentially complex) contents, or when making a client-side router.
The Transition Hook has the following signature:
const [isPending, startTransition] = useTransition()

The isPending state can be used to handle the loading state. The startTransition function
allows us to pass a function to start the transition. This function needs to be synchronous. While
the updates (for example, setting state) triggered inside the functions are being executed and
their effects on components evaluated, isPending will be set to true. This does not block the UI
in any way, so other components still behave normally while the transition is executing.

Using the Transition Hook


We are now going to use the Transition Hook to avoid blocking the UI when showing lots of
comments. Let’s get started:

1. Copy the Chapter07_2 folder to a new Chapter07_3 folder by executing the following
command:
$ cp -R Chapter07_2 Chapter07_3
172 Using Hooks for Handling Forms

2. Open the new Chapter07_3 folder in VS Code.


3. Edit src/components/comment/CommentSection.jsx and import the useTransition
function:
import { useState, useTransition } from 'react'

4. Define a Transition Hook, as follows:


export function CommentSection() {
const [showComments, setShowComments] = useState(false)
const [isPending, startTransition] = useTransition()

5. In the handleClick function, start a transition:


function handleClick() {
startTransition(() => {
setShowComments((prev) => !prev)
})
}

Transitions have specific use cases and certain limitations. For example, do
not use Transitions for handling controlled input state, because Transitions
are non-blocking, but we actually want the input state to update immediately.
Additionally, inside a Transition, all updates need to be called immediately.
While it is generally possible to await asynchronous functions in Transitions,
it is not possible to wait for an asynchronous request to finish before updating
the state inside a Transition. If you need to wait for an asynchronous request
before updating state, it is better to await it in the handler function and
then start the Transition afterward. For more information, check out the
troubleshooting guide on the React docs: https://react.dev/reference/
react/useTransition#troubleshooting

6. We can now disable the button while the transition is pending:


<button onClick={handleClick} disabled={isPending}>

Testing out the non-blocking Transition


Now we can test the comment section and see how it does not block the UI anymore. Follow
these steps:
Chapter 7 173

1. Run the project, as follows:


$ npm run dev

2. Open the frontend in your browser by going to http://localhost:5173/


3. Click on one of the Show comments buttons.
4. You will see that after pressing the button, the rest of the UI remains responsive. Try
pressing one of the other Show comments buttons – it works now and triggers another
Transition!

As we can see, by using Transitions we can keep the UI responsive while causing state updates
that render computationally expensive components!

Example code

The example code for this section can be found in the Chapter07/Chapter07_3
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Using the Optimistic Hook to implement optimistic


updates
There are two ways to handle updates/mutations:

• Show a loading state and disable certain actions while it is loading


• Do an optimistic update, which immediately shows the result of the action on the client,
while the mutation is still pending. Then, update the local state from the server state
when the mutation finishes.

Depending on your use case, one or the other option will be a better fit. Usually, optimistic up-
dates are great for fast-paced actions, such as a chat app. While a loading state without optimistic
updates is better for critical actions, such as making a bank transfer.

The Optimistic Hook has the following signature:


const [optimisticState, addOptimistic] = useOptimistic(state, updateFn)

As we can see, it accepts a state (usually, this is a server state) and an updateFn function to process
the update. Then, it returns an optimisticState and an addOptimistic function, which can be
used to optimistically add a new item to the state.
174 Using Hooks for Handling Forms

The updateFn accepts two arguments, the currentState and the optimisticValue passed to the
addOptimistic function. It then returns a new optimistic state.

Implementing optimistic comment creation


In our case, we are going to implement a way to create new comments using optimistic updates.
Let’s get started doing it:

1. Copy the Chapter07_3 folder to a new Chapter07_4 folder by executing the following
command:
$ cp -R Chapter07_3 Chapter07_4

2. Open the new Chapter07_4 folder in VS Code.


3. Create a new src/components/comment/CreateComment.jsx file and import the
useContext function and the UserContext:

import { useContext } from 'react'


import { UserContext } from '@/contexts/UserContext.js'

4. Define the CreateComment component, which accepts an addComment function:


export function CreateComment({ addComment }) {

5. Get the username of the currently logged in user from the context:
const [username] = useContext(UserContext)

6. Define a submitAction, which calls the addComment function:


async function submitAction(formData) {
const content = formData.get('content')
const comment = {
author: username,
content,
}
await addComment(comment)
}

7. Define a <form> and pass the submitAction to it:


return (
<form action={submitAction}>
<input type='text' name='content' />
Chapter 7 175

<i> ~ {username}</i>
<input type='submit' value='Create' />
</form>
)
}

As we can see, it is also possible to define Form Actions without using the
Action State Hook. However, then we only get a simple function to handle
form submission, without any of the form state handling functionality (such
as pending and error states).

8. Edit src/components/comment/CommentList.jsx and import the following:


import { useContext, useState, useOptimistic } from 'react'
import { UserContext } from '@/contexts/UserContext.js'
import { CreateComment } from './CreateComment.jsx'

9. Remove the following code:


const comments = Array.from({ length: 1000 }, (_, i) => ({
id: i,
content: `Comment #${i}`,
author: 'test',
}))

10. Then, define a Context Hook to get the username of the currently logged in user:
const [username] = useContext(UserContext)

11. Next, define a State Hook to store the comments:


const [comments, setComments] = useState([])

To keep this section short and to the point, we only focus on optimistic updates, feel free
to implement a way to store the comments in the database on your own here.

12. Now, define the Optimistic Hook:


const [optimisticComments, addOptimisticComment] = useOptimistic(
comments,
176 Using Hooks for Handling Forms

13. In the update function, we add the comment to the array with a sending prop set to true.
We are going to use this prop later to visually distinguish optimistically created comments
from real comments:
(state, comment) => [
...state,
{
...comment,
sending: true,
id: Date.now(),
},
],
)

We also defined a temporary ID for the optimistic comments here, which we can use later
for the key prop.

14. Now, define the addComment function, which first adds the comment optimistically, then
waits for a second, and then adds it to the “database”:
async function addComment(comment) {
addOptimisticComment(comment)
await new Promise((resolve) => setTimeout(resolve, 1000))
setComments((prev) => [...prev, comment])
}

15. Render the optimistic comments, as follows:


return (
<div>
{optimisticComments.map((comment) => (
<Comment {...comment} key={comment.id} />
))}
Chapter 7 177

16. If there are no comments yet, we can show an empty state:


{optimisticComments.length === 0 && <i>No comments</i>}

17. If the user is logged in, we allow them to create new comments:
{username && <CreateComment addComment={addComment} />}
</div>
)
}

18. Finally, edit src/components/comment/Comment.jsx and add the sending prop to it:
export function Comment({ content, author, sending }) {

19. Then, remove the following code from it:


let startTime = performance.now()
while (performance.now() - startTime < 1) {
// do nothing for 1 ms
}

20. Now, change the color depending on the sending prop, showing optimistically inserted
comments in a gray color:
return (
<div style={{ padding: '0.5em 0', color: sending ? 'gray' :
'black' }}>

21. Run the project, as follows:


$ npm run dev

22. Open the frontend in your browser by going to http://localhost:5173/


23. Log in using the form at the top, then press one of the Show comments buttons. It should
show the No comments message.
24. Type in a new comment and press Create.
178 Using Hooks for Handling Forms

You will see the comment being optimistically inserted in gray color at first, and then after a second
it will appear in black, signifying that the comment has been successfully stored in the “database”:

Figure 7.1 – Optimistically inserting a new comment

Example code

The example code for this section can be found in the Chapter07/Chapter07_4
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

As you can see, the Optimistic Hook is a great way to implement optimistic updates and keep
your app fast and responsive!

Summary
In this chapter, we first learned about handling form submission and states using Form Actions
and the Action State Hook. Then, we simulated a potential issue when dealing with rendering
computationally expensive components: Blocking the UI. Next, we solved this issue by intro-
ducing a Transition Hook to change the state in a non-blocking way, allowing the UI to remain
responsive while the computationally expensive components are rendering. Finally, we learned
about implementing optimistic updates to show results immediately while waiting for an asyn-
chronous operation to finish.

In the next chapter, we are going to learn how to use Hooks to implement client-side routing in
our blog application.
Chapter 7 179

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. Which feature can we use to handle form submission in React 19?


2. Which web standard is used for handling form data in React 19?
3. Which Hook is used to handle different form states?
4. What is a potential problem that can happen when rendering computationally expensive
components?
5. How do we avoid that problem?
6. What are limitations of Transitions?
7. Which Hook can we use to display state on the client before it finished persisting to the
server?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• FormData API: https://developer.mozilla.org/en-US/docs/Web/API/FormData


• Form submission with React: https://react.dev/reference/react-dom/components/
form
• The Action State Hook: https://react.dev/reference/react/useActionState
• The Transition Hook: https://react.dev/reference/react/useTransition
• The Optimistic Hook: https://react.dev/reference/react/useOptimistic
• More information on optimistic updates: https://dev.to/_jhohannes/why-your-
applications-need-optimistic-updates-3h62
180 Using Hooks for Handling Forms

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
8
Using Hooks for Routing
In the previous chapter, we learned how to use Hooks for handling form submission using the
Action State Hook, how to avoid blocking UI with the Transition Hook, and how to use the
Optimistic Hook to implement optimistic updates.

In this chapter, we are going to learn how to implement client-side routing in our blog app by
using React Router. First, we will learn how React Router works, and which features it offers.
Then, we will be creating a new route for viewing a single post and using the Param Hook to get
the post ID from the URL. Next, we will learn how to use the Link component to link to different
routes. Finally, we will learn how to programmatically implement navigation to redirect to a
newly created post using the Navigation Hook.

The following topics will be covered in this chapter:

• Introducing React Router


• Creating a new route and using the Param Hook
• Linking to routes using the Link component
• Programmatically redirecting using the Navigation Hook

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.
182 Using Hooks for Routing

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter08

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Introducing React Router


React Router started out as a simple, declarative routing library. It provides features to define and
manage different routes for our app, as well as navigating between them. Recently, React Router
can also be used as a React framework, providing ways to handle layouts and advanced server-side
rendering. However, since this book focuses on Hooks, we will focus on React Router as a library.

The library consists of three main components:

• The BrowserRouter component, which provides a context to use routing in


• The Routes component, which lets us define some routes and renders the component of
the currently active route
• The Route component, which lets us define a specific route and component to render
Chapter 8 183

Additionally, the library provides components to create links to certain routes (using the Link
and NavLink components), as well as Hooks to get parameters from the URL (Param Hook) and
to navigate (Navigation Hook).

Now, let’s get started setting up React Router and an index route (which will contain the home
page of our blog, with the feed of blog posts). The index route will be what is served on the main
URL of our server, also sometimes called the entry point or / route.

Setting up React Router


Follow these steps to get started setting up the React Router library and an index route:

1. Copy the Chapter07_4 folder to a new Chapter08_1 folder by executing the following
command:
$ cp -R Chapter07_4 Chapter08_1

2. Open the new Chapter08_1 folder in VS Code.


3. Open a Terminal and install the react-router library, as follows:
$ npm install --save-exact [email protected]

4. Create a new src/pages/ folder, in which we are going to put the various pages of our app.
5. Create a new src/pages/Home.jsx file to contain the home page of our blog app (which
will show the feed of posts that we already had before).
6. Inside it, import Suspense, the PostFeed, and the ThemeContext:
import { Suspense } from 'react'
import { PostFeed } from '@/components/post/PostFeed.jsx'
import { ThemeContext } from '@/contexts/ThemeContext.js'

7. Define and export a Home component, which shows a fallback while loading posts, then
shows the featured posts with a special color, and then the regular posts:
export function Home() {
return (
<Suspense fallback={<strong>Loading posts...</strong>}>
<ThemeContext.Provider value={{ primaryColor: 'salmon' }}>
<PostFeed featured />
</ThemeContext.Provider>
<PostFeed />
184 Using Hooks for Routing

</Suspense>
)
}

8. Edit src/App.jsx and remove the import of Suspense, as we are not going to need it any-
more:
import { useState, Suspense } from 'react'

9. Also, remove the import of the PostFeed component:


import { PostFeed } from './components/post/PostFeed.jsx'

10. Then, import the BrowserRouter, Routes, and Route from react-router:
import { BrowserRouter, Routes, Route } from 'react-router'

11. Also, import the Home page component:


import { Home } from './pages/Home.jsx'

12. Inside the App component, define the BrowserRouter, making sure it wraps all of the com-
ponents, so that we can make use of the Navigation Hook in the header components later:
export function App() {
const [username, setUsername] = useState('')
return (
<QueryClientProvider client={queryClient}>
<UserContext.Provider value={[username, setUsername]}>
<ThemeContext.Provider value={{ primaryColor: 'black' }}>
<BrowserRouter>
<div style={{ padding: 8 }}>
<UserBar />
<br />
{username && <CreatePost />}
<hr />

13. Inside the ErrorBoundary, replace the Suspense component and all its children. Instead,
render the Routes component, in which we can define routes for our app:
<QueryErrorResetBoundary>
{({ reset }) => (
Chapter 8 185

<ErrorBoundary
onReset={reset}
fallbackRender={FetchErrorNotice}
>
<Routes>

14. Define an index route which renders the Home page component:
<Route index element={<Home />} />
</Routes>
</ErrorBoundary>
)}
</QueryErrorResetBoundary>
</div>
</BrowserRouter>
</ThemeContext.Provider>
</UserContext.Provider>
</QueryClientProvider>
)
}

15. Run the app, as follows:


$ npm run dev

When opening the app in a browser, you will see that it looks exactly the same way as before, but
now the home page is rendered via React Router instead of being hardcoded!

Example code

The example code for this section can be found in the Chapter08/Chapter08_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Creating a new route and using the Param Hook


Now that we have React Router set up successfully, we can start creating a new route for viewing
a single post. This route will look as follows: /post/:id, with :id being a URL param containing
the id of a post to be viewed.
186 Using Hooks for Routing

A URL param is a parameter used in an URL to define dynamic content. For example, in the
/post/:id route, the /post/ part would be a static string, but the :id will be replaced with a
dynamic post ID. Let’s assume you have an URL that ends with /post/8, that would mean that
the route matches with the id param being set to 8.

Let’s get started setting up the page and route:

1. Copy the Chapter08_1 folder to a new Chapter08_2 folder by executing the following
command:
$ cp -R Chapter08_1 Chapter08_2

2. Open the new Chapter08_2 folder in VS Code.


3. Edit src/api.js and define a new function to fetch a single post:
export async function fetchPost({ id }) {
const res = await fetch(`/api/posts/${id}`)
return await res.json()
}

4. Edit src/components/post/Post.jsx and import the useSuspenseQuery and the


fetchPost functions:

import { useSuspenseQuery } from '@tanstack/react-query'


import { fetchPost } from '@/api.js'

5. Change the Post component to only accept an id prop:


export function Post({ id }) {

6. Inside the Post component, add a Suspense Query Hook to fetch the post and get all
data from it:
const { data } = useSuspenseQuery({
queryKey: ['post', id],
queryFn: async () => await fetchPost({ id }),
})
const { title, content, author } = data
Chapter 8 187

7. Create a new src/pages/ViewPost.jsx file. Inside it, import Suspense, the useParams
function from react-router, and the Post component:
import { Suspense } from 'react'
import { useParams } from 'react-router'
import { Post } from '@/components/post/Post.jsx'

8. Define and export the ViewPost page component:


export function ViewPost() {

9. Use a Params Hook to get the id from the URL params:


const { id } = useParams()

10. Use a Suspense boundary to provide a fallback while the post is fetching, then render the
Post component:

return (
<Suspense fallback={<strong>Loading post...</strong>}>
<Post id={id} />
</Suspense>
)
}

11. Edit src/App.jsx and import the ViewPost component:


import { ViewPost } from './pages/ViewPost.jsx'

12. Then, define a new route with the :id param for the ViewPost page:
<Routes>
<Route index element={<Home />} />
<Route path='post/:id' element={<ViewPost />}
/>
</Routes>

13. Run the app, as follows (leave it running throughout the rest of this chapter):
$ npm run dev
188 Using Hooks for Routing

It is now possible to manually go to the single post page by appending /post/:id to the URL in
a browser (for example /post/1):

Figure 8.1 – Viewing a single post on our newly defined route

However, it would be nice if we could visit this page by clicking on one of the posts in the main
post feed on the home page. Let’s implement this in the next section by using the Link component.

Linking to routes using the <Link> component


When dealing with links that the user can click to visit a different page, it is best and easiest to
use the Link component. This component will automatically create a simple link to a specific
page for us.
Chapter 8 189

Let’s get started using the Link component to provide a link to a single post:

1. Create a new src/components/post/PostListItem.jsx file, in which we are going to


define a simplified version of the Post component, which will be shown in the PostList
component. Inside it, import the useContext function, the ThemeContext and the Link
component from react-router:
import { useContext } from 'react'
import { ThemeContext } from '@/contexts/ThemeContext.js'
import { Link } from 'react-router'

2. Define and export the PostListItem component, which accepts the post id, title, and
author as props:

export function PostListItem({ id, title, author }) {

3. Define a Context Hook to get the theme:


const theme = useContext(ThemeContext)

4. Render the title, as we did before:


return (
<div>
<h3 style={{ color: theme.primaryColor }}>{title}</h3>

5. Now, render a Link component, which will go to /post/:id and show the ViewPost page:
<div>
<Link to={`/post/${id}`}>View Post &gt;</Link>
</div>

6. Then, show the author, but no contents, to avoid cluttering the feed:
<br />
<i>
Written by <b>{author}</b>
</i>
</div>
)
}
190 Using Hooks for Routing

7. Edit src/components/post/PostList.jsx and replace the Post import with an import


of the PostListItem component:
import { PostListItem } from './PostListItem.jsx'

8. Render the PostListItem component instead of the Post component:


{posts.map((post) => (
<Fragment key={post.id}>
<PostListItem {...post} />

Now it is possible to go from the home page to a single post:

Figure 8.2 – The Link component rendering a “View Post >” link to go to the single post page

But there is still no way to go back to the home page. Let’s implement this in the next section.

Defining a navigation bar using <NavLink>


If we want to add styling to the link, for example, to implement a navigation bar where we show
which page we are currently on, we can use the NavLink component.

Let’s use this component to implement a navigation bar with a link to go back to the home page:

1. Create a new src/components/NavBarLink.jsx file. Inside it, import the NavLink com-
ponent:
import { NavLink } from 'react-router'
Chapter 8 191

2. Define and export a component that accepts a to prop, which defines which route we
should link to, and a children prop to provide a text or component to put the link on:
export function NavBarLink({ children, to }) {
return (
<NavLink
to={to}

3. Then, define a style, in which we check if the link is active (when we are currently on the
page), and then render it in bold:
style={({ isActive }) => ({
fontWeight: isActive ? 'bold' : 'normal',
})}
>
{children}
</NavLink>
)
}

4. Edit src/App.jsx and import the NavBarLink component, as follows:


import { NavBarLink } from './components/NavBarLink.jsx'

5. In the header section of our blog app, before the UserBar, define a NavBarLink back to
the index/home page:
<BrowserRouter>
<div style={{ padding: 8 }}>
<NavBarLink to='/'>Home</NavBarLink>
<hr />
<UserBar />
192 Using Hooks for Routing

Now we have a way to go from the home page to a single post, and back to the home page again
to view other posts:

Figure 8.3 – Rendering a “Home” NavLink, which is currently active (bold)

Next, let’s look at a way to programmatically navigate to the single post page after creating a
new post.

Programmatically navigating using the Navigation


Hook
Whenever we want to programmatically navigate instead of having a link for the user to click, we
can use the Navigation Hook provided by React Router. The Navigation Hook provides a function
to navigate programmatically.
Chapter 8 193

Let’s get started using the Navigation Hook now:

1. Edit src/components/post/CreatePost.jsx and import the useNavigate function:


import { useNavigate } from 'react-router'

2. Define a Navigate Hook inside the CreatePost component:


export function CreatePost() {
const [username] = useContext(UserContext)
const navigate = useNavigate()

3. Inside the Action State Hook, get the result from the mutation and then redirect to the
ViewPost page of the newly created post:

const [error, submitAction, isPending] = useActionState(


async (currentState, formData) => {
const title = formData.get('title')
const content = formData.get('content')
const newPost = { title, content, author: username, featured:
false }
try {
const result = await createPostMutation.mutateAsync(newPost)
navigate(`/post/${result.id}`)
} catch (err) {
return err
}
},
)

4. Try creating a new post in the blog app, and you will see that you get redirected to the
page of the newly created post!

We have successfully implemented routing in our blog application! As an exercise, you could
now try to implement the login/signup and create post forms on separate pages. When doing so,
I would recommend refactoring the home page link into a new NavBar component with links to
the various pages.
194 Using Hooks for Routing

Example code

The example code for this section can be found in the Chapter08/Chapter08_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Summary
In this chapter, we first learned how the React Router library works and which components it
consists of. Then, we set up the library and an index route for the home page of our blog (showing a
feed of blog posts). Next, we defined a new route to show a single post on a separate page and used
the Params Hook to get the id value from the URL. Then, we learned how to navigate to this new
route and back to the home page using Link and NavLink components. Finally, we learned how
to programmatically navigate after a post was successfully created by using the Navigation Hook.

In the next chapter, we are going to learn about advanced built-in Hooks that React provides.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. Which components does the React Router library consist of?


2. How do we define a new route with the React Router library?
3. How can we read dynamic values (params) in URLs?
4. What are ways to define links with React Router and how do they differ?
5. Which Hook is used to programmatically navigate with React Router?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following link:

• Official website of React Router: https://reactrouter.com/


Chapter 8 195

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
9
Advanced Hooks Provided
by React
In the previous chapter, we learned about implementing routes using React Router. Then, we
learned about using the Params Hook for dynamic routes. Next, we learned about using the Link
component to provide links to different routes. Finally, we learned how to programmatically
redirect using the Navigation Hook.

In this chapter, we are going to learn about the various built-in Hooks provided by React. We
will start by giving an overview of the built-in React Hooks, and then learn about various utility
Hooks. Next, we will learn how to use Hooks to optimize the performance of your app. Finally,
we will learn about advanced Effect Hooks.

By the end of this chapter, you will have a full overview of all the built-in Hooks that React provides.

The following topics will be covered in this chapter:

• Overview of built-in React Hooks


• Using utility Hooks
• Using Hooks for performance optimizations
• Using Hooks for advanced effects

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check the official website: https://nodejs.org/.
198 Advanced Hooks Provided by React

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to the official website: https://code.visualstudio.com.

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• VS Code v1.97.2

While installing a newer version should not be an issue, please note that certain steps might work
differently on a newer version. If you are having an issue with the code and steps provided in this
book, please try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter09.

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Overview of built-in React Hooks


React provides certain built-in Hooks. We have already learned about the basic Hooks that React
provides:

• useState in Chapter 2, Using the State Hook


• useEffect in Chapter 4, Using the Reducer and Effect Hooks
• useContext in Chapter 5, Implementing React Contexts

Additionally, React provides more advanced Hooks, which can be very useful in certain use cases.
We already covered the following advanced Hooks:

• useReducer in Chapter 4, Using the Reducer and Effect Hooks


• useActionState in Chapter 7, Using Hooks for Handling Forms
• useFormStatus (not covered yet, but similar to useActionState)
• useOptimistic in Chapter 7, Using Hooks for Handling Forms
• useTransition in Chapter 7, Using Hooks for Handling Forms
Chapter 9 199

However, there are still more advanced Hooks that React provides:
• useRef
• useImperativeHandle
• useId
• useSyncExternalStore
• useDebugValue
• useDeferredValue
• useMemo
• useCallback
• useLayoutEffect
• useInsertionEffect

First, let’s recap and summarize the Hooks we have already learned about. Then, we are going to
briefly cover all these advanced Hooks that React provides and learn why and how to use them.

useState
The State Hook returns a value that will persist across re-renders and a function to update it. A
value for initialState can be passed to it as an argument:
const [state, setState] = useState(initialState)

Calling setState updates the value and re-renders the component with the updated value. If the
value does not change, React will not re-render the component.

A function can also be passed to the setState function, with the first argument being the current
value. For example, consider the following code:
setState(val => val + 1)

Additionally, a function can be passed to the first argument of the Hook if the initial state is the
result of a complex computation. In that case, the function will only be called once during ini-
tialization of the Hook:
const [state, setState] = useState(() => {
return computeInitialState()
})

The State Hook is the most ubiquitous Hook provided by React.

We used this Hook in Chapter 2, Using the State Hook.


200 Advanced Hooks Provided by React

useEffect
The Effect Hook accepts a function that contains code with side effects, such as timers and sub-
scriptions. The function passed to the Hook will run after the render is done and the component
is on the screen:
useEffect(() => {
// do something
})

A cleanup function can be returned from the Hook, which will be called when the component
unmounts and is used, for example, to clean up timers or subscriptions:
useEffect(() => {
const interval = setInterval(() => {}, 100)
return () => {
clearInterval(interval)
}
})

The cleanup function will also be called if the component renders multiple times before the effect
is activated again.

To avoid triggering the effect on every re-render, we can specify an array of values as the second
argument to the Hook. When any of these values change, the effect will get triggered again:
useEffect(() => {
// do something when state changes
}, [state])

This array passed as the second argument is called the dependency array of the effect. If you want
the effect to only trigger during mounting and the cleanup during unmounting, you can pass an
empty array as the second argument.

We used this Hook in Chapter 4, Using the Reducer and Effect Hooks.

useContext
The Context Hook accepts a context object and returns the current value for the context. When
the context provider updates its value, the Hook will trigger a re-render with the latest value:
const value = useContext(NameOfTheContext)
Chapter 9 201

We used this Hook in Chapter 5, Implementing React Contexts.

useReducer
The Reducer Hook is an advanced version of the useState Hook. It accepts a reducer as the first
argument, which is a function with two arguments: state and action. The reducer function then
returns the updated state computed from the current state and the action. If the reducer returns
the same value as the previous state, React will not re-render components or trigger effects:
const [state, dispatch] = useReducer(reducer, initialState, initFn)

We should use the useReducer Hook instead of the useState Hook when dealing with complex
state changes. It is also easier to deal with a global state because we can simply pass down the
dispatch function instead of multiple setter functions.

The dispatch function is stable and will not change on re-renders, so it is safe to
omit it from useEffect or the useCallback dependency arrays.

We can specify the initial state by setting the initialState value or specifying an initFn func-
tion as the third argument. Specifying such a function makes sense when computing the initial
state takes a long time or when we want to reuse the function to reset the state through an action.

We used this Hook in Chapter 4, Using the Reducer and Effect Hooks.

useActionState
The Action State Hook is defined as follows:
const [state, action, isPending] = useActionState(actionFn, initialState)

To define an Action State Hook, we need to provide an action function as the first argument,
which has the following signature:
function actionFn(currentState, formData) {

We then need to pass the action prop to a <form> element. When this form gets submitted, the
action function is called with the current state of the Hook and the FormData submitted inside
the form.
202 Advanced Hooks Provided by React

Additionally, it is possible to provide an initialState for the Hook and use the isPending value
to show a loading state while the action is being processed.

We used this Hook in Chapter 7, Using Hooks for Handling Forms.

useFormStatus
The Form Status Hook is defined as follows:
const { pending, data, method, action } = useFormStatus()

It is used in cases where the form submission is not handled by us. For example, if we have a
backend that handles the form submission for us, or if we are using a server action for the form
state (relevant when carrying out full-stack React development).

It returns a status object with the following properties:

• pending: Is set to true if the parent <form> is currently being submitted


• data: Contains the FormData that is being submitted by the parent form
• method: Set to either 'get' or 'post', depending on which method was defined in the
parent <form>.
• action: If an action function was passed to the parent <form>, this will contain a reference
to it. Otherwise, it will be null.

For example, it can be used to implement a submit button that is disabled while the form is
submitting to the server-side:
import { useFormStatus } from 'react-dom'

function SubmitButton() {
const { pending } = useFormStatus()
return <button disabled={pending}>Submit</button>
}

function ExampleForm() {
return (
<form>
<SubmitButton />
</form>
)
}
Chapter 9 203

The Form Status Hook can only be used in components rendered inside a <form>. Unlike other
Hooks, at the time of writing, this is the only Hook that is exported from react-dom, and not react.

useOptimistic
The Optimistic Hook has the following signature:
const [optimisticState, addOptimistic] = useOptimistic(state, updateFn)

It can be used to optimistically update a state while we are waiting for the remote state from
the server to finish updating. It accepts a state (usually from an API request, such as a Query
Hook) and an update function. The Hook then returns an optimistic state and a function to add
an optimistic state.

For example, the Optimistic Hook can be used to insert a new object into an array while we are
waiting for the server to finish adding it. In that case, the update function would look as follows:
function updateFn(state, newObject) {
return state.concat(
{ ...newObject, pending: true }
)
}

This update function optimistically inserts a new object but adds a pending: true flag to it, so
that we can later render pending objects in a different way (for example, slightly grayed out).

We used this Hook in Chapter 7, Using Hooks for Handling Forms.

useTransition
The Transition Hook lets you handle asynchronous operations by updating the state without
blocking the UI. This is especially useful for rendering computationally expensive component trees,
such as rendering tabs and their (potentially complex) contents, or when making a client-side
router. The Transition Hook has the following signature:
const [isPending, startTransition] = useTransition()

The isPending state can be used to handle the loading state. The startTransition function
allows us to pass a function to start the transition. This function needs to be synchronous. While
the updates (for example, setting state) triggered inside the functions are being executed and
their effects on components evaluated, isPending will be set to true.
204 Advanced Hooks Provided by React

This does not block the UI, so other components still behave normally while the transition is
executing.

We used this Hook in Chapter 7, Using Hooks for Handling Forms.

After recapping the built-in Hooks we have already learned about, let’s now move on to learning
about other advanced built-in Hooks, which we have not used, starting with built-in utility Hooks
that React provides.

Using utility Hooks


We start by learning about utility Hooks. These are Hooks that allow us to model certain use cases
or help us when developing our own Hooks, as in Chapter 12, Building Your Own Hooks.

We are now going to set up a demo page in our blog app to be able to test out the various utility
Hooks.

Let’s get started setting up the demo page to test out these Hooks:

1. Copy the Chapter08_2 folder to a new Chapter09_1 folder by executing the following
command:
$ cp -R Chapter08_2 Chapter09_1

2. Open the new Chapter09_1 folder in VS Code.


3. Create a new src/components/demo/ folder. This is where we will put our demo compo-
nents later to try out the various Hooks we will learn about.
4. Create a new src/pages/Demo.jsx file, with the following content:
export function Demo() {
return <h1>Demo Page</h1>
}

5. Edit src/App.jsx and import the Demo page:


import { Demo } from './pages/Demo.jsx'

6. Then, define a new NavBarLink for it:


<BrowserRouter>
<div style={{ padding: 8 }}>
<NavBarLink to='/'>Home</NavBarLink>
{' | '}
<NavBarLink to='/demo'>Demo</NavBarLink>
Chapter 9 205

7. Finally, define a route for it:


<Routes>
<Route index element={<Home />} />
<Route path='post/:id' element={<ViewPost />}
/>
<Route path='demo' element={<Demo />} />

8. Start the dev server and keep it running throughout the chapter, as follows:
$ npm run dev

9. Click the Demo link in the nav bar to open the demo page.

Now that we have a demo page, we can get started learning about the other built-in advanced
Hooks that React provides!

Figure 9.1 – The Demo page in our blog app

useRef
The Ref Hook returns a ref object that can be assigned to a component or element via the ref prop:
const refContainer = useRef(initialValue)
206 Advanced Hooks Provided by React

After assigning the ref object to an element or component, the ref object can be accessed via
refContainer.current. If initialValue is set, refContainer.current will be set to this value
before assignment.

ref objects can be used for various use cases, but the two main ones are:

• Getting a reference to an element to access it on the Document Object Model (DOM)


• Keeping mutable values around that should not be affected by the React lifecycle (e.g.,
not triggering a re-render when the value is mutated)

Auto-focusing an input field using a Ref Hook


We can use a Ref Hook to get a reference to an input field element and then access its focus()
function via the DOM to implement an input field that automatically gets focused when it renders.
While there is also an autofocus attribute that can be provided to elements via HTML, sometimes
it’s necessary to do it programmatically – for example, if we want to focus a field after the user
has finished doing something else.

Let’s now get started implementing an auto-focusing input field using a Ref Hook:

1. Create a new src/components/demo/useRef/ folder.


2. Create a new src/components/demo/useRef/AutoFocus.jsx file. Inside it, import useRef
and useEffect:
import { useRef, useEffect } from 'react'

3. Then, define the component and a Ref Hook:


export function AutoFocus() {
const inputRef = useRef(null)

4. Next, define an Effect Hook that is called on render and causes the input field to be focused:
useEffect(() => inputRef.current.focus(), [])

5. Render the input field and pass the Ref to it:


return (
<div>
<h3>AutoFocus</h3>
<input ref={inputRef} type='text' />
</div>
)
}
Chapter 9 207

6. Now, edit src/pages/Demo.jsx and import the AutoFocus component:


import { AutoFocus } from '@/components/demo/useRef/AutoFocus.jsx'

7. Render it on the Demo page by adjusting the component as follows:


export function Demo() {
return (
<div>
<h1>Demo Page</h1>
<h2>useRef</h2>
<AutoFocus />
</div>
)
}

Refresh the page; you should see the input field getting focused automatically.

Figure 9.2 – The input field is automatically being focused

Changing state within a ref


It is important to note that mutating the current value of a ref does not cause a re-render. If this is
needed, we can use a ref callback function instead. This function will be called when the element
is loaded. We can use this, for example, to get the initial size of an element in the DOM. We can
then set the state of a State Hook inside this callback function to trigger a re-render.

If we do not just want to get the initial width of a component but also the current
width (even when the component is resized later), we need to use a Layout Effect
Hook. We are going to cover this use case later in this chapter, in the Using Hooks
for advanced effects section.
208 Advanced Hooks Provided by React

Let’s try out callback functions in refs to get the initial width of a component now:

1. Create a new src/components/demo/useRef/InitialWidthMeasure.jsx file. Inside it,


import the useState function:
import { useState } from 'react'

2. Then, define the component and a State Hook to store the width of the component:
export function InitialWidthMeasure() {
const [width, setWidth] = useState(0)

3. Now, define a callback function for the ref, which accepts the DOM node as an argument:
function measureRef(node) {

4. Check whether we successfully got a reference to the DOM node, then use the DOM API
to get the current width of the element:
if (node !== null) {
setWidth(node.getBoundingClientRect().width)
}
}

5. Render the component and add the callback function via the ref prop:
return (
<div>
<h3>InitialWidthMeasure</h3>
<div ref={measureRef}>I was initially {Math.round(width)}px
wide</div>
</div>
)
}

6. Edit src/pages/Demo.jsx and import the InitialWidthMeasure component there:


import { InitialWidthMeasure } from '@/components/demo/useRef/
InitialWidthMeasure.jsx'

7. Finally, render the component on the Demo page:


export function Demo() {
return (
<div>
Chapter 9 209

<h1>Demo Page</h1>
<h2>useRef</h2>
<AutoFocus />
<InitialWidthMeasure />

The Demo page should now automatically refresh in your browser and show the component and
its initial width!

Figure 9.3 – The component displaying its initial width

Using refs to persist mutable values across re-renders


Refs can be used to access the DOM, but also to keep mutable values around even when the com-
ponent is re-rendered, such as storing references to intervals.

Let’s try that out by implementing a timer that counts the seconds passed:

1. Create a new src/components/demo/useRef/Timer.jsx file. Inside it, import the useRef,


useState, and useEffect functions:

import { useRef, useState, useEffect } from 'react'

2. Then, define and export a Timer component:


export function Timer() {

3. Inside it, define a Ref Hook to store the interval and a State Hook to store the current count:
const intervalRef = useRef(null)
const [seconds, setSeconds] = useState(0)

4. Define a function that will increase the count:


function increaseSeconds() {
setSeconds((prevSeconds) => prevSeconds + 1)
}

5. Now, define an Effect Hook that defines a new interval and stores it in the ref:
useEffect(() => {
intervalRef.current = setInterval(increaseSeconds, 1000)
210 Advanced Hooks Provided by React

6. We can now use this ref to clear the interval when the component unmounts:
return () => clearInterval(intervalRef.current)
}, [])

7. Render the current count of the timer:


return (
<div>
<h3>Timer</h3>
{seconds} seconds

8. Lastly, render a button to cancel the timer:


<button type='button' onClick={() =>
clearInterval(intervalRef.current)}>
Cancel
</button>
</div>
)
}

If we did not need to access the interval ID outside of the Effect Hook, we
could simply use a const inside the effect instead of defining a Ref. While
we could use a State Hook to store the interval ID, this would cause the
component to re-render. As we can see, Refs are ideal for storing values that
need to change but are not used for rendering.

9. Edit src/pages/Demo.jsx and import the Timer component there:


import { Timer } from '@/components/demo/useRef/Timer.jsx'

10. Finally, render the component on the Demo page:


export function Demo() {
return (
<div>
<h1>Demo Page</h1>
<h2>useRef</h2>
<AutoFocus />
<InitialWidthMeasure />
<Timer />
Chapter 9 211

The Demo page should now automatically refresh in your browser and show the component
counting seconds! Press the Cancel button to stop the timer.

Using refs as in the previous example makes them similar to instance variables in
classes, such as this.intervalRef.

The following screenshot shows what the Timer component looks like on the Demo page after
42 seconds have elapsed since opening the page:

Figure 9.4 – The Timer component showing seconds elapsed since being rendered

Passing refs as props


Sometimes, you may want to get a ref to an input field inside another component (for example,
when dealing with custom input fields). In the past, this required the forwardRef helper. However,
since React 19, we can simply pass refs as props.
Let’s try it out:

1. Create a new src/components/demo/useRef/CustomInput.jsx file.


2. Inside it, define the following custom input component, accepting a ref as a prop:
export function CustomInput({ ref }) {

3. We can use the ref as usual now:


return <input ref={ref} type='text' />
}

4. Now, edit the src/components/demo/useRef/AutoFocus.jsx file and import the


CustomInput component:
import { CustomInput } from './CustomInput.jsx'

5. Replace the input field with our CustomInput component, and pass the ref to it:
return (
<div>
<h3>AutoFocus</h3>
<CustomInput ref={inputRef} />
212 Advanced Hooks Provided by React

Refresh the Demo page and you will see that the input field is still getting autofocused!

Creating ref contents only once


If you have a complex algorithm that needs initialization, for example, a pathfinding algorithm,
it is possible to store a reference to it in a ref to avoid creating it on every render. This should be
done as follows:
function Map() {
const pathfinderRef = useRef(null)
if (pathfinderRef.current === null) {
pathfinderRef.current = createPathfinder()
}
}

Generally, writing or reading ref.current in a render like that is not allowed in


React. However, in this case, it is fine because the condition makes it only execute
once when the component is initialized.

While React always only saves the initial value of a ref once, directly calling the function inside
the Ref Hook, such as useRef(createPathfinder()), would unnecessarily execute the expensive
function on every render.

As we have seen, there are many use cases for refs. Generally, refs are useful to do the following:

• Store information between re-renders, because – unlike regular variables – refs do not
reset when re-rendering
• Change information without triggering a re-render, because – unlike State Hooks – refs
do not trigger a re-render
• Store information local to each copy of the component, because – unlike regular vari-
ables outside of components – refs do not have a shared value between different instances
of a component

useImperativeHandle
The Imperative Handle Hook can be used to customize instance values that are exposed to other
components when pointing a ref to it. Doing this should be avoided as much as possible, however,
as it tightly couples components together, which harms reusability.
Chapter 9 213

The useImperativeHandle function has the following signature:


useImperativeHandle(ref, createHandle, [dependencies])

We can use this Hook, for example, to expose a special focus function that not only focuses an
input field but also highlights it. Other components can then trigger this function via a ref to
the component. Let’s try it out now:

1. Create a new src/components/demo/useImperativeHandle/ folder.


2. Inside it, create a new src/components/demo/useImperativeHandle/
HighlightFocusInput.jsx file.
3. Import the useImperativeHandle, useRef, and useState functions:
import { useImperativeHandle, useRef, useState } from 'react'

4. Then, define a component that accepts a ref:


export function HighlightFocusInput({ ref }) {

5. Inside the component, we define a Ref Hook for the input field and a State Hook to store
the highlight state:
const inputRef = useRef(null)
const [highlight, setHighlight] = useState(false)

6. Now, define an Imperative Handle Hook, pass the ref to it, and pass a function that re-
turns an object:
useImperativeHandle(ref, () => ({

7. This object contains a focus function, which will trigger the focus function on the input
element, and then set the highlight state to true for a second:
focus: () => {
inputRef.current.focus()
setHighlight(true)
setTimeout(() => setHighlight(false), 1000)
},
}))
214 Advanced Hooks Provided by React

8. Lastly, render an input field and pass inputRef to it:


return (
<input
ref={inputRef}
type='text'

9. If highlight is set to true, render the background in a yellow color:


style={{ backgroundColor: highlight ? 'yellow' : undefined }}
/>
)
}

10. Create a new src/components/demo/useImperativeHandle/HighlightFocus.jsx file.


11. Inside it, import the useRef function and the HighlightFocusInput component:
import { useRef } from 'react'
import { HighlightFocusInput } from './HighlightFocusInput.jsx'

12. Now, define a component and a Ref Hook:


export function HighlightFocus() {
const inputRef = useRef(null)

13. Render a button to trigger the focus function from the component, and then render the
component and pass the ref to it:
return (
<div>
<h3>HighlightFocus</h3>
<button onClick={() => inputRef.current.focus()}>focus it</
button>
<HighlightFocusInput ref={inputRef} />
</div>
)
}

14. Edit src/pages/Demo.jsx and import the HighlightFocus component:


import { HighlightFocus } from '@/components/demo/
useImperativeHandle/HighlightFocus.jsx'
Chapter 9 215

15. Render the HighlightFocus component, as follows:


<InitialWidthMeasure />
<Timer />
<h2>useImperativeHandle</h2>
<HighlightFocus />
</div>
)
}

Now, go to the Demo page and click the focus it button. You will see the input field getting fo-
cused and highlighted!

Figure 9.5 – The component focusing and highlighting the input field

As we can see, by using refs and the Imperative Handle Hook, we can access functions from other
components. However, this should be used with caution, as it tightly couples components, which
can become a problem when our app grows and we want to reuse the components somewhere else.

useId
The Id Hook is used to generate unique IDs. This can be useful, for example, to provide IDs for
elements for accessibility attributes (such as aria-labelledby or aria-describedby). The Id
Hook has the following signature:
const uniqueId = useId()

Let’s try it out now by providing a label for a checkbox field:

1. Create a new src/components/demo/useId/ folder.


2. Inside it, create a new src/components/demo/useId/AriaInput.jsx file.
3. Import the useId function:
import { useId } from 'react'
216 Advanced Hooks Provided by React

4. Then, define a component that uses the Id Hook to generate an ID for a label:
export function AriaInput() {
const inputId = useId()

5. Render a label with the htmlFor tag pointing to inputId:


return (
<div>
<h3>AriaInput</h3>
<label htmlFor={inputId}>

6. Render a checkbox field with the generated ID:


<input id={inputId} type='checkbox' /> I agree to the Terms
and Conditions.
</label>
</div>
)
}

7. Edit src/pages/Demo.jsx and import the AriaInput component:


import { AriaInput } from '@/components/demo/useId/AriaInput.jsx'

8. Then, render the component on the Demo page:


<Timer />
<h2>useImperativeHandle</h2>
<HighlightFocus />
<h2>useId</h2>
<AriaInput />
</div>
)
}
Chapter 9 217

Now, go to the Demo page and open the inspector on the input field; you will see that React
generated an :r0: ID for us:

Figure 9.6 – A unique ID being generated automatically by React

In React 19.1, the format of IDs was changed from :r123: to «r123», to ensure that
they are valid CSS selectors.

In addition to connecting the label to the input field for screen readers (improving accessibility),
using a <label> element has the added advantage of allowing us to click on the label to check/
uncheck the checkbox.
218 Advanced Hooks Provided by React

While we could manually set an ID in this case, for example, tos-check, the ID needs to be unique
across the whole page. So, if we wanted to render the same input field again, the ID would already
be invalid, as it is reused. To prevent this issue, always prefer to use the Id Hook for these cases so
that the component can be reused multiple times on the same page. If you have multiple input
fields in a single component, it is best practice to only use a single Id Hook in the component
and then extend IDs by adding tags to the generated ID – for example: `${id}-tos-check` and
`${id}-username`.

useSyncExternalStore
The Sync External Store Hook is used to subscribe to external stores, such as state management
libraries or browser APIs. Its signature looks as follows:
const snapshot = useSyncExternalStore(subscribe, getSnapshot,
getServerSnapshot)

As we can see, the Sync External Store Hook accepts three parameters and returns the current snap-
shot of the store, which can be used to render information from it. The parameters are as follows:

• The first parameter, subscribe, is a function that takes a callback function as an ar-
gument and subscribes it to the store. When the store changes, the provided function
should be called. The subscribe function should also return a function that cleans up
the subscription.
• The second parameter, getSnapshot, is a function that returns a snapshot of the current
state of the data in the store. If the store changes (the callback function in the subscribe
function is called), React calls the getSnapshot function and checks whether the returned
value is different. If it is, the component will re-render.
• The third parameter, getServerSnapshot, is an optional function that returns the initial
snapshot of the current state of the data in the store. This function is only called during
server rendering and used to hydrate server-rendered content on the client.

In most cases, you will be better off using State and Reducer Hooks instead of this Hook. Most
state management libraries also provide their own Hooks. This Hook is mostly useful when in-
tegrating with existing non-React code, but it can also be useful when interacting with certain
browser APIs, which is what we are going to try out now.
Chapter 9 219

Let’s implement an indicator that checks whether a network connection is available or not, by
subscribing to a browser API via the Sync External Store Hook:

1. Create a new src/components/demo/useSyncExternalStore/ folder.


2. Inside it, create a new src/components/demo/useSyncExternalStore/OnlineIndicator.
jsx file.
3. Import the useSyncExternalStore function:
import { useSyncExternalStore } from 'react'

4. Define a subscribe function that accepts a callback function as an argument:


function subscribe(callback) {

5. Add event listeners for the online and offline events from the browser:
window.addEventListener('online', callback)
window.addEventListener('offline', callback)

6. Return a function that will clean up those event listeners:


return () => {
window.removeEventListener('online', callback)
window.removeEventListener('offline', callback)
}
}

7. Now, define a getSnapshot function that returns the current online status:
function getSnapshot() {
return navigator.onLine
}

8. Then, define the component and a Sync External Store Hook:


export function OnlineIndicator() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot)

9. Define the status based on the result from the browser API:
const status = isOnline ? 'online' : 'offline'
220 Advanced Hooks Provided by React

10. Render the status:


return (
<div>
<h3>OnlineIndicator</h3>
{status}
</div>
)
}

11. Edit src/pages/Demo.jsx and import the OnlineIndicator component:


import { OnlineIndicator } from '@/components/demo/
useSyncExternalStore/OnlineIndicator.jsx'

12. Render the component on the Demo page:


<h2>useId</h2>
<AriaInput />
<h2>useSyncExternalStore</h2>
<OnlineIndicator />
</div>
)
}

Now, go to the Demo page and (if you are online) it should show online. Turn all network con-
nections off to see it change to offline.

Figure 9.7 – Detecting that the user has gone offline via an external store (browser APIs)

useDebugValue
The Debug Value Hook is useful for developing custom Hooks that are part of shared libraries. It
can be used to show certain values for debugging in React DevTools. Its signature is as follows:
useDebugValue(value, format)
Chapter 9 221

The first parameter, value, is the value or message that should be logged. The second, optional
format parameter is used to provide a format function that will format the value before being
shown.

Let’s briefly try it out by defining a custom Hook for the OnlineIndicator component:

1. Edit src/components/demo/useSyncExternalStore/OnlineIndicator.jsx and import


the useDebugValue function:
import { useSyncExternalStore, useDebugValue } from 'react'

2. Then, define a new Hook function before the component is defined:


function useOnlineStatus() {
const isOnline = useSyncExternalStore(subscribe, getSnapshot)
const status = isOnline ? 'online' : 'offline'

3. Add the Debug Value Hook, as follows:


useDebugValue(status)

4. Return the status from the Hook:


return status
}

5. Adjust the component to use the custom Hook instead:


export function OnlineIndicator() {
const status = useOnlineStatus()

return (
<div>
<h3>OnlineIndicator</h3>
{status}
</div>
)
}

Now, go to the Demo page. If you haven’t already installed the React Developer Tools extension,
please install it for your browser (follow the instructions at https://react.dev/learn/react-
developer-tools). Go to the Components tab in the inspector of your browser and select the
OnlineIndicator component.
222 Advanced Hooks Provided by React

You will see the debug value of the custom Hook being shown there:

Figure 9.8 – The status of our custom Hook being shown in the React Developer Tools

After learning about the various built-in utility Hooks that React provides, let’s move on to learning
about using built-in Hooks for performance optimizations.

Using Hooks for performance optimizations


Certain Hooks can be used to optimize the performance of your app. Generally, the rule of thumb
is to not optimize prematurely. This is especially true with the React Compiler, introduced in
React 19. Nowadays, the React Compiler optimizes most cases for us automatically. So, keep in
mind to only use these Hooks when you have identified a specific performance problem with
your app. Generally, the rule of thumb is to not optimize prematurely unless you know it will be
an expensive computation.

The React Compiler is a Babel plugin that can be manually installed, and it also
ships with certain frameworks, such as Next.js. For more information on the React
Compiler, please read the following page in the React docs: https://react.dev/
learn/react-compiler.
Chapter 9 223

useDeferredValue
The Deferred Value Hook can be used to defer low-priority updates (such as filtering a list) so that
higher-priority updates (such as updating the text entered in an input field) can be processed first.

For example, if you have a search where text can be entered to filter items, the Deferred Value
Hook can be used to defer updates to the filter. Unlike debouncing, where we set a fixed time
after which updates are persisted, deferring is dynamic and dependent on how fast the UI can be
rendered. On faster machines, it will update more frequently, while on slower machines, updates
will not slow down the rest of the UI.

The signature of the useDeferredValue function looks as follows:


const deferredValue = useDeferredValue(value, initialValue)

The first parameter is a value to be deferred. For example, this value can be from a State Hook
that handles user input.

The second parameter is an optional initial value used for the initial render of the component. If
no initial value is defined, the Hook will not defer during the initial render because there is no
value that it can render until value gets set (for example, by the user typing into an input field).

Implementing a search without deferred values


Let’s first implement a search page where blog posts are searched, without using deferred values:

1. Edit src/api.js and define a function to produce an artificial delay, so that we can sim-
ulate the search operation being slow:
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms))
}

2. Next, define a searchPosts API function, which fetches all posts (featured and non-fea-
tured):
export async function searchPosts(query) {
const res = await fetch('/api/posts')
const posts = await res.json()
224 Advanced Hooks Provided by React

3. Then, filter the posts using a simple search (making both the title and query lowercase
and then checking whether the query is included in the title):
const filteredPosts = posts.filter((post) => {
const title = post.title.toLowerCase()
return title.includes(query.toLowerCase())
})

4. Add an artificial delay of one second:


await sleep(1000)

5. Then, return the filtered posts:


return filteredPosts
}

6. Create a new src/components/post/PostSearchResults.jsx file. Inside it, import the


following:
import { useSuspenseQuery } from '@tanstack/react-query'
import { searchPosts } from '@/api.js'
import { PostList } from './PostList.jsx'

7. Now, define a component that will display the search results for a given query using the
Suspense Query Hook, our API function, and the PostList component:
export function PostSearchResults({ query }) {
const { data } = useSuspenseQuery({
queryKey: ['posts', query],
queryFn: () => searchPosts(query),
})
return <PostList posts={data} />
}

8. Next, create a new src/components/post/PostSearch.jsx file. Inside it, import the fol-
lowing:
import { useState, Suspense } from 'react'
import { PostSearchResults } from './PostSearchResults.jsx'
Chapter 9 225

9. Define a PostSearch component that uses a State Hook and an input field to handle the
query:
export function PostSearch() {
const [query, setQuery] = useState('')
return (
<div>
<input value={query} onChange={(e) =>setQuery(e.target.value)}
/>

10. Define a Suspense boundary and, inside it, render the PostSearchResults component:
<Suspense fallback={<h4>loading...</h4>}>
<PostSearchResults query={query} />
</Suspense>
</div>
)
}

11. Create a new src/pages/Search.jsx file. Inside it, import the PostSearch component:
import { PostSearch } from '@/components/post/PostSearch.jsx'

12. Render a page with the PostSearch component, as follows:


export function Search() {
return (
<div>
<h1>Search posts</h1>
<PostSearch />
</div>
)
}

13. Edit src/App.jsx and import the Search page:


import { Search } from './pages/Search.jsx'

14. Add a link to the page, as follows:


<BrowserRouter>
<div style={{ padding: 8 }}>
<NavBarLink to='/'>Home</NavBarLink>
226 Advanced Hooks Provided by React

{' | '}
<NavBarLink to='/search'>Search</NavBarLink>
{' | '}
<NavBarLink to='/demo'>Demo</NavBarLink>

15. Finally, define the route:


<Routes>
<Route index element={<Home />} />
<Route path='post/:id' element={<ViewPost />}
/>
<Route path='demo' element={<Demo />} />
<Route path='search' element={<Search />} />
</Routes>

Now go to the Search page and enter a query into the search; you will see that it shows loading…
for a second before showing the new results.

Figure 9.9 – Waiting for the new results to load


Chapter 9 227

While this search works, it’s not a great user experience to replace all results with a loading…
message while the user is typing a query.

Introducing deferred values


With the Deferred Value Hook, we can improve this behavior by showing the old query results
while the new results are being fetched, and then seamlessly replace them with the new results
once they are ready.

Let’s get started using the Deferred Value Hook now:

1. Edit src/components/post/PostSearch.jsx and import the useDeferredValue function:


import { useState, Suspense, useDeferredValue } from 'react'

2. Define the Deferred Value Hook inside the PostSearch component:


export function PostSearch() {
const [query, setQuery] = useState('')
const deferredQuery = useDeferredValue(query)

3. Now, replace the query for the SearchResults component with deferredQuery:
<Suspense fallback={<h4>loading...</h4>}>
<SearchResults query={deferredQuery} />
</Suspense>
</div>
)
}

Go to the Search page and enter a query into the search input field; you will see that the previous
results are shown until the new results come in. The loading… message is now only displayed
initially before the first query is entered!
228 Advanced Hooks Provided by React

Figure 9.10 – Showing stale results while the new results are loading

useMemo
The Memo Hook takes the result of a function and memoizes it. This means that it will not be
recomputed every time. This Hook can be used for performance optimizations:
const memoizedVal = useMemo(
() => computeVal(a, b, c),
[a, b, c]
)

In the previous example, computeVal is a performance-heavy function that computes a result


from a, b, and c.

useMemo runs during rendering, so make sure the computation function does not
cause any side effects, such as resource requests. Side effects should be put into a
useEffect Hook.
Chapter 9 229

The array passed as the second argument specifies the dependencies of the function. If any of
these values change, the function will be recomputed; otherwise, the stored result will be used.
If no array is provided, a new value will be computed on every render. If an empty array is passed,
the value will only be computed once.

Do not rely on useMemo to only compute things once. React may forget some
previously memoized values if they are not used for a long time, for example, to
free up memory. Use it only for performance optimizations.

Since React 19, the React Compiler attempts to memoize values as much as possible automatically.
In most cases, it should not be necessary to manually wrap values with useMemo anymore. Only
use this Hook if you find a performance problem that the React Compiler does not memoize in
a satisfying way. As a rule of thumb, try not to prematurely optimize your components, and do
not use Memo Hooks unless you have a very good reason for doing so.

useCallback
The useCallback Hook works similarly to the useMemo Hook. However, it returns a memoized
callback function instead of a value:
const memoizedCallback = useCallback(
() => doSomething(a, b, c),
[a, b, c]
)

The previous code is similar to the following useMemo Hook:


const memoizedCallback = useMemo(
() => () => doSomething(a, b, c),
[a, b, c]
)

The function returned will only be redefined if one of the values passed in the dependency array
changes.

Similarly to the Memo Hook, the Callback Hook should only be used if you have identified a spe-
cific performance problem that the React Compiler does not handle in a satisfying way, such as
infinite re-render loops or too many renders.
230 Advanced Hooks Provided by React

Now that we have learned about using built-in Hooks for performance optimization, let’s briefly
cover the final few built-in Hooks provided by React: advanced versions of the Effect Hook.

Using Hooks for advanced effects


There are two special versions of the Effect Hook: the Layout Effect Hook and the Insertion Effect
Hook. These are only needed for very specific use cases, so we are only briefly covering them here.

useLayoutEffect
The Layout Effect Hook is identical to the Effect Hook, but it fires synchronously after all DOM
mutations are completed and before the component is rendered in the browser. It can be used to
read information from the DOM and adjust the appearance of components before rendering. Up-
dates inside this Hook will be processed synchronously before the browser renders the component.

Do not use this Hook unless it is really needed, which is only in certain edge cases. useLayoutEffect
will block visual updates in the browser, and thus, is slower than useEffect.

The rule here is to use useEffect first. If your mutation changes the appearance of the DOM node,
which can cause it to flicker, you should use useLayoutEffect instead.

useInsertionEffect
The Insertion Effect Hook is similar to the Effect Hook, but it fires before any layout effects fire.
This Hook is intended to be used only by CSS-in-JS library authors, so you will most likely not
need it.

Example code

The example code for this chapter can be found in the Chapter09/Chapter09_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Summary
In this chapter, we learned about all Hooks provided by React as of version 19.1. We started by
giving an overview of the built-in Hooks and then learned about various utility Hooks. Next, we
moved on to learning about Hooks for performance optimization. Finally, we learned about
advanced Effect Hooks.
Chapter 9 231

We now have an overview of all the different built-in Hooks that React provides.

In the next chapter, we are going to learn about using various Hooks developed by the React
community, as well as where to find more of them.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. What are the different use cases of the Ref Hook?


2. Which functionality does the Imperative Handle Hook add?
3. When should we use the Id Hook?
4. Which use case does the Sync External Store Hook cover?
5. How can we use the Debug Value Hook?
6. What advantages does using the Deferred Value Hook give us?
7. When should we use Memo and Callback Hooks?
8. In most cases, is it still necessary to use Memo and Callback Hooks?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• The Built-in React Hooks section of the React docs: https://react.dev/reference/react/


hooks
• More information about the React Compiler: https://react.dev/learn/react-compiler

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
10
Using Community Hooks
In the previous chapter, we learned about the various built-in React Hooks.

In this chapter, we are going to learn about various Hooks provided by the community. First,
we are going to learn about using Hooks to manage application state. Then, we will implement
debouncing using Hooks. Next, we will learn about various utility Hooks. Finally, we are going
to learn where to find more community Hooks.

The following topics will be covered in this chapter:

• Using Hooks to manage application state


• Debouncing with Hooks
• Learning about various utility Hooks
• Finding more community Hooks

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com
234 Using Community Hooks

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter10

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Using Hooks to manage application state


In this section we are going to learn about various community Hooks that help you manage
application state. These Hooks are provided by useHooks.com, which is a collection of various
useful Hooks packaged in a single library.

useLocalStorage
The Local Storage Hook allows you to store and retrieve data using the browser’s LocalStorage
API. The LocalStorage API is a way to persistently store information in the user’s browser. We can
use this to, for example, store information about the currently logged in user.

The useLocalStorage function has the following signature:


const [data, saveData] = useLocalStorage(key, initialValue)

As we can see, the Local Storage Hook accepts a key (which is used to identify the data in local
storage) and an initial value (which is a fallback used when there is no item with the given key
in local storage). It then returns an API similar to the State Hook: The data itself, and a function
to update data in local storage.
Chapter 10 235

In our case, we are simply going to store the username in local storage.

In a real application you should instead store a token, such as a JSON Web Token
(JWT), and ideally store it in a Cookie instead of local storage. However, this would
require a server and some full-stack knowledge. To learn more about full-stack
projects with React including real-world authentication, please refer to my book:
Modern Full-Stack React Projects.

Follow these steps to get started storing the username in local storage:

1. Copy the Chapter09_1 folder to a new Chapter10_1 folder by executing the following
command:
$ cp -R Chapter09_1 Chapter10_1

2. Open the new Chapter10_1 folder in VS Code.


3. Install the useHooks library, as follows:
$ npm install --save-exact @uidotdev/[email protected]

4. Remove the src/contexts/UserContext.js file. We are going to replace the UserContext


with local storage now.
5. Edit src/App.jsx and remove the following imports:
import { useState } from 'react'
import { UserContext } from './contexts/UserContext.js'

6. Instead, replace them with the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

7. Remove the following State Hook:


export function App() {
const [username, setUsername] = useState('')

8. Replace it with a Local Storage Hook:


export function App() {
const [username] = useLocalStorage('username', null)
236 Using Community Hooks

9. Remove the UserContext by deleting the following highlighted lines:


return (
<QueryClientProvider client={queryClient}>
<UserContext.Provider value={[username, setUsername]}>

</UserContext.Provider>
</QueryClientProvider>
)
}

10. Edit src/components/user/UserBar.jsx and remove the following imports:


import { useContext } from 'react'
import { UserContext } from '@/contexts/UserContext.js'

11. Replace them with an import to the useLocalStorage function:


import { useLocalStorage } from '@uidotdev/usehooks'

12. Then, replace the Context Hook with a Local Storage Hook, as follows:
export function UserBar() {
const [username] = useLocalStorage('username', null)

13. Edit src/components/user/Register.jsx and replace all imports with the following:
import { useState } from 'react'
import { useLocalStorage } from '@uidotdev/usehooks'

14. Now, replace the Context Hook with a Local Storage Hook:
export function Register() {
const [, setUsername] = useLocalStorage('username', null)

15. Edit src/components/user/Login.jsx and replace all imports with the following:
import { useLocalStorage } from '@uidotdev/usehooks'

16. Next, replace the Context Hook with a Local Storage Hook:
export function Login() {
const [, setUsername] = useLocalStorage('username', null)
Chapter 10 237

17. Edit src/components/user/Logout.jsx and replace all imports with the following:
import { useState, useEffect } from 'react'
import { useLocalStorage } from '@uidotdev/usehooks'

18. Replace the Context Hook with a Local Storage Hook:


export function Logout() {
const [username, setUsername] = useLocalStorage('username', null)

19. Edit src/components/post/CreatePost.jsx and remove the useContext import:


import { useContext, useActionState } from 'react'

20. Then, remove the following import:


import { UserContext } from '@/contexts/UserContext.js'

21. Replace it with an import of useLocalStorage:


import { useLocalStorage } from '@uidotdev/usehooks'

22. Replace the Context Hook with a Local Storage Hook:


export function CreatePost() {
const [username] = useLocalStorage('username', null)

23. Edit src/components/comment/CreateComment.jsx and replace all imports with the


following:
import { useLocalStorage } from '@uidotdev/usehooks'

24. Then, replace the Context Hook with a Local Storage Hook:
export function CreateComment({ addComment }) {
const [username] = useLocalStorage('username', null)

25. Edit src/components/comment/CommentList.jsx and remove the useContext import:


import { useContext, useState, useOptimistic } from 'react'

26. Remove the following import:


import { UserContext } from '@/contexts/UserContext.js'
238 Using Community Hooks

27. Replace it with an import of useLocalStorage:


import { useLocalStorage } from '@uidotdev/usehooks'

28. Replace the Context Hook with a Local Storage Hook:


export function CommentList() {
const [username] = useLocalStorage('username', null)

29. Now, run the blog app, as follows:


$ npm run dev

You will see that the registration, login and logout still work the same way as before, but there
is now an added advantage: When refreshing the page, the user stays logged in until they press
the Logout button!

As you can see, the Local Storage Hook is a great way to persistently store information in the
browser!

useHistoryState
The History State Hook is an extended version of the State Hook, adding functionality to undo /
redo changes to the state. It has the following signature:
const { state, set, undo, redo, clear, canUndo, canRedo } =
useHistoryState(initialState)

We provide an initial state to it, and it returns the current state, a function to set the state, an
undo function to undo changes to the state, a redo function to redo changes, a clear function to
reset the state to the initial state, and canUndo and canRedo flags to tell whether it is possible to
undo/redo the state.

The best way to understand this Hook is to try it out, so let’s get started implementing undo/redo
functionality for our CreatePost component:

1. Edit src/components/post/CreatePost.jsx and import the useHistoryState function:


import { useLocalStorage, useHistoryState } from '@uidotdev/
usehooks'

2. Define a History State Hook for the post content, as follows:


export function CreatePost() {
const [username] = useLocalStorage('username', null)
Chapter 10 239

const navigate = useNavigate()


const { state, set, undo, redo, clear, canUndo, canRedo } =
useHistoryState('')

3. Define a handler function for when the content is changed by the user:
function handleContentChange(e) {
const { value } = e.target
set(value)
}

4. Define buttons to undo/redo and clear the content:


<div>
<label htmlFor='create-title'>Title:</label>
<input type='text' name='title' id='create-title' />
</div>
<div>
<button type='button' disabled={!canUndo} onClick={undo}>
Undo
</button>
<button type='button' disabled={!canRedo} onClick={redo}>
Redo
</button>
<button type='button' onClick={clear}>
Clear
</button>
</div>

It is important to add type='button' to all the buttons here. Otherwise,


pressing those buttons will submit the form.

5. Make the <textarea> a controlled element by providing the value and onChange handler:
<textarea name='content' value={state}
onChange={handleContentChange} />
240 Using Community Hooks

6. Lastly, inside the Action State Hook, call the clear function after the post was successfully
created:
const [error, submitAction, isPending] = useActionState(
async (currentState, formData) => {
const title = formData.get('title')
const content = formData.get('content')
const post = { title, content, author: username, featured:
false }
try {
const result = await createPostMutation.mutateAsync(post)
clear()
navigate(`/post/${result.id}`)
} catch (err) {
return err
}
},
)

As we are dealing with a controlled element now, we need to clear its content ourselves.
It is not done automatically on form submission anymore.

7. Start the blog app, as follows:


$ npm run dev

You will see that there are now three new buttons, as shown in the following screenshot:

Figure 10.1 – Providing undo/redo/clear buttons when creating a post

Try typing in some text into the field, and you will be able to undo/redo changes made to it!
However, you may have noticed that only a single character at a time is undone/redone. Next,
we are going to implement debouncing, which means that our changes will only be added to
the undo/redo history after a certain amount of time, not after every character that we entered.
Chapter 10 241

Example code

The example code for this section can be found in the Chapter10/Chapter10_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Debouncing with Hooks


As we have seen in the previous section, when we press undo it undoes a single character at a
time. Sometimes, we do not want to store every change in our undo history. To avoid storing
every change, we need to implement debouncing, which means that the function that stores the
content to the history state is only called after there are no changes for a certain amount of time.

The use-debounce library provides a Debounce Hook, which can be used, as follows, for simple
values:
const [text, setText] = useState('')
const [value] = useDebounce(text, 1000)

Now, if we change the text via setText, the text value will be updated instantly, but the value
variable will only be updated after 1000 ms (1 second).

However, for our use case, this is not enough. We are going to need debounced callbacks in order to
implement debouncing in combination with the History State Hook. Thankfully, the use-debounce
library also provides the Debounced Callback Hook, which can be used as follows:
const [text, setText] = useState('')
const [debouncedSet, cancelDebounce] = useDebouncedCallback(
(value) => setText(value),
1000
)

Now, if we call debouncedSet('text'), the text value will be updated after 1000 ms (1 second).
If debouncedSet is called multiple times, the timeout will get reset every time, so that only after
1000 ms of no further call to the debouncedSet function, the setText function will be called.
242 Using Community Hooks

Debouncing changes in the post editor


Now that we have learned about debouncing, we are going to implement it in combination with
the History State Hook in our post editor. Follow these steps to get started:

1. Copy the Chapter10_1 folder to a new Chapter10_2 folder by executing the following
command:
$ cp -R Chapter10_1 Chapter10_2

2. Open the new Chapter10_2 folder in VS Code.


3. Install the use-debounce library, as follows:
$ npm install --save-exact [email protected]

4. Edit src/components/post/CreatePost.jsx and import the useState, useEffect and


useDebouncedCallback functions:

import { useActionState, useState, useEffect } from 'react'


import { useDebouncedCallback } from 'use-debounce'

5. Define a new State Hook that will contain the controlled input value:
const { state, set, undo, redo, clear, canUndo, canRedo } =
useHistoryState('')
const [content, setContent] = useState('')

6. Then, define a Debounced Callback Hook, which will set the History State after 200 ms:
const debounced = useDebouncedCallback((value) => set(value), 200)

7. Next, we have to define an Effect Hook, which will trigger whenever the History State
changes, cancel the current debounce, and set the controlled input value to the current
value from the History State Hook:
useEffect(() => {
debounced.cancel()
setContent(state)
}, [state, debounced])
Chapter 10 243

8. Adjust the handler function to trigger the setContent function to update the controlled
input value, and the debounced function to update the History State:
function handleContentChange(e) {
const { value } = e.target
setContent(value)
debounced(value)
}

9. Finally, adjust the textarea to use content instead of state for its value:
<textarea name='content' value={content}
onChange={handleContentChange} />

10. Start the blog app, as follows:


$ npm run dev

We now instantly set the controlled input value, but we do not store anything to the History State
yet. After the debouncing callback triggers (after 200 ms), we store the current value to the History
State. Whenever the History State updates, for example, when we press the Undo/Redo buttons,
we cancel the current debouncing to avoid overwriting the value after undoing/redoing. Then,
we set the controlled input value to the new value of the History State Hook.

If we now type some text into our editor, we can see that the Undo button only activates after a
while. If we now press the Undo button, we can see that we will not undo character-by-character,
but more text at once. As we can see, undo/redo works very well together with debouncing!

Difference between debounced and deferred values


You may remember that in Chapter 9, Advanced Hooks Provided by React, we used the Deferred
Value Hook to wait for the new search results to come in before showing them, letting us avoid
showing a loading screen while waiting for new results. While we could have used debouncing
there as well, there are certain downsides to using debounce for this use case.

The main difference between debounced and deferred values is that when debouncing, we define a
fixed time interval after which the value is updated. Deferred values, however, will keep attempting
to update after every change (and cancel them if a new change comes in). Thus, deferred values
are not limited to a fixed time interval, but instead limited by the speed at which the requests
can be processed.
244 Using Community Hooks

Example code

The example code for this section can be found in the Chapter10/Chapter10_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Learning about various utility Hooks


We are now going to learn about a selection of some useful utility Hooks provided by the useHooks
library.

useCopyToClipboard
The Copy To Clipboard Hook makes it easy to copy text to the clipboard across various browsers.
If available, it uses the modern navigator.clipboard.writeText API. Otherwise, it falls back to
the traditional document.execCommand("copy") method, ensuring that the functionality works
for older and newer browsers. This Hook is also provided by useHooks.com.

The useCopyToClipboard function has the following signature:


const [copiedText, copyToClipboard] = useCopyToClipboard()

It provides a similar API to the State Hook, with a copyToClipboard function that accepts a string
and copies it to the clipboard, as well as storing it in the copiedText value. This value can also be
used to check if we successfully copied the text to the clipboard.

Let’s now use the Hook to implement a way of copying a link to blog posts:

1. Copy the Chapter10_2 folder to a new Chapter10_3 folder by executing the following
command:
$ cp -R Chapter10_2 Chapter10_3

2. Open the new Chapter10_3 folder in VS Code.


3. Create a new src/components/post/CopyLink.jsx file.
4. Inside it, import the useCopyToClipboard function:
import { useCopyToClipboard } from '@uidotdev/usehooks'
Chapter 10 245

5. Define a checkmark and link emoji for the copy link button:
const CHECKMARK_EMOJI = <>&#9989;</>
const LINK_EMOJI = <>&#128279;</>

6. Then, define a component that accepts a url:


export function CopyLink({ url }) {

7. Inside the component, define the Copy To Clipboard Hook, as follows:


const [copiedText, copyToClipboard] = useCopyToClipboard()

8. Now, render a button that triggers the copyToClipboard function:


return (
<button type='button' onClick={() => copyToClipboard(url)}>

9. If the link was already copied, show the checkmark symbol, otherwise the link symbol:
{copiedText ? CHECKMARK_EMOJI : LINK_EMOJI}
</button>
)
}

10. Edit src/components/post/Post.jsx and import the CopyLink component:


import { CopyLink } from './CopyLink.jsx'

11. Render the component next to the title of the blog post, passing the current URL to it:
<h3 style={{ color: theme.primaryColor }}>
{title} <CopyLink url={window.location.href} />
</h3>

12. Start the blog app, as follows:


$ npm run dev
246 Using Community Hooks

13. Click on one of the View post > links to go to the single post page. You will see a button
with the link emoji now:

Figure 10.2 – Showing a “copy link” button next to the blog post title

1. After clicking this button, it will show the checkmark emoji and copy the current URL to
your clipboard:

Figure 10.3 – The state of the button after successfully copying the link

Try pasting the link somewhere to see if it worked!

useHover
The Hover Hook tracks whether an element is being hovered over by the user. It has the following
signature:
const [ref, hovering] = useHover()

As we can see, it returns a ref, which we need to pass to the element that we want to track the
hover state of. It also returns a hovering state, which will be true if the user is hovering over the
element, and false if they are not. This Hook is also provided by useHooks.com.
Chapter 10 247

Let’s now use the Hover Hook to show a hint when the user hovers over the copy link button:

1. Edit src/components/post/CopyLink.jsx and import the useHover function:


import { useCopyToClipboard, useHover } from '@uidotdev/usehooks'

2. Then, define a Hover Hook:


export function CopyLink({ url }) {
const [copiedText, copyToClipboard] = useCopyToClipboard()
const [ref, hovering] = useHover()

3. Create a Fragment, so that we can display a message next to the button:


return (
<>

4. Pass the ref of the Hover Hook to the button:


<button ref={ref} type='button' onClick={() =>
copyToClipboard(url)}>

5. If we are hovering over the button, show a small info text, as follows:
{copiedText ? CHECKMARK_EMOJI : LINK_EMOJI}
</button>
{hovering && (
<small>
<i> {copiedText ? 'copied!' : 'click to copy a link to the
post'}</i>
</small>
)}
</>
)
}

In real world projects, most hover effects in the UI would be done with CSS. A real
world example for using the Hover Hook would be to send events to an analytics
API on hover. However, this would be a significantly longer example than showing
a text on hover.
248 Using Community Hooks

Try hovering over the copy link button now, and you will see the info text:

Figure 10.4 – Showing an info text when hovering over the button

Example code

The example code for this section can be found in the Chapter10/Chapter10_3
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Finding more community Hooks


We have already learned about the collection of Hooks provided by useHooks.com. However, there
are many more Hooks that are provided by the community. You can find a searchable list of various
Hooks on the following page: https://nikgraf.github.io/react-hooks/.

To give you an idea of which other Hooks are out there, the following features are provided by
community Hooks. We now list a couple more interesting Hooks provided by the community.

• use-events (https://github.com/sandiiarov/use-events): Various JavaScript events


that have been turned into Hooks, such as mouse position, touch events, clicking outside,
and so on.
• react-use (https://github.com/streamich/react-use): Various Hooks to deal with
sensors (useBattery, useIdle, useGeolocation, and so on), UI (useAudio, useCss,
useFullscreen, and so on), animations (useSpring, useTween, useRaf, and so on), and
side effects (useAsync, useDebounce, useFavicon, and so on)

Of course, there are many more Hooks to be found on GitHub and npm.
Chapter 10 249

Summary
In this chapter, we first learned how to persistently store data in the browser by using the
LocalStorage API via the Local Storage Hook. Then, we implemented undo/redo functionality in
the CreatePost component using the History State Hook. Next, we learned about debouncing and
implemented it using the Debounced Callback Hook. Then, we learned about some utility Hooks
to copy to clipboard and handle hover states. Finally, we learned where to find more community
Hooks.

In the next chapter, we are going to learn about the rules of Hooks, teaching us the basics we need
to know before developing our own custom Hooks.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. Which Hook can we use to persistently store information in the browser?


2. Which Hook can we use to implement undo/redo functionality?
3. What is debouncing? Why do we need to do it?
4. Which Hook can we use for debouncing?
5. How does debouncing values differ from deferring values?
6. Where can we find more Hooks?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following book and links:

• Modern Full-Stack React Projects by Daniel Bugl


• useHooks website: https://usehooks.com
• use-debounce library documentation: https://github.com/xnimorz/use-debounce
• Collection of React Hooks: https://nikgraf.github.io/react-hooks/
250 Using Community Hooks

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
Part 3
Refactoring and
Migrating Existing Code
In this final part of the book, you will first learn about the rules of Hooks, serving as the basis
for creating your own Hooks. Then, you will learn in depth how to refactor our existing blog app
code to use custom Hooks where it makes sense to do so. Finally, you will migrate an existing
project from React class components to React Hooks and learn how the two solutions compare.

This part has the following chapters:

• Chapter 11, Rules of Hooks


• Chapter 12, Building Your Own Hooks
• Chapter 13, Migrating from React Class Components
11
Rules of Hooks
In the previous chapter we learned about using various Hooks that have been developed by the
React community, as well as where to find more of them.

In this chapter, we are going to learn about everything that there is to know and watch out for
when using Hooks. We are also going to learn what we need to know to start developing our own
Hooks. Hooks have certain limitations regarding the places and the order that they are defined
in. Violating the rules of Hooks can cause bugs or unexpected behavior, so we need to make sure
that we learn and enforce the rules.

The following topics will be covered in this chapter:

• Using Hooks
• Order of Hooks
• Names of Hooks
• Enforcing the rules of Hooks

Using Hooks
Hooks can only be used in:

• React function components


• Custom Hooks (we are going to learn about creating custom Hooks in the next chapter)
254 Rules of Hooks

Hooks cannot be used:

• Inside conditions or loops


• After a conditional return statement
• In event handlers
• In class components
• Inside functions passed to Memo, Reducer or Effect Hooks
• Inside try/catch/finally blocks

In some places, like the React docs, using a Hook is sometimes called calling the Hook.

Hooks are normal JavaScript functions, except that React relies on them being invoked from inside
a function component. Of course, custom Hooks that use other Hooks can be defined outside of
React function components, but when using those custom Hooks, we always need to make sure
that we call them inside a React component.

Next, we are going to learn about the rules regarding the order of Hooks.

Order of Hooks
Only use Hooks at the top level (not nested), ideally at the beginning of function components or
custom Hooks, like so:
function ExampleComponent() {
const [name, setName] = useState('')
// …
}

function useCustomHook() {
const [name, setName] = useState('')
return { name, setName }
}

Do not use Hooks inside conditions, loops, or nested functions—doing so changes the order of
Hooks, which causes bugs. We have already learned that changing the order of Hooks causes the
state to get mixed up between multiple Hooks.
Chapter 11 255

To recap, in Chapter 2, Using the State Hook, in Example 2, we learned that we cannot do the fol-
lowing:
const [enableFirstName, setEnableFirstName] = useState(false)
const [name, setName] = enableFirstName
? useState('')
: ['', () => {}]
const [lastName, setLastName] = useState('')

We rendered a checkbox and two input fields for the first name and last name, and then we entered
something in the last name field, as shown in this screenshot:

Figure 11.1 – Revisiting the example from Chapter 2, Using the State Hook

At the moment, the order of Hooks looks as follows:


1. enableFirstName
2. lastName

Then, we clicked on the checkbox to enable the first name field. Doing so changed the order of
Hooks, because now our Hook definitions look like this:
1. enableFirstName
2. firstName
3. lastName
256 Rules of Hooks

Since React solely relies on the order of Hooks to manage their state, the firstName field is now
the second Hook, so it gets the state from the lastName field, as you can see here:

Figure 11.2 – Problem of changing the order of Hooks from Chapter 2, Using the State Hook

If we use the real useState Hook from React in Example 2 from Chapter 2, Using the State Hook,
we can see that React automatically detects when the order of Hooks has changed, and it will
log a warning, as follows:

Figure 11.3 – React printing a warning when detecting that the order of Hooks has changed

When running React in development mode, it will additionally throw an error and crash the
application when this happens:
Chapter 11 257

Figure 11.4 – React throwing an error in development mode

As we can see, changing the order of Hooks or conditionally enabling Hooks is not possible, as
React internally uses the order of Hooks to keep track of which data belongs to which Hook.

To fix this issue, we always defined the Hook, like so:


const [enableFirstName, setEnableFirstName] = useState(false)
const [name, setName] = useState('')
const [lastName, setLastName] = useState('')

Then, we conditionally rendered the name instead of conditionally defining the Hook:
My name is: {enableFirstName ? name : ''} {lastName}

The fixed version can be seen in Example 3 from Chapter 2, Using the State Hook.

After learning about the order of Hooks, let’s move on to the naming convention for Hooks.

Names of Hooks
When it comes to naming Hooks, there is a convention that Hook functions should always be
prefixed with use, followed by the Hook name starting with a capital letter. For example: useState,
useEffect, and useQuery. This is important because, otherwise, we would not know which func-
tions are Hooks, and which are not. Especially when automatically enforcing the rules of Hooks,
we need to be able to know which functions are Hooks, so that we can make sure they are not
being called conditionally or in loops.

It is best practice to name Hooks in such a way that they semantically make sense.
For example, if you want to create a custom Hook for an input field, you would call
it useInputField. This ensures that when using the Hook it is immediately clear
what that Hook will be useful for.
258 Rules of Hooks

As we can see, naming conventions make our lives a lot easier: Knowing the difference between
normal functions and Hooks makes it very easy to automatically enforce the rules of Hooks.

In the next section, we are going to learn how the rules of Hooks are automatically enforced by
ESLint.

Enforcing the rules of Hooks


If we stick to the convention of prefixing Hook functions with use, we can automatically enforce
the other two rules:

• Only call Hooks from React function components


• Only call Hooks at the top level (not inside loops, conditions, or nested functions)

In order to enforce the rules automatically, React provides an ESLint plugin called eslint-plugin-
react-hooks, which will automatically detect when Hooks are used, and will ensure that the rules
are not broken. ESLint is a linter, which is a tool that analyzes source code and finds problems
such as stylistic errors, potential bugs, and programming errors.

Thankfully, Vite has already set up ESLint with the relevant React plugins for us. You may re-
member that in Chapter 2, Using the State Hook, we had to specifically disable the linter when we
added a conditional Hook, by adding the following line:
// eslint-disable-next-line react-hooks/rules-of-hooks

If we removed this line, we would get the following linter error:

Figure 11.5 – ESLint showing an error when violating the rules of Hooks
Chapter 11 259

So, this whole time that we have been using Hooks, the linter was already making sure that we
do not use them incorrectly! To recap how we set up ESLint, please refer back to Chapter 1, Intro-
ducing React and React Hooks.

Summary
In this chapter, we first learned that we should only call Hooks from React function components
and that we need to ensure that the order of Hooks stays the same. Additionally, we learned about
the naming convention of Hooks, and that they should always start with the use prefix. Then, we
learned how ESLint can help us by enforcing the rules of Hooks.

Knowing about the rules of Hooks, and enforcing them, is very important in order to avoid bugs
and unexpected behavior. These rules will be especially important when creating our own Hooks.

Now that we have a good grasp of how Hooks work, and the rules and conventions, in the next
chapter, we are going to learn how to create our own Hooks!

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. Where can Hooks be called?


2. Can we use Hooks in React class components?
3. What do we need to watch out for regarding the order of Hooks?
4. Can Hooks be called inside conditions, loops, or nested functions?
5. What is the naming convention for Hooks?
6. How are the rules of Hooks automatically enforced?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following link:

• Rules of Hooks in the official React documentation: https://react.dev/reference/


rules/rules-of-hooks
260 Rules of Hooks

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
12
Building Your Own Hooks
In the previous chapter, we learned about the limitations and rules of Hooks. We also learned
where to call Hooks, why the order matters, and naming conventions for Hooks.

In this chapter, we are going to learn how to create custom Hooks by extracting existing code
from our components. We are also going to learn how to use custom Hooks and how Hooks can
interact with each other. Finally, we are going to learn how to write tests for our custom Hooks.

At the end of this chapter, you will be able to create custom Hooks to encapsulate and re-use
application logic, keeping your code clean and maintainable.

The following topics will be covered in this chapter:

• Creating a custom Theme Hook


• Creating a custom User Hook
• Creating custom API Hooks
• Creating a Debounced History State Hook
• Testing custom Hooks

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com
262 Building Your Own Hooks

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter12

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided in the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Creating a custom Theme Hook


After getting a good grasp on the concept of Hooks by learning about the built-in React Hooks,
community Hooks, as well as the rules of Hooks, we are now going to build our own Hooks.

In Chapter 5, Implementing React Contexts, we introduced a ThemeContext to style blog posts in our
app. We used a Context Hook to access the ThemeContext in many components. Functionality
that is used across multiple components is usually a good opportunity for a custom Hook. As you
might have noticed, we often do the following:
import { ThemeContext } from '@/contexts/ThemeContext.js'

export default function SomeComponent () {


const theme = useContext(ThemeContext)
// …

We could abstract this functionality into a useTheme Hook, which will get the theme object from
the ThemeContext.
Chapter 12 263

Usually, it makes the most sense to first write the component, and then later
extract a custom Hook from it if we notice that we use similar code across multiple
components. Doing so avoids prematurely creating custom Hooks and making our
project unnecessarily complex.

Now, let’s get started creating the custom Theme Hook.

Creating the custom Theme Hook


Let’s get started creating a custom Theme Hook now, by extracting the existing code for the
Context Hook into a separate function:

1. Copy the Chapter10_3 folder to a new Chapter12_1 folder by executing the following
command:
$ cp -R Chapter10_3 Chapter12_1

2. Open the new Chapter12_1 folder in VS Code.


3. Create a new src/hooks/ folder.
4. Inside it, create a new src/hooks/theme.js file.
5. In this newly created file, import the useContext function and the ThemeContext:
import { useContext } from 'react'
import { ThemeContext } from '@/contexts/ThemeContext.js'

6. Now, define and export a useTheme function, which simply returns the Context Hook:
export function useTheme() {
return useContext(ThemeContext)
}

It’s as simple as that, as long as we stick to the rules and naming conventions of Hooks, we can
easily create our own custom Hooks! Let’s continue by using our custom Theme Hook throughout
the blog app.
264 Building Your Own Hooks

Using the custom Theme Hook


To get started using our custom Theme Hook:

1. Edit src/components/post/Post.jsx and remove the following imports:


import { useContext } from 'react'
import { ThemeContext } from '@/contexts/ThemeContext.js'

Replace them with an import of the useTheme function:


import { useTheme } from '@/hooks/theme.js'

2. Replace the existing Context Hook with our custom Theme Hook:
export function Post({ id }) {
const theme = useTheme()

3. Edit src/components/post/PostListItem.jsx and remove the following imports:


import { useContext } from 'react'
import { ThemeContext } from '@/contexts/ThemeContext.js'

Replace them with an import of the useTheme function:


import { useTheme } from '@/hooks/theme.js'

4. Replace the Context Hook with our Theme Hook:


export function PostListItem({ id, title, author }) {
const theme = useTheme()

5. Run the dev server, as follows:


$ npm run dev

You will see that the theme still works the same way as before, showing featured posts
in a different color.

As we can see, replacing the Context Hooks with our Theme Hook simplifies the code a bit (re-
quiring less imports) and allows us to easily adjust the theming system later. For example, if we
wanted to fetch the default theme from a user setting instead of getting it from the context, we
could implement this functionality in the Theme Hook and all components would automatically
use this new theme system.
Chapter 12 265

Example code

The example code for this section can be found in the Chapter12/Chapter12_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Now that we have successfully created a custom Theme Hook, let’s move on to creating a custom
User Hook.

Creating a custom User Hook


In Chapter 5, Implementing React Contexts, we defined a UserContext to store the username of the
currently logged in user. In Chapter 10, Using Community Hooks, we replaced the UserContext
with a Local Storage Hook. As you may remember, refactoring from the Context Hook to a Local
Storage Hook required us to adjust the code in many components.

To avoid such issues in the future, we can put all user related information and functions into a
User Hook, which then exposes them to be used by other components.

Creating the custom User Hook


Let’s start by extracting all of our existing code related to dealing with the username into a cus-
tom User Hook:

1. Copy the Chapter12_1 folder to a new Chapter12_2 folder by executing the following
command:
$ cp -R Chapter12_1 Chapter12_2

2. Open the new Chapter12_2 folder in VS Code.


3. Create a new src/hooks/user.js file.
4. Inside it, import the useLocalStorage function:
import { useLocalStorage } from '@uidotdev/usehooks'

5. Define a new useUser function, in which we use the Local Storage Hook:
export function useUser() {
const [username, setUsername] = useLocalStorage('username', null)
266 Building Your Own Hooks

6. Additionally, we define a flag to tell whether the user is logged in:


const isLoggedIn = username !== null

7. Now, define the register, login, and logout functions:


function register(username) {
setUsername(username)
}

function login(username) {
setUsername(username)
}

function logout() {
setUsername(null)
}

8. Return the username, isLoggedIn flag, and the functions:


return { username, isLoggedIn, register, login, logout }
}

As you can see, we are not just returning the username and a function to set the username, but
instead returning an object with various information about the user session, as well as functions
that we can call to adjust the user state. Now that we have abstracted this functionality into a
User Hook, we can easily extend it later to support full authentication (instead of just storing
the username).

With our User Hook successfully created, let’s use it in our app.

Using the custom User Hook


Let’s now refactor our blog app to use the User Hook instead of directly reading from and writing
to the Local Storage Hook:

1. Edit src/App.jsx and remove the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

Replace it with an import to the useUser function:


import { useUser } from './hooks/user.js'
Chapter 12 267

2. Replace the Local Storage Hook with our custom User Hook, as follows:
export function App() {
const { isLoggedIn } = useUser()

3. Replace the username check with a check for the isLoggedIn flag:
{isLoggedIn && <CreatePost />}

Using the isLoggedIn flag from the Hook makes the code easier to read – before it may
have been unclear why we are checking for the username, but now it’s clear that we only
want to render this component when the user is logged in. Doing the check like this has
the additional benefit that we can change the logic for checking if the user is logged in
later by adjusting the User Hook.

4. Now edit src/components/user/UserBar.jsx and remove the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

Replace it with an import to the useUser function:


import { useUser } from '@/hooks/user.js'

5. Replace the Local Storage Hook with our custom User Hook, as follows:
export function UserBar() {
const { isLoggedIn } = useUser()

6. Replace the username check with a check for the isLoggedIn flag:
if (isLoggedIn) {

7. Next edit src/components/user/Register.jsx and remove the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

Replace it with an import to the useUser function:


import { useUser } from '@/hooks/user.js'

8. Replace the Local Storage Hook with our custom User Hook, as follows:
export function Register() {
const { register } = useUser()
268 Building Your Own Hooks

9. In the handleSubmit function, replace the setUsername function with our new register
function:
const username = e.target.elements.username.value
register(username)
}

Again, we are making our code easier to read by calling a function that explains what we
actually want to do (register a new user). Before, we were just calling setUsername here.
Later, we may want to actually connect this to a database, so having the register function
in the User Hook will make it easier for us to add this functionality later.

10. Edit src/components/user/Login.jsx and remove the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

Replace it with an import to the useUser function:


import { useUser } from '@/hooks/user.js'

11. Replace the Local Storage Hook with our custom User Hook, as follows:
export function Login() {
const { login } = useUser()

12. In the handleSubmit function, replace the setUsername function with our new login
function:
const username = e.target.elements.username.value
login(username)
}

13. Edit src/components/user/Logout.jsx and remove the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

Replace it with an import to the useUser function:


import { useUser } from '@/hooks/user.js'

14. Replace the Local Storage Hook with our custom User Hook, as follows:
export function Logout() {
const { username, logout } = useUser()
Chapter 12 269

15. In the handleSubmit function, replace the setUsername function with our new logout
function:
function handleSubmit(e) {
e.preventDefault()
logout()
}

16. Edit src/components/post/CreatePost.jsx and remove the following import of the


useLocalStorage function:

import { useLocalStorage, useHistoryState } from '@uidotdev/


usehooks'

17. Add an import to the useUser function, as follows:


import { useUser } from '@/hooks/user.js'

18. Replace the Local Storage Hook with our custom User Hook, as follows:
export function CreatePost() {
const { username } = useUser()

Being able to access the username from the User Hook decouples the component from the
internal logic. For example, we may later store a whole user object, or an authentication
token, in local storage. If we used the Local Storage Hook in each component, we would
need to adjust every single component that uses it. Now, we can simply adjust the User
Hook and as long as we still return the username from it, we do not need to change any
components.

19. Edit src/components/comment/CreateComment.jsx and remove the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

Replace it with an import to the useUser function:


import { useUser } from '@/hooks/user.js'

20. Replace the Local Storage Hook with our custom User Hook, as follows:
export function CreateComment({ addComment }) {
const { username } = useUser()
270 Building Your Own Hooks

21. Edit src/components/comment/CommentList.jsx and remove the following import:


import { useLocalStorage } from '@uidotdev/usehooks'

Replace it with an import to the useUser function:


import { useUser } from '@/hooks/user.js'

22. Replace the Local Storage Hook with our custom User Hook, as follows:
export function CommentList() {
const { isLoggedIn } = useUser()

23. Replace the username check with a check for the isLoggedIn flag:
{isLoggedIn && <CreateComment addComment={addComment} />}

As we can see, the refactored code with the User Hook is already significantly easier to read. In-
stead of doing checks on the username, we are checking an isLoggedIn flag. Additionally, we are
calling login, register and logout functions, abstracting implementation details and allowing
the components to focus on their features. Doing this separates concerns about the application
logic into custom Hooks, while components focus on the user interaction.

We can now start the dev server, as follows:


$ npm run dev

You will see that all functionality of our blog still works the same way as before.

Example code

The example code for this section can be found in the Chapter12/Chapter12_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Now that we finished creating the custom User Hook, let’s move on to creating custom Hooks
for API calls.

Creating custom API Hooks


We can also create Hooks for the various API calls. Putting these Hooks in a single file allows us
to adjust the API calls easily later on. We are going to prefix our custom API Hooks with useAPI
so it is easy to tell which functions are API Hooks.
Chapter 12 271

Extracting custom API Hooks


Let’s create custom Hooks for our API now by following these steps:

1. Copy the Chapter12_2 folder to a new Chapter12_3 folder by executing the following
command:
$ cp -R Chapter12_2 Chapter12_3

2. Open the new Chapter12_3 folder in VS Code.


3. Create a new src/hooks/api.js file.
4. Edit src/hooks/api.js and import the following functions:
import { useSuspenseQuery, useMutation } from '@tanstack/react-
query'
import {
fetchPosts,
fetchPost,
searchPosts,
createPost,
queryClient,
} from '@/api.js'

5. Define a function to fetch posts, copied over from the code we had in src/components/
post/PostFeed.jsx:

export function useAPIFetchPosts({ featured }) {


const { data } = useSuspenseQuery({
queryKey: ['posts', featured],
queryFn: async () => await fetchPosts({ featured }),
})
return data
}

6. Define a function to fetch a single post, copied over from the code we had in src/
components/post/Post.jsx:

export function useAPIFetchPost({ id }) {


const { data } = useSuspenseQuery({
queryKey: ['post', id],
queryFn: async () => await fetchPost({ id }),
272 Building Your Own Hooks

})
return data
}

7. Define a function to search for posts, copied over from the code we had in src/components/
post/PostSearchResults.jsx:

export function useAPISearchPosts({ query }) {


const { data } = useSuspenseQuery({
queryKey: ['posts', query],
queryFn: async () => await searchPosts(query),
})
return data
}

8. Define a function to create posts, copied over from the code we had in src/components/
post/CreatePost.jsx:

export function useAPICreatePost() {


const createPostMutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries(['posts'])
},
})
return createPostMutation.mutateAsync
}

Similarly to the User Hook, the API Hooks abstract implementation details and only expose the
necessary information, such as the data or the mutateAsync function. This means that we could
even swap out React Query for a different library later, simply by adjusting the custom API Hooks.

We can now refactor our blog app to use the custom API Hooks.

Using custom API Hooks


Follow these steps to refactor the app to use the previously defined API Hooks:
Chapter 12 273

1. Edit src/components/post/PostFeed.jsx and remove the following imports:


import { useSuspenseQuery } from '@tanstack/react-query'
import { fetchPosts } from '@/api.js'

Replace them with an import of useAPIFetchPosts:


import { useAPIFetchPosts } from '@/hooks/api.js'

2. Replace the Suspense Query Hook with our API Fetch Posts Hook:
export function PostFeed({ featured = false }) {
const posts = useAPIFetchPosts({ featured })
return <PostList posts={posts} />
}

Instead of having implementation details on how we fetch posts from the API, we now only
provide the information that is relevant to the component (whether the posts are featured
or not). The rest is handled internally by the custom API Hook and can be changed later.

3. Edit src/components/post/Post.jsx and remove the following imports:


import { useSuspenseQuery } from '@tanstack/react-query'
import { fetchPost } from '@/api.js'

Replace them with an import of useAPIFetchPost:


import { useAPIFetchPost } from '@/hooks/api.js'

4. Replace the Suspense Query Hook with our API Fetch Posts Hook:
export function Post({ id }) {
const theme = useTheme()
const { title, content, author } = useAPIFetchPost({ id })
return (

5. Edit src/components/post/PostSearchResults.jsx and remove the following imports:


import { useSuspenseQuery } from '@tanstack/react-query'
import { searchPosts } from '@/api.js'

Replace them with an import of useAPISearchPosts:


import { useAPISearchPosts } from '@/hooks/api.js'
274 Building Your Own Hooks

6. Replace the Suspense Query Hook with our API Fetch Posts Hook:
export function PostSearchResults({ query }) {
const posts = useAPISearchPosts({ query })
return <PostList posts={posts} />
}

7. Edit src/components/post/CreatePost.jsx and remove the following imports:


import { useMutation } from '@tanstack/react-query'
import { createPost, queryClient } from '@/api.js'

Replace them with an import of useAPICreatePost:


import { useAPICreatePost } from '@/hooks/api.js'

8. Remove the existing Mutation Hook:


const createPostMutation = useMutation({
mutationFn: createPost,
onSuccess: () => {
queryClient.invalidateQueries(['posts'])
},
})

Replace it with our API Create Post Hook:


const createPost = useAPICreatePost()

9. We can now directly call the createPost function, as follows:


const newPost = { title, content, author: username, featured:
false }
try {
const result = await createPost(post)
clear()
navigate(`/post/${result.id}`)
} catch (err) {
return err
}

10. Start the dev server, and make sure that everything still works like before:
$ npm run dev
Chapter 12 275

Again, refactoring to use custom Hooks has made our components easier to read, allowing us
to focus on the user interaction logic, while our custom Hooks deal with the application logic
internally.

Example code

The example code for this section can be found in the Chapter12/Chapter12_3
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

After creating custom API Hooks, let’s move on to creating a Debounced History State Hook.

Creating a Debounced History State Hook


We are now going to create a slightly more advanced Hook for debounced history state func-
tionality. In Chapter 10, Using Community Hooks, we learned about the History State Hook, which
allowed us to implement undo/redo functionality in the CreatePost component. We then used
a Debounce Hook to avoid storing every single change in the history, allowing us to undo/redo
larger parts of the text instead of a single character at a time. Now, we are going to extract this
combined functionality into a custom Debounced History State Hook.

While this functionality is currently only used in one component, it is a generic feature that could
be used in other components. Also, abstracting this functionality into a separate Hook allows us
to keep the CreatePost component code clean and concise.

Creating the Debounced History State Hook


Let’s now get started extracting the code from the CreatePost component into a Debounced
History State Hook:

1. Copy the Chapter12_3 folder to a new Chapter12_4 folder by executing the following
command:
$ cp -R Chapter12_3 Chapter12_4

2. Open the new Chapter12_4 folder in VS Code.


3. Create a new src/hooks/debouncedHistoryState.js file.
276 Building Your Own Hooks

4. Inside it, import the following:


import { useState, useEffect } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { useHistoryState } from '@uidotdev/usehooks'

5. Define a function that accepts an initial state and a timeout value for the debounce:
export function useDebouncedHistoryState(initialState, timeout) {

6. Now, define the History State Hook:


const { state, set, undo, redo, clear, canUndo, canRedo } =
useHistoryState(initialState)

7. Next, define a State Hook for the actively edited content:


const [content, setContent] = useState(initialState)

8. Then, define a Debounced Callback Hook that will set the value of the History State Hook:
const debounced = useDebouncedCallback((value) =>
set(value), timeout)

9. Add the Effect Hook that we had before in the CreatePost component:
useEffect(() => {
debounced.cancel()
setContent(state)
}, [state, debounced])

Remember, this Effect Hook is used to sync the History State back into the actively edited
content state, meaning that it will change the content of the textbox whenever we trigger
the undo, redo, or clear functionality.

10. Now, define a handler function which sets the content state and starts the debounced
callback:
function handleContentChange(e) {
const { value } = e.target
setContent(value)
debounced(value)
}
Chapter 12 277

11. Finally, return all the values and functions we need from the Hook:
return { content, handleContentChange, undo, redo, clear, canUndo,
canRedo }
}

Now we have a drop-in replacement for the Debounced History State functionality, which we
now use in the CreatePost component, so let’s do it!

Using the Debounced History State Hook


Follow these steps to refactor the CreatePost component to use the Debounced History State Hook:

1. Edit src/components/post/CreatePost.jsx and remove the following highlighted im-


ports:
import { useActionState, useState, useEffect } from 'react'
import { useDebouncedCallback } from 'use-debounce'
import { useHistoryState } from '@uidotdev/usehooks'

2. Add an import of the useDebouncedHistoryState function:


import { useDebouncedHistoryState } from '@/hooks/
debouncedHistoryState.js'

3. Remove all of the following code:


const { state, set, undo, redo, clear, canUndo, canRedo } =
useHistoryState('')
const [content, setContent] = useState('')
const debounced = useDebouncedCallback((value) => set(value), 200)

useEffect(() => {
debounced.cancel()
setContent(state)
}, [state, debounced])

Replace it with the Debounced History State Hook:


const { content, handleContentChange, undo, redo, clear, canUndo,
canRedo } =
useDebouncedHistoryState('', 200)
278 Building Your Own Hooks

4. Remove the following handler function:


function handleContentChange(e) {
const { value } = e.target
setContent(value)
debounced(value)
}

5. Start the dev server and ensure that creating a post and undo/redo still work:
$ npm run dev

That’s all there is to it – now the CreatePost component code is much less cluttered!

Example code

The example code for this section can be found in the Chapter12/Chapter12_4
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

After creating the Debounced History State Hook, let’s move on to learn about testing custom
Hooks.

Testing custom Hooks


Now our blog application fully makes use of Hooks! We even defined custom Hooks for various
functions to make our code more reusable, concise, and easy to read.

When creating custom Hooks, it also makes sense to write unit tests for them to ensure they work
properly, even when we change them later on or add more options. We are going to use Vitest
to write our unit tests. Vitest and Vite go very well together, because Vitest can read and use
Vite configurations. Vitest also offers a Jest-compatible API. Jest is another very popular testing
framework. If you are already familiar with Jest, learning Vitest will be a breeze. Additionally,
Vitest is very fast and well equipped for modern web apps.

However, as a result of the rules of Hooks, we cannot call Hooks from the test functions because
they can only be called inside the body of a functional React component. As we do not want to
create a component specifically for each test, we are going to use the React Testing Library to
test Hooks directly. This library actually creates a test component and provides various utility
functions to interact with Hooks.
Chapter 12 279

In the past, there were two libraries: The React Testing Library and the React Hooks
Testing Library. However, nowadays the React Testing Library already includes
support for rendering and testing Hooks out of the box, so it is the perfect fit for
testing React components and Hooks! The React Hooks Testing Library is deprecated
now, so we will only be using the React Testing Library.

We should particularly write tests for Hooks in the following circumstances:

• When writing libraries that define and export Hooks


• When you have Hooks that are used throughout multiple components
• When a Hook is complex and will thus be difficult to change/refactor later

When you have Hooks that are specific to one component, it is often better to just test the compo-
nent directly. However, testing React components is out of scope of this book. More information
about testing components can be found on the React Testing Library website: https://testing-
library.com/docs/react-testing-library/intro/

Now, let’s get started setting up Vitest and the React Testing Library!

Setting up Vitest and the React Testing Library


Before we can start writing tests for our Hooks, we first need to set up Vitest and the React Testing
Library:

1. Copy the Chapter12_4 folder to a new Chapter12_5 folder by executing the following
command:
$ cp -R Chapter12_4 Chapter12_5

2. Open the new Chapter12_5 folder in VS Code.


3. Install Vitest, the React Testing Library, and jsdom, by executing the following command:
$ npm install --save-exact --save-dev [email protected] @testing-library/
[email protected] [email protected]

jsdom provides an environment to access the DOM in Node.js. Since our tests are not
actually running in a browser, it is necessary to provide such an environment to be able
to render React components and test Hooks.
280 Building Your Own Hooks

4. Edit package.json and add a script for running Vitest:


"scripts": {
"test": "vitest",

5. Finally, edit vite.config.js and add a config for Vitest at the end of the file:
rewrite: (path) => path.replace(/^\/api/, ''),
},
},
},
test: {
environment: 'jsdom',
},
})

Now that we have successfully set up Vitest, we can start testing Hooks!

Testing a simple Hook


First of all, we are going to test a very simple Hook that does not make use of contexts or asyn-
chronous code such as timeouts. To do this, we are going to create a new Hook called useCounter.
Then, we are going to test various parts of the Hook.

Creating the Counter Hook


The Counter Hook is going to provide a current count and functions to increment and reset the
counter. Follow these steps to create it:

1. Create a new src/hooks/counter.js file.


2. Inside it, import the useState function:
import { useState } from 'react'

3. Then, define a Counter Hook, which takes an initialCount as argument:


export function useCounter(initialCount = 0) {

4. Define a State Hook for the count:


const [count, setCount] = useState(initialCount)
Chapter 12 281

5. Now, define a function to increment the count by 1:


function increment() {
setCount((count) => count + 1)
}

6. Next, define a function to reset the counter to the initial count:


function reset() {
setCount(initialCount)
}

7. Return the current count and the two functions:


return { count, increment, reset }
}

Now that we have defined a simple Hook, we can start writing our first tests.

Creating unit tests for the Counter Hook


Let’s now write unit tests for the Counter Hook by following these steps:

1. Create a new src/hooks/counter.test.js file.


2. Inside it, import the describe, test and expect functions from Vitest, as well as the
renderHook and act functions from the React Testing Library:

import { describe, test, expect } from 'vitest'


import { renderHook, act } from '@testing-library/react'

3. Also, import the useCounter function, which we are going to write tests for:
import { useCounter } from './counter.js'

4. Now, we can start defining tests. In Vitest, we can use the describe function to define
a group of tests. The first argument is a name for the group, the second argument is an
options object to configure the test (we leave this as an empty object), and the third ar-
gument is a function in which we can define our various tests. Here, we create a group of
tests for the Counter Hook, so let’s call it that:
describe('Counter Hook', {}, () => {
282 Building Your Own Hooks

5. Inside the group, we can now define our tests. To define a test, we use the test function.
The first argument is the name of the test, the second argument the test options, and the
third argument is a function to be executed as the test. In our first test, we check if the
Hook returns 0 by default:
test('should return 0 by default', {}, () => {

6. In this test, we use the renderHook function to simulate the Hook being rendered in a
React component. It returns an object for us, which includes a result:
const { result } = renderHook(() => useCounter())

7. We can now access the count by getting it from the result.current object, and check
if it is 0:
expect(result.current.count).toBe(0)
})

With the expect function we can do tests on values. It works as follows: expect(actualValue).
toBe(expectedValue). If the actualValue matches the expectedValue, the test will suc-
ceed. Otherwise, it will fail.

There are many kinds of matchers to be used with expect – toBe is just one
of them! For a full list of matchers, please check out the Vitest API documen-
tation: https://vitest.dev/api/expect.html

If you have worked with Jest before, you will notice that the Vitest API is fully
compatible with it, so all these functions will be familiar to you.

8. Next, let’s define a test that checks if the initialCount argument works:
test('should initially return initial count', {}, () => {
const { result } = renderHook(() => useCounter(123))
expect(result.current.count).toBe(123)
})

9. Now, we define a test that checks if the increment function increments the counter:
test('should increment counter when increment() is called', {}, ()
=> {
const { result } = renderHook(() => useCounter(0))
Chapter 12 283

10. We can use the act function to trigger an action from the Hook. This function tells the
React Testing Library that something is being triggered inside the Hook, causing the
result.current value from the renderHook function to be updated:

act(() => result.current.increment())

11. Then, we can check if the new count is 1:


expect(result.current.count).toBe(1)
})

12. Next, let’s do a test that simulates the initialCount passed to the Counter Hook being
changed by a prop change of a React component:
test('should reset to initial value', {}, () => {

13. To simulate a React prop, we simply define a variable, then we define the Hook:
let initial = 0
const { result, rerender } = renderHook(() =>
useCounter(initial))

14. We can now change the prop by changing the variable and manually triggering a re-ren-
der of the React component, by using the rerender function returned from renderHook:
initial = 123
rerender()

As we have learned before, the React Testing Library creates a dummy component, which
is used for testing the Hook. We can force this dummy component to rerender to simulate
what would happen when props change in a real component.

15. Now, we call the reset function and check if the count was reset to the new initial count:
act(() => result.current.reset())
expect(result.current.count).toBe(123)
})
})

16. Run the tests, by executing the following command:


$ npm test
284 Building Your Own Hooks

Remember, for special scripts, such as start and test, we do not need to execute
npm run test; we can simply execute npm test.

The following screenshot shows the result after executing npm test:

Figure 12.1 – Running Vitest in watch mode

You will see that Vitest automatically runs the watch mode. This means that it will wait for file
changes and automatically re-run tests for you. You can just keep running it in that mode through-
out the rest of this chapter to see your tests executing as you write them.

Testing the Theme Hook


Using the React Hooks Testing Library, we can also test more complex Hooks, such as those Hooks
that make use of context. To test Hooks that make use of context, we first have to create a context
wrapper, and then we can test the Hook.

Let’s get started writing tests for the Theme Hook now:

1. Create a new src/hooks/theme.test.jsx file. Please note that the file extension needs
to be .jsx, not just .js, as we will be using JSX in that file.
2. Inside it, import the relevant functions from Vitest, the renderHook function, the
ThemeContext, and the useTheme function:

import { describe, test, expect } from 'vitest'


import { renderHook } from '@testing-library/react'
Chapter 12 285

import { ThemeContext } from '@/contexts/ThemeContext.js'


import { useTheme } from './theme.js'

3. Now, define a ThemeContextWrapper component, which will set up the context provider
for testing:
function ThemeContextWrapper({ children }) {

The wrapper accepts children as a prop, which is a special property of React components.
It will contain all other components defined inside the wrapper, such as <ThemeContext
Wrapper>{children}</ThemeContextWrapper>.

4. Inside the wrapper component, define the context provider and a value for primaryColor:
return (
<ThemeContext.Provider value={{ primaryColor: 'deepskyblue' }}>
{children}
</ThemeContext.Provider>
)
}

5. Now, we can begin writing tests for the Theme Hook. We start by creating a test group:
describe('Theme Hook', {}, () => {

6. Inside the group, we define a test which checks for the primary color:
test('should return the primaryColor defined by the context',
{}, () => {

7. Then render the Hook, passing the wrapper component to the renderHook function:
const { result } = renderHook(() => useTheme(), {
wrapper: ThemeContextWrapper,
})

8. Now, check if the primary color is the same as we defined in the wrapper component:
expect(result.current.primaryColor).toBe('deepskyblue')
})
})
286 Building Your Own Hooks

9. If you kept Vitest running in watch mode, you should see it successfully executing the test
we just wrote! If not, start Vitest again by executing the following command:
$ npm test

The following image shows how Vitest automatically executes our newly defined test in watch
mode:

Figure 12.2 – Vitest automatically executing our newly defined test

Now that we have successfully written a test for the Theme Hook, let’s move on to the slightly
more complex User Hook.

Testing the User Hook


The User Hook internally uses a Local Storage Hook now. Thankfully, the jsdom environment
handles mocking the LocalStorage API for us already, so we do not need to do any setup for that.

Let’s get started writing tests for the User Hook now:

1. Create a new src/hooks/user.test.js file.


2. Inside it, import the relevant functions from Vitest, as well as the renderHook, act and
useUser functions:

import { describe, test, expect } from 'vitest'


import { renderHook, act } from '@testing-library/react'
import { useUser } from './user.js'

3. Then, define a test group for the User Hook:


describe('User Hook', {}, () => {
Chapter 12 287

4. For our first test, we ensure that the user is not logged in by default:
test('should not be logged in by default', {}, () => {
const { result } = renderHook(() => useUser())
expect(result.current.isLoggedIn).toBe(false)
expect(result.current.username).toBe(null)
})

5. Then, we test the registration functionality:


test('should be logged in after registering', {}, () => {
const { result } = renderHook(() => useUser())
act(() => result.current.register('testuser'))
expect(result.current.isLoggedIn).toBe(true)
expect(result.current.username).toBe('testuser')
})

6. Next, we test the login functionality:


test('should be logged in after logging in', {}, () => {
const { result } = renderHook(() => useUser())
act(() => result.current.login('testuser'))
expect(result.current.isLoggedIn).toBe(true)
expect(result.current.username).toBe('testuser')
})

7. For the final test, we make two actions, calling login first, then logout, and then we check
if the user is logged out:
test('should be logged out after logout', {}, () => {
const { result } = renderHook(() => useUser())
act(() => result.current.login('testuser'))
act(() => result.current.logout())
expect(result.current.isLoggedIn).toBe(false)
expect(result.current.username).toBe(null)
})
})

You will see that Vitest executed all our tests and they are all passing! Now, let’s move on to the
Debounced History State Hook.
288 Building Your Own Hooks

Testing asynchronous Hooks


Sometimes we need to test Hooks that perform asynchronous actions. This means that we need
to wait a certain amount of time until we check the result.

To write tests for these kind of Hooks, we can use the waitFor function from the React Testing
Library. This function can be used to wait for the condition to be met, instead of trying to match
it immediately. It can thus be used to test asynchronous operations in React components and
Hooks. If the condition fails to match even after a certain amount of time (which can be specified
with an optional timeout parameter), the test will fail.

Earlier in this chapter, we created the Debounced History State Hook, which only stores changes
in the history after a certain amount of time, thus making it an asynchronous Hook. We are now
going to use the waitFor function to test the debouncing in the Debounced History State Hook.

Follow these steps to get started:

1. Create a new src/hooks/debouncedHistoryState.test.js file.


2. Inside it, import the relevant functions from Vitest, as well as the renderHook, act and
waitFor functions from the React Testing Library, as well as the useDebouncedHistoryState
function:
import { describe, test, expect } from 'vitest'
import { renderHook, act, waitFor } from '@testing-library/react'
import { useDebouncedHistoryState } from './debouncedHistoryState.
js'

3. Define a test group and a first test, which just checks the initial value:
describe('Debounced History State Hook', {}, () => {
test('should return initial state as content', {}, () => {
const { result } = renderHook(() => useDebouncedHistoryState('',
10))
expect(result.current.content).toBe('')
})

4. Now, we define a test that checks if the content is updated immediately:


test('should update content immediately', {}, () => {
const { result } = renderHook(() => useDebouncedHistoryState('',
10))
act(() =>
Chapter 12 289

result.current.handleContentChange({ target: {
value: 'new content' } }),
)
expect(result.current.content).toBe('new content')
})

5. For the final test, we check if the Hook updates the history only after a debounce:
test('should only update history state after debounce', {},
async () => {

6. In this test, we first define the Hook:


const { result } = renderHook(() => useDebouncedHistoryState('',
10))

We kept the debounce timeout as 10ms to avoid unnecessarily slowing down our test.

7. Now, we trigger a content update:


act(() =>
result.current.handleContentChange({ target: {
value: 'new content' } }),
)

8. Before debouncing, the canUndo value should be false, as there is nothing stored in the
history state yet:
expect(result.current.canUndo).toBe(false)

9. Now we use waitFor to wait for the canUndo value to be true, which should happen after
the debounce timeout (10ms):
await waitFor(() => {
expect(result.current.canUndo).toBe(true)
})
})
})

Vitest will automatically run our new tests and we can see that they all succeed!
290 Building Your Own Hooks

In this case, we have a very simple timeout. However, there may be more advanced
cases where we have to wait for a longer time. For more control over dates and timers
when testing Hooks, you can mock the system time using fake timers in Vitest. Check
out the mocking guide on the official Vitest docs for more information: https://
vitest.dev/guide/mocking.html#dates

We are not testing the undo/redo/clear functionality, because those come from the History State
Hook from the useHooks library, so it is out of scope for our custom Hook. In most cases, it is
enough to only test the logic that we added in our own implementation.

Since the API Hooks are mostly wrappers for TanStack Query Hooks and do not add their own
logic, it also does not make much sense to write tests for them.

Running all tests


To verify that all our tests are succeeding now, let’s quit the Vitest watch mode by pressing the q
key. Then, run Vitest again by executing the following command:
$ npm test

As we can see, Vitest executed all our tests again, and they are all passing:

Figure 12.3 – All our tests are passing!


Chapter 12 291

Example code

The example code for this section can be found in the Chapter12/Chapter12_5
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Summary
In this chapter, we first learned how to extract custom Hooks from existing code in our blog app.
We defined the Theme Hook to easily access the context, then we defined the User Hook, which
manages user state and provides functions to register/login/logout. Then, we created API Hooks
and a more advanced Hook for the debounced history state functionality. Finally, we learned
about writing tests for our custom Hooks using Vitest and the React Testing Library.

Knowing when and how to extract custom Hooks is a very important skill in React development.
In a larger project, you are probably going to define many custom Hooks, specifically tailored to
your project’s needs. Custom Hooks can also make it easier to maintain the application, as we
only need to adjust functionality in one place. Testing custom Hooks is very important, because
if we refactor our custom Hooks later on, we want to make sure that they still work properly.

In the next chapter, we are going to learn how to migrate from class components to a Hook-based
system. We first create a small project using class components and then we replace them with
Hooks, taking a closer look at the differences between the two solutions.

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. How can we extract a custom Hook from existing code?


2. What is the advantage of creating custom Hooks?
3. When should we extract functionality into a custom Hook?
4. How do we use custom Hooks?
5. Which library can we use to test custom Hooks?
6. How are actions performed by Hooks tested?
7. How can we test Hooks that make use of React Context?
8. How can we test Hooks that perform asynchronous operations?
292 Building Your Own Hooks

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• Guide on “Reusing Logic with Custom Hooks” in the official React docs: https://react.
dev/learn/reusing-logic-with-custom-hooks
• Vitest documentation: https://vitest.dev/
• React Testing Library documentation: https://testing-library.com/docs/react-
testing-library/intro/

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
13
Migrating from React
Class Components
In the previous chapter, we learned how to build our own Hooks by extracting custom Hooks
from existing code. Then, we used our custom Hooks in the blog app. Finally, we learned how
to write tests for Hooks, using the React Testing Library, and wrote tests for our custom Hooks.

In this chapter, we are going to start by implementing a todo app using React class components.
Then, we are going to learn how to migrate an existing React class component application to
Hooks. Seeing the differences between Hooks and class components in practice will deepen our
understanding of the trade-offs of using either solution.

The following topics will be covered in this chapter:

• Handling state with React class components


• Migrating from React class components
• Trade-offs of React class components vs React Hooks

Technical requirements
A fairly recent version of Node.js should already be installed. The Node Package Manager (npm)
also needs to be installed (it should come with Node.js). For more information on how to install
Node.js, please check out their official website: https://nodejs.org/.

We are going to use Visual Studio Code (VS Code) for the guides in this book, but everything
should work similarly in any other editor. For more information on how to install VS Code, please
refer to their official website: https://code.visualstudio.com
294 Migrating from React Class Components

In this book, we use the following versions:

• Node.js v22.14.0
• npm v10.9.2
• Visual Studio Code v1.97.2

The versions mentioned in the preceding list are the ones used in the book. While installing a
newer version should not be an issue, please note that certain steps might work differently on a
newer version. If you are having an issue with the code and steps provided in this book, please
try using the mentioned versions.

You can find the code for this chapter on GitHub: https://github.com/PacktPublishing/Learn-
React-Hooks-Second-Edition/tree/main/Chapter13

It is highly recommended that you write the code on your own. Do not simply run
the code examples that are provided with the book. It is important to write the code
yourself to be able to learn and understand it properly. However, if you run into any
issues, you can always refer to the code example.

Handling state with React class components


Before we start migrating from class components to Hooks, we are going to create a small todo
list app using class components. Afterward, we are going to turn these class components into
function components using Hooks. Finally, we are going to compare the two solutions.

Designing the app structure


As we did before with the blog app, we are going to start by thinking about the basic structure of
our todo app. Here, we are going to need the following:

• A header
• A way to add new todo items
• A way to show all todo items in a list
• A filter for the todo items
Chapter 13 295

It is always a good idea to start with a mock-up. So let’s begin:

1. Based on the previous list of structural elements, start by drawing a mock-up of an in-
terface for our todo app:

Figure 13.1 – Mock-up of our todo app

2. Next, map out the simple components by drawing around the UI elements and giving
them a name, in a similar way to how we did it with the blog app:

Figure 13.2 – Mapping out simple components in our app mock-up


296 Migrating from React Class Components

3. Now, map out the container components, which group together the simple components:

Figure 13.3 – Mapping out container components in our app mock-up

As we can see, we are going to need the following components:


• App
• Header
• AddTodo
• TodoList
• TodoItem
• TodoFilter (+ TodoFilterItem)

The TodoList component makes use of a TodoItem component, which is used to show an item,
with a checkbox to complete and a button to remove it. The TodoFilter component internally
uses a TodoFilterItem component to show the various filters.

Initializing the project


We are going to use the barebones Vite app from Chapter 1, Introducing React and React Hooks to
create a new project. Let’s initialize the project now:

1. Copy the Chapter01_3 folder to a new Chapter13_1 folder, as follows:


$ cp -R Chapter01_3 Chapter13_1

2. Open the new Chapter13_1 folder in VS Code.


3. Delete the current src/App.jsx file, as we are going to create a new one in the next step.
Chapter 13 297

With our project initialized, we can start defining the app structure.

Defining the app structure


We already know what the basic structure of our app is going to be like from the mock-ups, so
let’s start by defining the App component:

1. Create a new src/App.jsx file.


2. Inside it, import React, and all the container components of our app:
import React from 'react'
import { Header } from './Header.jsx'
import { AddTodo } from './AddTodo.jsx'
import { TodoList } from './TodoList.jsx'
import { TodoFilter } from './TodoFilter.jsx'

3. Next, define the App component as a class component. In a class component, we need to
define a render method to render our component:
export class App extends React.Component {
render() {
return (
<div style={{ width: '400px' }}>
<Header />
<AddTodo />
<hr />
<TodoList />
<hr />
<TodoFilter />
</div>
)
}
}

The App component defines the basic structure of our app. It will consist of a header, a way to
add new todo items, a list of todo items, and a filter.
298 Migrating from React Class Components

Defining the components


Now, we are going to define the following components as static components:
• Header
• AddTodo
• TodoList
• TodoItem
• TodoFilter

Later in this chapter, we are going to add dynamic functionality to them.

Defining the Header component


We are going to start with the Header component, as it is the most simple out of all the components:

1. Create a new src/Header.jsx file.

In this project, we decided to put all components directly in the src/ folder,
as there are only a handful of components anyway.

2. Inside the newly created file, import React, and define the class component with a render
method:
import React from 'react'

export class Header extends React.Component {


render() {
return <h1>ToDo</h1>
}
}

Defining the AddTodo component


Next, we are going to define the AddTodo component, which renders an input field and a button:

1. Create a new src/AddTodo.jsx file.


2. Inside it, import React and define the class component:
import React from 'react'
Chapter 13 299

export class AddTodo extends React.Component {


render() {

3. In the render method, we return a form that contains an input field and a submit button:
return (
<form>
<input
type='text'
placeholder='enter new task...'
style={{ width: '350px' }}
/>
<input
type='submit'
style={{ float: 'right' }}
value='add'
/>
</form>
)
}
}

Defining the TodoList component


Now, we define the TodoList component, which is going to make use of the TodoItem component
(we will create that one in the next step). For now, we are going to statically define the data for
two todo items in an array and use it to render TodoItem components.

Let’s start defining the TodoList component:

1. Create a new src/TodoList.jsx file.


2. Inside it, import React and the TodoItem component:
import React from 'react'
import { TodoItem } from './TodoItem.jsx'

3. Then, define the class component:


export class TodoList extends React.Component {
render() {
300 Migrating from React Class Components

4. In the render method, we statically define two todo items:


const items = [
{ id: 1, title: 'Finish React Hooks book', completed: true },
{ id: 2, title: 'Promote the book', completed: false },
]

5. Finally, we are going to render the items using the map function:
return items.map((item) => <TodoItem {...item} key={item.id} />)
}
}

We define the key property last, in order to avoid overwriting it with the spreading
of the item object.

Defining the TodoItem component


After defining the TodoList component, we are now going to define the TodoItem component, in
order to render single items. Let’s start defining the TodoItem component:

1. Create a new src/TodoItem.jsx file.


2. Inside it, import React and define the class component:
import React from 'react'

export class TodoItem extends React.Component {


render() {

3. Now, we are going to use destructuring to get the title and completed props:
const { title, completed } = this.props

4. Finally, we are going to render a checkbox, the title and a button to delete the item:
return (
<div style={{ width: '400px', height: '25px' }}>
<input type='checkbox' checked={completed} />
{title}
<button type='button' style={{ float: 'right' }}>x</button>
</div>
Chapter 13 301

)
}
}

Defining the TodoFilter component


Finally, we are going to define the TodoFilter component. In the same file, we are also going to
define the TodoFilterItem component, as they are tightly coupled to each other. So, let’s start
implementing these components:

1. Create a new src/TodoFilter.jsx file.


2. Inside it, import React and define a TodoFilterItem class component:
import React from 'react'

export class TodoFilterItem extends React.Component {


render() {

3. We use destructuring to get the name prop:


const { name } = this.props

4. Then, we return a button to select the filter:


return <button type='button'>{name}</button>
}
}

5. Finally, we define the actual TodoFilter component, which is going to render three
TodoFilterItem components:

export class TodoFilter extends React.Component {


render() {
return (
<div>
<TodoFilterItem name='all' />
<TodoFilterItem name='active' />
<TodoFilterItem name='completed' />
</div>
)
}
}
302 Migrating from React Class Components

Now that we finished implementing all static components, we can start the app.

Starting the app


To start the app, run the following command:
$ npm run dev

Open the URL in a browser and you will see that the app looks just like our mock-up!

However, the app is completely static, and when we click on anything, nothing happens. In the
next step, we are going to make our app dynamic.

Implementing dynamic code


Now that we have defined all of the static components, our app should look just like the mock-up.
The next step is to implement dynamic code using React state, life cycle, and handler functions.

In this section, we are going to:

• Define a mock API


• Define the StateContext
• Make the App component dynamic
• Make the AddTodo component dynamic
• Make the TodoList component dynamic
• Make the TodoItem component dynamic
• Make the TodoFilter component dynamic

Let’s get started.

Defining a mock API


First of all, we are going to define an API that will fetch todos. In our case, we are simply going to
return an array of todo items, after a short delay, to simulate a network request, which usually
takes some time to resolve.

Let’s start implementing the mock API now:

1. Create a new src/api.js file.


2. Inside it, define and export a function that returns items after a short delay:
const mockItems = [
{ id: 1, title: 'Finish React Hooks book', completed: true },
Chapter 13 303

{ id: 2, title: 'Promote the book', completed: false },


]

export function fetchTodos() {


return new Promise((resolve) => {
setTimeout(() => resolve(mockItems), 100)
})
}

Here we are using the Promise API to resolve with an array of mock items after waiting
for 100ms to simulate a real API request.

Next, we are going to define a context that will keep our current list of todos.

Defining the StateContext


We are now going to define the context that will keep the current state of our todo app:

1. Create a new src/StateContext.js file.


2. Inside it, import the createContext function from React:
import { createContext } from 'react'

3. Then, define and export a context that contains an empty array:


export const StateContext = createContext([])

Now that we have a context where we can store our array of todo items, let’s move on to making
the components dynamic.

Making the App component dynamic


We are going to start by making the App component dynamic, adding functionality to fetch, add,
toggle, filter, and remove todo items. To do this:

1. Edit src/App.jsx and import the StateContext and the fetchTodos function:
import { StateContext } from './StateContext.js'
import { fetchTodos } from './api.js'

2. Next, we are going to modify our App class code, adding a constructor to it, which will
set the initial state:
export class App extends React.Component {
constructor(props) {
304 Migrating from React Class Components

3. In this constructor, we first need to call super to make sure that the parent class (React.
Component) constructor gets called and the component gets initialized properly:

super(props)

4. Now, we can set the initial state with this.state. Initially, there are no todo items, and
the filter is set to all:
this.state = { todos: [], filteredTodos: [], filter: 'all' }
}

5. Then, we define the componentDidMount lifecycle method, which is going to load todo
items when the component first renders:
componentDidMount() {
this.loadTodos()
}

6. Next, we are going to define the loadTodos method, which in our case, is simply going
to set the state, as we are not going to connect this simple app to a backend. We are also
going to call this.filterTodos() to update the filteredTodos array after fetching:
async loadTodos() {
const todos = await fetchTodos()
this.setState({ todos })
this.filterTodos()
}

We will define the filterTodos method later in this section.

7. Next, we define the addTodo method, which creates a new item and adds it to the array:
addTodo(title) {
const { todos } = this.state
const newTodo = { id: Date.now(), title, completed: false }
this.setState({ todos: [newTodo, ...todos] })
this.filterTodos()
}

To keep things simple, we are just using Date.now() to generate a unique


id for each todo item.
Chapter 13 305

8. We now define a toggleTodo method, which uses the map function to find and modify a
certain todo item:
toggleTodo(id) {
const { todos } = this.state
const updatedTodos = todos.map(item => {
if (item.id === id) {
return { ...item, completed: !item.completed }
}
return item
})
this.setState({ todos: updatedTodos })
this.filterTodos()
}

9. Now, define the removeTodo method, which uses the filter function to find and remove
a certain todo item:
removeTodo(id) {
const { todos } = this.state
const updatedTodos = todos.filter((item) => item.id !== id)
this.setState({ todos: updatedTodos })
this.filterTodos()
}

10. Then, define a method to apply different filters to todo items, such as active, completed
and all:
applyFilter(todos, filter) {
switch (filter) {
case 'active':
return todos.filter((item) => item.completed === false)
case 'completed':
return todos.filter((item) => item.completed === true)
case 'all':
default:
return todos
}
}
306 Migrating from React Class Components

11. Now, we can define the filterTodos method, which is going to call the applyFilter
method and update the filteredTodos and the filter state:
filterTodos(filterArg) {
this.setState(({ todos, filter }) => {
const newFilter = filterArg ?? filter
return {
filter: newFilter,
filteredTodos: this.applyFilter(todos, newFilter),
}
})
}

The nullish coalescing (??) operator is used to fall back to a different value when the
value on the left hand side is undefined or null. Unlike the || operator, the ?? operator
only triggers for undefined/null values, not for falsy values like 0, '', or false.

We are using filterTodos to re-filter todos after adding/removing, as well


as changing the filter. To allow for both functionalities to work properly, we
need to check if the filter argument filterArg was passed. If not, we fall
back to the current filter value from the state object.

12. Then, we adjust the render method and use the state to provide a value for the StateContext.
We also pass certain methods down to the components:
render() {
const { filter, filteredTodos } = this.state
return (
<StateContext.Provider value={filteredTodos}>
<div style={{ width: '400px' }}>
<Header />
<AddTodo addTodo={this.addTodo} />
<hr />
<TodoList toggleTodo={this.toggleTodo} removeTodo={this.
removeTodo} />
<hr />
<TodoFilter filter={filter} filterTodos={this.filterTodos}
/>
Chapter 13 307

</div>
</StateContext.Provider>
)
}
}

13. Finally, we need to re-bind this to the class, so that we can pass the methods to our com-
ponents without the this context changing. Adjust the constructor method by rebinding
the this context of all methods, as follows:
constructor(props) {
super(props)
this.state = { todos: [], filteredTodos: [], filter: 'all' }
this.loadTodos = this.loadTodos.bind(this)
this.addTodo = this.addTodo.bind(this)
this.toggleTodo = this.toggleTodo.bind(this)
this.removeTodo = this.removeTodo.bind(this)
this.filterTodos = this.filterTodos.bind(this)
}

Now, the App component can dynamically fetch, add, toggle, remove and filter todo items. As
we can see, when we use class components, we need to re-bind the this context of the handler
functions to the class. Otherwise, we would get an error when we call the methods, because
their this context is not bound to the class anymore and thus they cannot call methods such as
this.setState.

Making the AddTodo component dynamic


After making the App component dynamic, it is time to make all of our other components dynamic,
too. We are going to start from the top, with the AddTodo component:

1. Edit src/AddTodo.jsx and define a constructor, which sets the initial state for the input
field:
export class AddTodo extends React.Component {
constructor(props) {
super(props)
this.state = {
input: '',
}
}
308 Migrating from React Class Components

2. Then, define a method for handling changes in the input field:


handleInput(e) {
this.setState({ input: e.target.value })
}

3. Now, define a method to handle submission of the form:


handleSubmit(e) {
e.preventDefault()
const { input } = this.state
const { addTodo } = this.props
if (input) {
addTodo(input)
this.setState({ input: '' })
}
}

4. Next, we can assign the input value, as well as the input and submit handlers, to the rel-
evant components. Additionally, we’ll disable the button when the input field is empty:
render() {
const { input } = this.state
return (
<form onSubmit={this.handleSubmit}>
<input
type='text'
placeholder='enter new task...'
style={{ width: '350px' }}
value={input}
onChange={this.handleInput}
/>
<input
type='submit'
style={{ float: 'right' }}
value='add'
disabled={!input}
/>
</form>
)
Chapter 13 309

}
}

5. Finally, we need to adjust the constructor by re-binding the this context for all of the
handler methods, as follows:
constructor(props) {
super(props)
this.state = {
input: '',
}
this.handleInput = this.handleInput.bind(this)
this.handleSubmit = this.handleSubmit.bind(this)
}

Now, the AddTodo component will show a disabled button as long as no text is entered. Once we
enter some text and click the button, it will trigger the addTodo method from the App component.

Making the TodoList component dynamic


The next component in our todo app is the TodoList component. Here, we just need to get the
todo items from the StateContext. To do this:

1. Edit src/TodoList.jsx and import the StateContext:


import { StateContext } from './StateContext.js'

2. Then, we set the contextType to the StateContext, which will allow us to use the context
via this.context:
export class TodoList extends React.Component {
static contextType = StateContext

With class components, if you want to use multiple contexts, you have to
use the StateContext.Consumer component, as follows:
<StateContext.Consumer>
{value => <div>State is: {value}</div>}
</StateContext.Consumer>

As you can imagine, using multiple contexts like this will result in a very deep
component tree, and the code will be hard to read and refactor.
310 Migrating from React Class Components

3. Now we can get the items from this.context, instead of statically defining them:
render() {
const items = this.context

4. Finally, we pass all props down to the TodoItem component, so that we can use the
removeTodo and toggleTodo methods there:

return items.map((item) => (


<TodoItem {...item} {...this.props} key={item.id} />
))
}
}

Now the TodoList component gets the items from the StateContext instead of statically defining
them. Next, let’s move on to making the TodoItem component dynamic.

Making the TodoItem component dynamic


Now that we have passed on the removeTodo and toggleTodo methods as props to the TodoItem
component, we can implement these features there and make the TodoItem component dynamic
too:

1. Edit src/TodoItem.jsx and define handler methods for the toggleTodo and removeTodo
functions:
handleToggle() {
const { toggleTodo, id } = this.props
toggleTodo(id)
}

handleRemove() {
const { removeTodo, id } = this.props
removeTodo(id)
}

2. Then, we assign the handler functions to the checkbox and button, respectively:
render() {
const { title, completed } = this.props

return (
Chapter 13 311

<div style={{ width: '400px', height: '25px' }}>


<input
type='checkbox'
checked={completed}
onChange={this.handleToggle}
/>
{title}
<button
type='button'
style={{ float: 'right' }}
onClick={this.handleRemove}
>
x
</button>
</div>
)
}
}

3. Finally, we need to re-bind the this context for the handler methods. Define a new
constructor method, as follows:

export class TodoItem extends React.Component {


constructor(props) {
super(props)
this.handleToggle = this.handleToggle.bind(this)
this.handleRemove = this.handleRemove.bind(this)
}

Now the TodoItem component properly triggers the toggle and remove methods. Next, we are
going to make the TodoFilter component dynamic.

Making the TodoFilter component dynamic


Lastly, we are going to use the filterTodos method to dynamically filter our todo item list:

1. Edit src/TodoFilter.jsx and, in the TodoFilter class component, pass all props down
to the TodoFilterItem component:
export class TodoFilter extends React.Component {
render() {
312 Migrating from React Class Components

return (
<div>
<TodoFilterItem {...this.props} name='all' />
<TodoFilterItem {...this.props} name='active' />
<TodoFilterItem {...this.props} name='completed' />
</div>
)
}
}

2. In the same file, add a handleFilter method to the TodoFilterItem class component:
export class TodoFilterItem extends React.Component {
handleFilter() {
const { name, filterTodos } = this.props
filterTodos(name)
}

3. Then, get the filter prop, disable the button with the currently selected filter, and call
this.handleFilter when the button is clicked:

render() {
const { name, filter = 'all' } = this.props
return (
<button type='button' disabled={filter === name}
onClick={this.handleFilter}>
{name}
</button>
)
}
}

4. Finally, we define a constructor method for the TodoFilterItem class component and
re-bind the this context of the handler function:
export class TodoFilterItem extends React.Component {
constructor(props) {
super(props)
this.handleFilter = this.handleFilter.bind(this)
}
Chapter 13 313

Now the TodoFilter component triggers the filter handler function to change the filter. Our
whole app is dynamic now, and we can use all of its functionalities!

Starting the app


At this point, we have finished making all components dynamic and can start the app, as follows:
$ npm run dev

Open the URL in a browser, try adding/toggling/removing/filtering todos, and you will see that
everything works as expected!

Figure 13.4 – Showing only completed items in our todo app

Example code

The example code for this section can be found in the Chapter13/Chapter13_1
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Now that our app works well with React class components, let’s use it to learn how to migrate
an existing app to React Hooks.

Migrating from React class components


After setting up our example project with React class components, we are now going to migrate
this project to React Hooks. We are going to show how to migrate side effects, such as fetching
todos when the component mounts, as well as migrating the state management to a Hook based
solution.
314 Migrating from React Class Components

In this section, we are going to migrate the following components:


• TodoItem
• TodoList
• TodoFilterItem
• TodoFilter
• AddTodo
• App

Before we do that, please create a new folder for the migrated project, as follows:

1. Copy the Chapter13_1 folder to a new Chapter13_2 folder, as follows:


$ cp -R Chapter13_1 Chapter13_2

2. Open the new Chapter13_2 folder in VS Code.

Now, we can get started migrating the components.

Migrating the TodoItem component


One of the simplest components to migrate is the TodoItem component. It does not use any state
or side effects, so we can simply convert it to a function component.

Let’s start migrating the TodoItem component now:

1. Edit src/TodoItem.jsx and delete all of the existing code.


2. Now, we define a function component which accepts title, completed, id, toggleTodo
and removeTodo props:
export function TodoItem({ title, completed, id, toggleTodo,
removeTodo }) {

3. Then, we define two handler functions to toggle and remove todo items:
function handleToggle() {
toggleTodo(id)
}

function handleRemove() {
removeTodo(id)
}
Chapter 13 315

4. Finally, we render the component:


return (
<div style={{ width: '400px', height: '25px' }}>
<input type='checkbox' checked={completed}
onChange={handleToggle} />
{title}
<button type='button' style={{ float: 'right' }}
onClick={handleRemove}>
x
</button>
</div>
)
}

Try to keep your function components small, and combine them by creating new
function components that use other function components (this pattern is called
composition). It is always a good idea to have many small components, rather than
one large component. They are much easier to maintain, reuse, and refactor.

As you can see, function components do not require us to re-bind this, or to define constructors
at all. Also, we do not need to destructure from this.props multiple times in multiple places. We
can simply destructure all props in the function definition.

If you run the app now, you will see that it still works. React actually lets you combine
function components with class components, so there is no need to migrate the whole
codebase at once, you can migrate certain parts at a time. React class components
can even render function components that use Hooks. The only limitation is that
we cannot use Hooks in class components. Therefore, we need to migrate a whole
component at a time.

Now, let’s move on to migrating the TodoList component.

Migrating the TodoList component


The TodoList component renders multiple TodoItem components. Here, we used a context, which
means that we can now use a Context Hook.
316 Migrating from React Class Components

Let’s migrate the TodoList component now:

1. Edit src/TodoList.jsx and delete all of the existing code.


2. Import the following:
import { useContext } from 'react'
import { StateContext } from './StateContext.js'
import { TodoItem } from './TodoItem.jsx'

3. Define a function component. In this case, we do not destructure the props, but simply
get the whole object:
export function TodoList(props) {

4. Now, define the Context Hook:


const items = useContext(StateContext)

5. Finally, return the list of rendered items:


return items.map((item) => <TodoItem {...item} {...props}
key={item.id} />)
}

As we can see, using contexts with Hooks is much more straightforward. We can simply call a
function (the Context Hook), and use the return value. No magical assignment of this.context!

Next, let’s migrate the TodoFilter component.

Migrating the TodoFilter component


The TodoFilter component does not use any Hooks. However, we are going to replace the
TodoFilterItem and TodoFilter class components with two function components – one for
TodoFilterItem, and one for the TodoFilter component:

1. Edit src/TodoFilter.jsx and delete all of the existing code.


2. Define the TodoFilterItem component, as follows:
export function TodoFilterItem({ name, filterTodos, filter = 'all'
}) {
function handleFilter() {
filterTodos(name)
}
Chapter 13 317

return (
<button type='button' disabled={filter === name}
onClick={handleFilter}>
{name}
</button>
)
}

3. Next, define the TodoFilter component, as follows:


export function TodoFilter(props) {
return (
<div>
<TodoFilterItem {...props} name='all' />
<TodoFilterItem {...props} name='active' />
<TodoFilterItem {...props} name='completed' />
</div>
)
}

As we can see, the TodoFilter component statically renders three TodoFilterItem components,
which are used to toggle the filter between showing all, active or completed todo items. Next,
let’s migrate the AddTodo component.

Migrating the AddTodo component


For the AddTodo component, we are going to use a State Hook to handle the input field state.

Let’s migrate the AddTodo component now:

1. Edit src/AddTodo.jsx and delete all of the existing code.


2. Import the useState function:
import { useState } from 'react'

3. Define the AddTodo component, which accepts an addTodo function as prop:


export function AddTodo({ addTodo }) {

4. Next, define a State Hook for the input field state:


const [input, setInput] = useState('')
318 Migrating from React Class Components

5. Now, define the handler functions:


function handleInput(e) {
setInput(e.target.value)
}

function handleSubmit(e) {
e.preventDefault()
if (input) {
addTodo(input)
setInput('')
}
}

6. Finally, render the input field and button:


return (
<form onSubmit={handleSubmit}>
<input
type='text'
placeholder='enter new task...'
style={{ width: '350px' }}
value={input}
onChange={handleInput}
/>
<input
type='submit'
style={{ float: 'right' }}
value='add'
disabled={!input}
/>
</form>
)
}

As we can see, using the State Hook makes state management much simpler. We can define a sep-
arate value and setter function for each state value, instead of having to deal with a single state
object. Additionally, we do not need to destructure from this.state all the time. As a result, our
code is much more clean and concise.
Chapter 13 319

Now, let’s move on to migrating the App component.

Migrating the state management and App component


At last, all that is left to do is migrate the App component. Then, our whole todo app will be mi-
grated to React Hooks. Here, we are going to use a Reducer Hook to manage the state, an Effect
Hook to fetch todos when the component mounts, and a Memo Hook to store the filtered todos list.

In this section we are going to:

• Define the actions


• Define the reducers
• Migrate the App component

Let’s get started.

Defining the actions


Our app is going to accept five actions, let’s first take a moment to think about what they will
look like:

• LOAD_TODOS: To load a new list of todo items

{ type: 'LOAD_TODOS', todos: [] }

• ADD_TODO: To insert a new todo item

{ type: 'ADD_TODO', title: 'Test todo app' }

• TOGGLE_TODO: To toggle the completed value of a todo item

{ type: 'TOGGLE_TODO', id: 'xxx' }

• REMOVE_TODO: To remove a todo item

{ type: 'REMOVE_TODO', id: 'xxx' }

• FILTER_TODOS: To filter todo items

{ type: 'FILTER_TODOS', filter: 'completed' }

Defining the reducers


Now we are going to define the reducers for our state. We are going to need one app reducer and
two sub-reducers: one for todos and one for the filter.
320 Migrating from React Class Components

Defining the filter reducer


We are going to start by defining the reducer for the filter value:

1. Create a new src/reducers.js file.


2. In this file, define a function for the filterReducer, which is going to handle the FILTER_
TODOS action and set the filter value accordingly:

function filterReducer(state, action) {


if (action.type === 'FILTER_TODOS') {
return action.filter
}
return state
}

Next, let’s move on to the todosReducer function.

Defining the todos reducer


Now, we are going to define the todosReducer function for the todo items. Here, we are going to
handle the LOAD_TODOS, ADD_TODO, TOGGLE_TODO, and REMOVE_TODO actions:

1. Edit src/reducers.js and define a new function, which is going to handle these actions:
function todosReducer(state, action) {
switch (action.type) {

2. For the LOAD_TODOS action, we simply replace the current state with the new todos array:
case 'LOAD_TODOS':
return action.todos

3. For the ADD_TODO action, we are going to insert a new item at the beginning of the current
state array:
case 'ADD_TODO': {
const newTodo = { id: Date.now(), title: action.title,
completed: false }
return [newTodo, ...state]
}
Chapter 13 321

4. For the TOGGLE_TODO action, we are going to use the map function to update a single todo item:
case 'TOGGLE_TODO': {
return state.map((item) => {
if (item.id === action.id) {
return { ...item, completed: !item.completed }
}
return item
})
}

5. For the REMOVE_TODO action, we are going to use the filter function to remove a single
todo item:
case 'REMOVE_TODO': {
return state.filter((item) => item.id !== action.id)
}

6. By default (for all other actions), we simply return the current state:
default:
return state
}
}

Now we just need to define the appReducer, which will combine the other two reducers into one.

Defining the app reducer


To handle our todo app state in one Reducer Hook, we are going to combine the two reducers we
created into a single appReducer function. To do that:

1. Edit src/reducers.js and define and export a new function for the app reducer:
export function appReducer(state, action) {

2. In this function, we return an object with the values from the other reducers. We simply
pass the sub-state and action to the other reducers:
return {
todos: todosReducer(state.todos, action),
filter: filterReducer(state.filter, action),
}
}
322 Migrating from React Class Components

Now that we have defined a reducer to manage the app state, we can start migrating the App
component.

Migrating the App component


Let’s get started migrating the App component now:

1. Edit src/App.jsx and delete all of the existing code.


2. Import the following:
import { useReducer, useEffect, useMemo } from 'react'
import { Header } from './Header.jsx'
import { AddTodo } from './AddTodo.jsx'
import { TodoList } from './TodoList.jsx'
import { TodoFilter } from './TodoFilter.jsx'
import { StateContext } from './StateContext.js'
import { fetchTodos } from './api.js'
import { appReducer } from './reducers.js'

3. First, we define the function component, which is not going to accept any props:
export function App() {

4. Now, we define a Reducer Hook using the appReducer function:


const [state, dispatch] = useReducer(appReducer, { todos: [],
filter: 'all' })

5. Let’s also destructure the state object to make it easier for us to access later:
const { todos, filter } = state

6. Then, we implement the filter mechanism using a Memo Hook. This is one of the few cas-
es where using the Memo Hook is actually necessary. Otherwise, our component would
re-render too often, causing an infinite loop. We can use the Memo Hook to ensure that
a change to filteredTodos is only triggered when the todos or filter state changes, as
follows:
const filteredTodos = useMemo(() => {
switch (filter) {
case 'active':
return todos.filter((item) => item.completed === false)
case 'completed':
Chapter 13 323

return todos.filter((item) => item.completed === true)


case 'all':
default:
return todos
}
}, [todos, filter])

7. Now, we define an Effect Hook, which is going to fetch todos from our mock API and then
dispatch a LOAD_TODOS action:
useEffect(() => {
async function loadTodos() {
const todos = await fetchTodos()
dispatch({ type: 'LOAD_TODOS', todos })
}
void loadTodos()
}, [])

The function inside an Effect Hook needs to be synchronous, so we are using a


workaround to get the async/await construct to work here. When not caring
about waiting for an async function to complete, it is best practice to prefix
the function call with void to show that we intentionally did not await it.

8. Next, we define various functions that are going to dispatch actions and change the state
of our app:
function addTodo(title) {
dispatch({ type: 'ADD_TODO', title })
}

function toggleTodo(id) {
dispatch({ type: 'TOGGLE_TODO', id })
}

function removeTodo(id) {
dispatch({ type: 'REMOVE_TODO', id })
}
324 Migrating from React Class Components

function filterTodos(filter) {
dispatch({ type: 'FILTER_TODOS', filter })
}

9. Finally, we render all the components that are needed for our todo app:
return (
<StateContext.Provider value={filteredTodos}>
<div style={{ width: '400px' }}>
<Header />
<AddTodo addTodo={addTodo} />
<hr />
<TodoList toggleTodo={toggleTodo} removeTodo={removeTodo} />
<hr />
<TodoFilter filter={filter} filterTodos={filterTodos} />
</div>
</StateContext.Provider>
)
}

10. Now that our app is fully migrated, we can start the dev server and verify that everything
still works:
$ npm run dev

As we can see, using a Reducer Hook to handle complex state changes makes our code much
more concise and easier to maintain. Our app is now fully migrated to Hooks, and it still works
exactly the same way as before!

Another possibility to further refactor and improve the code would be to store the
state and dispatch function in a context, and then define custom Hooks for handling
the various functions of our todo app. However, in this example, we wanted to keep
it as close to the original class component code as possible, so any further refactoring
is left as an exercise for the reader.
Chapter 13 325

Example code

The example code for this section can be found in the Chapter13/Chapter13_2
folder. Check the README.md file inside the folder for instructions on how to set up
and run the example.

Now, let’s wrap up the book by discussing the trade-offs of using React class components vs
React Hooks.

Trade-offs of React class components vs React Hooks


Now that we have finished our migration from class components to Hooks, let us revise and sum
up what we have learned.

Function components with Hooks are easier to understand and test, since they are simply JavaS-
cript functions instead of complex React constructs. We were also able to refactor the state-chang-
ing logic into a separate reducers.js file, thus decoupling it from the App component, and making
it easier to refactor and test. We can safely say that separating concerns between app logic and
components significantly improved the maintainability of our project.

Now, let’s recap the advantages we gained by refactoring our app. With function components
and Hooks, the following points do not need to be taken into consideration anymore:

• No need to deal with constructors.


• No confusing this context (this re-binding).
• No need to destructure the same values over and over again.
• No magic when dealing with contexts, props, and state.
• No need to define componentDidMount and componentDidUpdate if we want to re-fetch
data when the props change.

Additionally, function components have the following advantages:

• Encourage making small and simple components.


• Are easier to refactor.
• Are easier to test.
• Require less code.
326 Migrating from React Class Components

• Are easier to understand for beginners.


• Are more declarative.
• React Server Components (RSCs) can only be function components.

However, class components can be fine in the following situations:

• When sticking to certain conventions.


• When using arrow functions to avoid this re-binding.
• Possibly easier to understand for the team, because of existing knowledge.
• Many projects still use classes. For dependencies, this is not such a problem, as they can
still be easily used in function components. However, in legacy codebases, you may still
need to work with class components.

In the end, it is a matter of preference, but Hooks do have many advantages over classes! If you
are starting a new project, definitely go for Hooks. If you are working on an existing project, con-
sider whether it might make sense to refactor certain components to Hook-based components
in order to make them simpler.

However, you should not immediately port all your projects to Hooks, as refactoring can always
introduce new bugs. The best way to adopt Hooks is to slowly but surely replace old class compo-
nents with Hook-based function components when appropriate. For example, if you are already
refactoring a component, you can refactor it to use Hooks!

Summary
In this chapter, we first built a todo app using React class components. We started by designing
the app structure, then implemented static components, and finally, we made them dynamic. Next,
we learned how to migrate an existing project using class components, to function components
using Hooks. Finally, we learned about the trade-offs of class components, when class components
or Hooks should be used, and how one should go about migrating an existing project to Hooks.

We have now seen in practice how React class components differ to function components with
Hooks. Hooks make our code much more concise and easier to read and maintain. We have also
learned that we should gradually migrate our components from class components to function
components with Hooks—there is no need to immediately migrate the whole application.
Chapter 13 327

This chapter marks the end of this book. In this book, we started out with a motivation to use
Hooks. We learned that there are common problems in React apps, which could not be easily
solved without Hooks. Then, we created our first component using Hooks, and compared it to
the class-based solution. Next, we learned about the State Hook, which is the most ubiquitous
of them all. We also learned about solving common problems with Hooks, such as conditional
Hooks, and Hooks in loops. We gained a deep understanding of how Hooks work internally and
what their limitations are.

After learning about the State Hook in depth, we developed a small blog app using Hooks. We then
learned about Reducer Hooks, Effect Hooks, and Context Hooks, in order to be able to implement
more features in our app. Next, we learned how to use TanStack Query Hooks and React Suspense
for data fetching. Then, we used Hooks to handle forms. Next, we used React Router to implement
routes in our blog app, and we learned how Hooks can make dynamic routing much easier. We
gained experience developing a real-world frontend application with Hooks.

We also learned about the various built-in Hooks provided by React, even the advanced ones.
Then, we learned about various Hooks that are provided by the community, which make dealing
with application state and various use cases much easier. We gained an in-depth understanding
of all the Hooks that React provides, and where to find more Hooks.

Towards the end of the book, we learned about the rules of Hooks, how to create our own custom
Hooks and how to write tests for them using Vitest. We also learned how to migrate an existing
class-component based app to a Hook-based solution. We thus gained an understanding on how
to effectively refactor React applications with Hooks.

Now that you have learned about Hooks in depth, you are ready to use them in your applications!
You have also learned how to migrate existing projects to Hooks, so you can start doing that now.
I hope you enjoyed learning about React Hooks, and that you are looking forward to implementing
Hooks in your applications! I am sure that using Hooks will make coding much more enjoyable
for you, just like they did for me.
328 Migrating from React Class Components

Questions
To recap what we have learned in this chapter, try to answer the following questions:

1. How are React class components defined?


2. What do we need to call when using a constructor with class components? Why?
3. How do we set the initial state with class components?
4. How do we change the state with class components?
5. Why do we need to re-bind the this context with class component methods?
6. How can we re-bind the this context?
7. How can we use React Context with class components?
8. What can we replace state management with when migrating to Hooks?
9. What are the trade-offs of using Hooks versus class components?
10. When, and how, should an existing project be migrated to Hooks?

Further reading
If you are interested in more information about the concepts that we have learned in this chapter,
take a look at the following links:

• Classes in JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/


Reference/Classes
• React class components: https://www.robinwieruch.de/react-component-
types/#react-class-components
• Migrating class components to functions: https://react.dev/reference/react/
Component#migrating-a-simple-component-from-a-class-to-a-function

Learn more on Discord


To join the Discord community for this book – where you can share feedback, ask questions to
the author, and learn about new releases – follow the QR code below:

https://packt.link/wnXT0
www.packtpub.com
Subscribe to our online digital library for full access to over 7,000 books and videos, as well as
industry leading tools to help you plan your personal development and advance your career. For
more information, please visit our website.

Why subscribe?
• Spend less time learning and more time coding with practical eBooks and Videos from
over 4,000 industry professionals
• Improve your learning with Skill Plans built especially for you
• Get a free eBook or video every month
• Fully searchable for easy access to vital information
• Copy and paste, print, and bookmark content

At www.packtpub.com, you can also read a collection of free technical articles, sign up for a range
of free newsletters, and receive exclusive discounts and offers on Packt books and eBooks.
Other Books
You May Enjoy
If you enjoyed this book, you may be interested in these other books by Packt:

React Key Concepts, Second Edition

Maximilian Schwarzmüller

ISBN: 978-1-83620-227-1
• Build modern, user-friendly, and reactive web apps
• Create components and utilize props to pass data between them
• Handle events, perform state updates, and manage conditional content
• Add styles dynamically and conditionally for modern user interfaces
• Use advanced state management techniques such as React’s Context API
• Utilize React Router to render different pages for different URLs
• Understand key best practices and optimization opportunities
• Learn about React Server Components and Server Actions
Other Books You May Enjoy

Modern Full-Stack React Projects

Daniel Bugl

ISBN: 978-1-83763-795-9

• Implement a backend using Express and MongoDB, and unit-test it with Jest
• Deploy full-stack web apps using Docker, set up CI/CD and end-to-end tests using
Playwright
• Add authentication using JSON Web Tokens (JWT)
• Create a GraphQL backend and integrate it with a frontend using Apollo Client
• Build a chat app based on event-driven architecture using Socket.IO
• Facilitate Search Engine Optimization (SEO) and implement server-side rendering
• Use Next.js, an enterprise-ready full-stack framework, with React Server Components
and Server Actions
Other Books You May Enjoy 333

Packt is searching for authors like you


If you’re interested in becoming an author for Packt, please visit authors.packtpub.com and
apply today. We have worked with thousands of developers and tech professionals, just like you,
to help them share their insight with the global tech community. You can make a general appli-
cation, apply for a specific hot topic that we are recruiting an author for, or submit your own idea.

Share your thoughts


Now you’ve finished Learn React Hooks, Second Edition, we’d love to hear your thoughts! If you
purchased the book from Amazon, please click here to go straight to the Amazon review
page for this book and share your feedback or leave a review on the site that you purchased it from.

Your review is important to us and the tech community and will help us make sure we’re deliv-
ering excellent quality content.
Index

A B
absolute imports 115 backend server
actions 94, 95 db.json file, creating 134-137
Action State Hook 165, 181, 201 json-server tool, installing 137, 138
form submission, handling 164 package.json scripts, installing 138-140
using 165-167 proxy, configuring 140, 141
setting up 134
AddTodo component
defining 298, 299 blocking UI
migrating 317, 318 avoiding, with Transition Hook 171
Comment component, implementing 168
AddTodo component dynamic
CommentList component,
making 307-309
implementing 168
App component CommentSection component,
actions, defining 319 implementing 169, 170
implementing 73-75 simulating 167
migrating 319-324
reducers, defining 319 C
App component dynamic class components
making 303-307 advantages 326
Application Programming Interface (API) 134 closure 38
app reducer community Hooks
defining 321 finding 248
async/await construct 143-145 conditional Hooks 43-46
asynchronous functions 144 solving 49-51
asynchronous Hooks context for global state
testing 288-290 app, refactoring for UserContext 127-130
336 Index

context provider, defining 126, 127 Debounced Callback Hook 242


defining 125, 126 Debounced History State Hook
using 125 creating 275-277
Context Hook 10, 200 using 277, 278
Copy To Clipboard Hook Debounce Hook 241
using 244-246 changes, debouncing in post editor 242, 243
Counter Hook versus Deferred Value Hook 243
creating 280, 281 debouncing 233
unit tests, creating 281-284 Deferred Value Hook 223
CreatePost component 70 deferred values 227
implementing 70 dependency array 103
cross-origin resource sharing (CORS) 140 development environment
custom API Hooks project, setting up with Vite 14-17
creating 270 setting up 11
extracting 271, 272 VS Code and extensions, installing 11-14
using 272-275 Document Object Model (DOM) 206
custom Hooks dynamic code
asynchronous Hooks, testing 288-290 AddTodo component dynamic,
React Testing Library, setting up 279 making 307-309
simple Hook, testing 280 App component dynamic, making 303-307
testing 278 app, starting 313
tests, running 290, 291 implementing 302
Theme Hook, testing 284-286 mock API, defining 302
User Hook, testing 286, 287 StateContext, defining 303
Vitest, setting up 279 TodoFilter component dynamic,
custom Theme Hook making 311, 312
creating 262, 263 TodoItem component dynamic,
using 264 making 310, 311
TodoList component dynamic, making 309
custom User Hook
creating 265, 266
using 266-270
E
ECMAScript Module (ESM) 18
D Effect Hook 10, 200
db.json file componentDidMount 101-103
creating 134-137 componentDidUpdate 101-103
effects, cleaning 105
Index 337

effect, triggering on mount 105 higher-order components 9


effect, triggering on props change 104 History State Hook 238-240
implementing, in blog app 106, 107 Hook APIs
life cycle methods, migrating to 103 alternatives 47, 48
using 101
Hook factories 47, 48
Error Boundary
Hooks
setting up 158, 160
adding, for post features 82
ESLint adding, for user features 76
configuring 20, 21 App component, adjusting 82
dependencies, installing 18 CreatePost component, adjusting 83, 84
new script, adding to run linter 22 debouncing with 241
setting up 18 defining 253, 254
names 257
F order 254-257
filter reducer rules, enforcing 258
defining 320 solving, in loops 51, 52
Form Actions 78, 164 used, for implementing stateful
components 76
FormData API
UserBar component, adjusting 76-78
reference link 165
user, passing to CreatePost 81, 82
Form Status Hook 202 validation, adding 79-81
function components Hooks for advanced effects
advantages 325 useInsertionEffect 230
useLayoutEffect 230
G using 230
getServerSnapshot function 218 Hooks for performance optimizations
getSnapshot function 218 useCallback 229
useDeferredValue 223
global state 82
useMemo 228
global variable
using 222
using 39
Hooks, to manage application state
H useHistoryState, using 238-240
useLocalStorage, using 234-238
Header component using 234
defining 298 Hover Hook
using 246-248
338 Index

I N
Imperative Handle Hook 212 Named Hooks 47
input field navigation bar
auto-focusing, with Ref Hook 206, 207 defining, with NavLink component 190-192
Insertion Effect Hook 230 Navigation Hook 181
inversion of control 125 used, to navigate
programmatically 192, 193
J Node.js
reference link 55
json-server tool
non-blocking Transition
installing 137, 138
testing 172
JSON Web Token (JWT) 235
O
L
optimistic comment creation
Layout Effect Hook 207, 230
implementing 174-177
life cycle methods 6
Optimistic Hook 181, 203
Link component 197 using, to implement optimistic updates 173
local state 80
LocalStorage API 234 P
Local Storage Hook 234-238 package.json scripts
Login component 61, 62 configuring 138-140
rendering 63 Param Hook 181, 197
Logout component 65 using 186, 187
Post component 69
M implementing 69
Memo Hook 228 PostList component 71-73
mock API implementing 71
defining 302 posts
multiple Hooks CreatePost component 70
defining 40 creating, on server 145-147
support, adding 40-42 creating, with Mutation Hook 152-154
fetching, from server 141-143
Mutation Hook 147
fetching, with Query Hook 149-152
used, for creating posts 152-154
implementing 68
Index 339

Post component 69 dynamic code, implementing 302


PostList component 71-73 migrating 313, 314
Prettier project, initializing 296
configuring 19 state management, migrating 319
extension, configuring 20 static components, defining 298
ignore file, creating 20 TodoFilter component, migrating 316, 317
reference link 19 TodoItem component, migrating 314, 315
setting up 18 TodoList component, migrating 315, 316
proxy used, for handling state 294
configuring 140, 141 versus React Hooks 325, 326
React Compiler 222
Q reference link 222
React Context 112
Query Hook 147, 203
absolute imports, using 115, 116
used, for fetching posts 149-152
consumer 114
consumer, defining 116, 117
R
context 114
React Context Hook, using 117, 118
class components 5 defining 114, 115
function components 5 implementing 112-114
principles 4 need for 124, 125
React built-in Hooks provider 114
overview 198, 199 provider, defining 120, 121
useActionState 201 provider, nesting 122
useContext 200 used, for implementing themes 114
useEffect 200 React Developer Tools extension
useFormStatus 202 reference link 221
useOptimistic 203 React Hooks
useReducer 201 advantages 30
useState 199 class components 28, 29
useTransition 203, 204 class components, versus function
React class components component 29, 30
AddTodo component, migrating 317, 318 class component, using 23-26
App component, migrating 319 confusing classes 6, 7
app structure, defining 297 limitations 46
app structure, designing 294-296 migrating to 30
340 Index

mindset 31 state change 207, 208


motivation 6 used, for auto-focusing input field 206, 207
rule considerations 31 Register component 64
using 10, 11, 22, 26-28 regular anonymous functions 144
versus React class components 325, 326
regular functions 143
wrapper hell 8, 9
render function 116
React principles
render props 8
component-based 5
declarative 5 Representational State Transfer (REST) 134
learn once, write anywhere 5 resources, with Effect and State/Reducer
Hook
React projects structure 56
async/await construct 143-145
component structure 58-60
posts, creating on server 145-147
features, defining 57
posts, fetching from server 141-143
folder structure 56
requesting 141
initial structure 57
Rolldown
React Query 164
reference link 18
React Router 181, 182
routes
setting up 183-185
creating 185-187
React Testing Library
linking, with Link component 188-190
reference link 279
setting up 279, 280
S
Reducer Hook 97, 201
defining 100, 101 search page
State Hooks, turning 98 implementing, without deferred
values 223-227
using 97
versus State Hooks 92 simple Hook
Counter Hook, creating 280, 281
reducers 94
testing 280
app reducer, defining 321
defining 95, 96, 319 simulated blocking UI
filter reducer, defining 320 testing 170, 171
todos reducer, defining 320, 321 single responsibility principle 59
Ref Hook 206 state
mutable values, persisting 209, 210 handling, with React class components 294
ref contents, creating 212 StateContext
refs, passing as props 211 defining 303
Index 341

stateful components
T
implementing, with Hooks 76
State Hook 199 TanStack Query
actions 94, 95 library, setting up 147-149
issues, resolving 39 posts, creating with Mutation Hook 152- 154
limitations 92, 93 posts, fetching with Query Hook 149-152
reducers 94 using, to make changes 147
reimplementing 36-38 using, to request resources 147
turning, into Reducer Hook 98 template string 9
versus Reducer Hooks 92 ternary operator 8
State Hook, conversion into Reducer Hook Theme Hook
actions, defining 98 testing 284-286
reducer, implementing 99 themes
state management implementing, via React Context 114
migrating 319 TodoFilter component
static components defining 301
AddTodo component, defining 298, 299 migrating 316, 317
App component, implementing 73-75 TodoFilter component dynamic
app, starting 302 making 311, 312
defining 298
TodoItem component
Header component, defining 298
defining 300
implementing 60
migrating 314, 315
posts, implementing 68
TodoItem component dynamic
TodoFilter component, defining 301
making 310, 311
TodoItem component, defining 300
TodoList component, defining 299, 300 TodoList component
user-related static components, defining 299, 300
implementing 61 migrating 315, 316
subscribe function 218 TodoList component dynamic
Suspense Boundary making 309, 310
setting up 155-158 todos reducer
Suspense Query Hook 186 defining 320, 321

Sync External Store Hook 218 Transition Hook 181, 203


blocking UI, avoiding 171
using 171
342 Index

useSyncExternalStore 218, 219


U
useTransition 203, 204
URL param 186
utility Hooks 204
useActionState 201 usage 244
useCallback 229 useCopyToClipboard 244-246
useDebugValue 220, 221 useDebugValue 220, 221
useDeferredValue 223 useHover 246-248
search page, implementing without useId 215-218
deferred values 223-227 useImperativeHandle 212-215
useEffect 200 useRef 205
useSyncExternalStore 218, 219
useFormStatus 202
using 204, 205
useId 215-218
useImperativeHandle 212-215 V
useInsertionEffect 230
Visual Studio Code
useLayoutEffect 230 (VS Code) 4, 11, 55, 112, 198, 293
useMemo 228 installing 11-14
useOptimistic 203 reference link 198
UserBar component 66-68 Vite
useReducer 201 alternatives 18
useRef 205 used, for setting up project 15-17

user features Vitest 278


Hooks, adding 76 setting up 279, 280

User Hook
testing 286, 287
W
user interface (UI) 56 wrapper hell 9
user-related static components
implementing 61
Login component 61, 62
Logout component 65
Register component 64
UserBar component 66-68
useState 199
useState Hook
reimplementing 46
Download a free PDF copy of this book
Thanks for purchasing this book!

Do you like to read on the go but are unable to carry your print books everywhere?

Is your eBook purchase not compatible with the device of your choice?

Don’t worry, now with every Packt book you get a DRM-free PDF version of that book at no cost.

Read anywhere, any place, on any device. Search, copy, and paste code from your favorite technical
books directly into your application.

The perks don’t stop there, you can get exclusive access to discounts, newsletters, and great free
content in your inbox daily.

Follow these simple steps to get the benefits:

1. Scan the QR code or visit the link below:

https://packt.link/free-ebook/9781836209171

2. Submit your proof of purchase.


3. That’s it! We’ll send your free PDF and other benefits to your email directly.

You might also like