With Suspense and Concurrent Mode John Larsen M A N N I N G React Hooks in Action WITH SUSPENSE AND CONCURRENT MODE JOHN LARSEN MANNING SHELTER ISLAND For online information and ordering of this and other Manning books, please visit www.manning.com. The publisher offers discounts on this book when ordered in quantity. For more information, please contact Special Sales Department Manning Publications Co. 20 Baldwin Road PO Box 761 Shelter Island, NY 11964 Email: [email protected] ©2021 by Manning Publications Co. All rights reserved. No part of this publication may be reproduced, stored in a retrieval system, or transmitted, in any form or by means electronic, mechanical, photocopying, or otherwise, without prior written permission of the publisher. Many of the designations used by manufacturers and sellers to distinguish their products are claimed as trademarks. Where those designations appear in the book, and Manning Publications was aware of a trademark claim, the designations have been printed in initial caps or all caps. Recognizing the importance of preserving what has been written, it is Manning’s policy to have the books we publish printed on acid-free paper, and we exert our best efforts to that end. Recognizing also our responsibility to conserve the resources of our planet, Manning books are printed on paper that is at least 15 percent recycled and processed without the use of elemental chlorine. Development editor: Helen Stergius Technical development editor: John Guthrie Manning Publications Co. Review editor: Aleksandar Dragosavljevic´ 20 Baldwin Road Production editor: Deirdre S. Hiam PO Box 761 Copy editor: Sharon Wilkey Shelter Island, NY 11964 Proofreader: Keri Hales Technical proofreader: Clive Harber Typesetter: Dennis Dalinnik Cover designer: Marija Tudor ISBN: 9781617297632 Printed in the United States of America To Mum, for all the books. And to Dad, for all the gadgets. contents preface xi acknowledgments xiii about this book xiv about the author xviii about the cover illustration xix PART 1 ........................................................................ 1 1 React is evolving 3 1.1 What is React? 3 Building a UI from components 4 ■ Synchronizing state and UI 6 ■ Understanding component types 10 1.2 What’s new in React? 12 1.3 React Hooks can add state to function components 13 Stateful function components: Less code, better organization 14 Custom hooks: Easier code reuse 17 ■ Third-party hooks provide ready-made, well-tested functionality 18 1.4 Better UX with Concurrent Mode and Suspense 21 Concurrent Mode 22 ■ Suspense 23 1.5 React’s new publication channels 24 v vi CONTENTS 1.6 Whom is this book for? 25 1.7 Getting started 25 2 Managing component state with the useState hook 27 2.1 Setting up the bookings manager app 28 Generating the app skeleton with create-react-app 30 ■ Editing the four key files 31 ■ Adding a database file for the application 36 Creating page components and a UserPicker.js file 37 2.2 Storing, using, and setting values with useState 38 Assigning new values to variables doesn’t update the UI 39 Calling useState returns a value and an updater function 42 Calling the updater function replaces the previous state value 46 Passing a function to useState as the initial value 49 ■ Using the previous state when setting the new state 50 2.3 Calling useState multiple times to work with multiple values 53 Using a drop-down list to set state 53 ■ Using a check box to set state 56 2.4 Reviewing some function component concepts 60 3 Managing component state with the useReducer hook 65 3.1 Updating multiple state values in response to a single event 66 Taking users out of the movie with unpredictable state changes 66 Keeping users in the movie with predictable state changes 68 3.2 Managing more complicated state with useReducer 70 Updating state using a reducer with a predefined set of actions 71 Building a reducer for the BookablesList component 73 Accessing component state and dispatching actions with useReducer 76 3.3 Generating the initial state with a function 79 Introducing the WeekPicker component 81 ■ Creating utility functions to work with dates and weeks 82 ■ Building the reducer to manage dates for the component 83 ■ Passing an initialization function to the useReducer hook 84 ■ Updating BookingsPage to use WeekPicker 85 3.4 Reviewing some useReducer concepts 86 CONTENTS vii 4 Working with side effects 92 4.1 Exploring the useEffect API with simple examples 93 Running side effects after every render 93 ■ Running an effect only when a component mounts 95 ■ Cleaning up side effects by returning a function 97 ■ Controlling when an effect runs by specifying dependencies 99 ■ Summarizing the ways to call the useEffect hook 103 ■ Calling useLayoutEffect to run an effect before the browser repaints 103 4.2 Fetching data 104 Creating the new db.json file 104 ■ Setting up a JSON server 105 ■ Fetching data within a useEffect hook 106 Working with async and await 108 4.3 Fetching data for the BookablesList component 109 Examining the data-loading process 110 ■ Updating the reducer to manage loading and error states 111 ■ Creating a helper function to load data 113 ■ Loading the bookables 114 5 Managing component state with the useRef hook 118 5.1 Updating state without causing a re-render 119 Comparing useState and useRef when updating state values 119 Calling useRef 121 5.2 Storing timer IDs with a ref 122 5.3 Keeping references to DOM elements 125 Setting focus on an element in response to an event 126 Managing a text box via a ref 129 6 Managing application state 134 6.1 Passing shared state to child components 135 Passing state from a parent by setting props on the children 136 Receiving state from a parent as a prop 136 ■ Receiving an updater function from a parent as a prop 138 6.2 Breaking components into smaller pieces 140 Seeing components as part of a bigger app 141 ■ Organizing multiple components within a page’s UI 142 ■ Creating a BookableDetails component 143 6.3 Sharing the state and dispatch function from useReducer 146 Managing state in the BookablesView component 147 ■ Removing an action from the reducer 148 ■ Receiving state and dispatch in the BookablesList component 148 viii CONTENTS 6.4 Sharing the state value and updater function from useState 151 Managing the selected bookable in the BookablesView component 152 Receiving the bookable and updater function in BookablesList 153 6.5 Passing functions to useCallback to avoid redefining them 159 Depending on functions we pass in as props 159 ■ Maintaining function identity with the useCallback hook 161 7 Managing performance with useMemo 164 7.1 Breaking the cook’s heart by calling, “O, shortcake!” 165 Generating anagrams with an expensive algorithm 166 Avoiding redundant function calls 169 7.2 Memoizing expensive function calls with useMemo 170 7.3 Organizing the components on the Bookings page 171 Managing the selected bookable with useState 173 ■ Managing the selected week and booking with useReducer and useState 173 7.4 Efficiently building the bookings grid with useMemo 176 Generating a grid of sessions and dates 177 ■ Generating a lookup for bookings 180 ■ Providing a getBookings data-loading function 182 Creating the BookingsGrid component and calling useMemo 182 Coping with racing responses when fetching data in useEffect 186 8 Managing state with the Context API 194 8.1 Needing state from higher up the component tree 195 Displaying a call-to-action message when the page first loads 196 Displaying booking information when a visitor selects a booking 198 Displaying an edit button for a user’s bookings: The problem 199 Displaying an edit button for a user’s bookings: The solution 200 8.2 Working with custom providers and multiple contexts 206 Setting an object as the context provider’s value 206 ■ Moving the state to a custom provider 207 ■ Working with multiple contexts 213 ■ Specifying a default value for a context 216 9 Creating your own hooks 218 9.1 Extracting functionality into custom hooks 220 Recognizing functionality that could be shared 223 ■ Defining custom hooks outside your components 224 ■ Calling custom hooks from custom hooks 225 CONTENTS ix 9.2 Following the Rules of Hooks 227 Call hooks only at the top level 228 ■ Call hooks only from React functions 228 ■ Using an ESLint plugin for the rules of hooks 229 9.3 Extracting further examples of custom hooks 229 Accessing window dimensions with a useWindowSize hook 229 Getting and setting values with a useLocalStorage hook 231 9.4 Consuming a context value with a custom hook 233 9.5 Encapsulating data fetching with a custom hook 235 Creating the useFetch hook 236 ■ Using the data, status, and error values the useFetch hook returns 237 ■ Creating a more specialized data-fetching hook: useBookings 238 10 Using third-party hooks 245 10.1 Accessing state in the URL with React Router 246 Setting up routes to enable nesting 248 ■ Adding nested routes to the Bookables page 249 ■ Accessing URL parameters with the useParams hook 250 ■ Navigating with the useNavigate hook 252 10.2 Getting and setting query string search parameters 256 Getting search parameters from the query string 258 ■ Setting the query string 262 10.3 Streamlining data-fetching with React Query 266 Introducing React Query 267 ■ Giving components access to a React Query client 269 ■ Fetching data with useQuery 270 Accessing data in the query cache 273 ■ Updating server state with useMutation 276 PART 2 .....................................................................281 11 Code splitting with Suspense 283 11.1 Importing code dynamically with the import function 284 Setting up a web page to load JavaScript when a button is clicked 284 ■ Using default and named exports 285 Using static imports to load JavaScript 286 ■ Calling the import function to dynamically load JavaScript 287 11.2 Importing components dynamically with lazy and Suspense 288 Converting a component to a lazy component with the lazy function 289 ■ Specifying fallback content with the Suspense