Rating: 7.9/10.
Learning React: Modern Patterns for Developing React Apps by Alex Banks and Eve Porcello
This is a good introductory book on React that starts from the basics, including JavaScript and React components, then goes on to explain the usages of many different React hooks, how they work, and in what situations they should be used.
Chapter 1-3. History of React, review of Javascript and functional programming.
Chapter 4. You can create React elements that will be rendered to DOM elements by ReactDOM. This process takes a root component and React component tree to render. React elements can contain other elements in a tree structure. A function component takes properties and returns a tree of React elements; there used to be other ways to create elements using class, but these methods are now deprecated.
Chapter 5: JSX is more readable syntax than constructing trees of React components using JS, because it has an HTML-like syntax but you need a compiler, such as Babel, to compile it into executable JS. You can construct a tree of components this way, each with its own properties; rendering several elements must be wrapped in a fragment. The create-react-app tool sets up all the tooling for you, including Babel and webpack. Webpack builds all the files into a single bundle, and Babel transforms JSX into plain JS. Enable source maps to debug using original files in the Chrome DevTool.
Chapter 6: React State Management. The useState hook gives you a value and a setter function so when the setter is called, the component automatically rerenders. For reusable components, it’s advisable to pass down props so that other users can modify properties of the component as desired. A common way to manage state is to store the state at the top level and then pass it down to the leaf components where it’s needed. In these patterns, the component is stateless with the parent controlling all the state and callback functions, and the component is only responsible for rendering it.
The useRef hook provides a reference to a component to set the properties of a DOM element directly, but this results in imperative code instead of using the React rerendering system, it is usually not advised. You can also create your own custom hook to wrap existing hooks, like useState, eg, you can expose a function to reset a value to its default state.
The React context renders it unnecessary to pass the state all the way down the component tree to leaf components. Instead, wrap everything with a context provider and you can access the context anywhere using the useContext hook to obtain values from the context. A stateful context allows consumers to modify things in the context. However, the best practice is to expose specific operations and not the setter hook itself. You can also define custom hooks to directly access context properties without any knowledge of the existence of context.
Chapter 7: The useEffect hook is used to run code after a component renders. By default, it runs after the component renders, but you can also give it a dependency array to run additionally when anything in the array changes. The return value of useEffect is run when the component is removed, so it can be used for any kind of tear-down functionality.
The dependency array doesn’t work if the dependency is an object, like a list of strings, because it uses reference equality for comparison, eg, a list of strings is identical to another, but the references are not equal. Therefore, for these values, there is the useMemo function that memoizes a value. This can be passed into the useEffect only when the value has actually changed. There is also a useCallback function to do the same thing, but for functions, which are also passed as references. However, useMemo and useCallback are generally used to optimize for renders when your application is doing too many of them; not recommend to do this optimization prematurely.
Hooks should be declared in the top-level scope of a React component and never in a conditional or sub-function. The useReducer is useful for complex state; here, you expose a reducer function that modifies the underlying state in a predefined way rather than setting the state directly, this is useful when the new state depends on the previous state, like when modifying values of an object.
A pure component is a component that should only rerender when its properties change; the memo function can be used to turn a component into a pure component. You can also pass in functions to sometimes rerender, depending on a predicate, this replaces the shouldComponentUpdate function in older versions of React.
Chapter 8: incorporating data into applications. The fetch function can be used to make GET and POST requests, which can then be used to render the response data in a component. HTTP requests have three states: pending, success, and failed. It is recommended to display some kind of loading indication when the response is pending, and some kind of error message if it fails.
The chapter builds an example application of rendering all of a user’s GitHub projects in a virtualized list; this is a component that renders list data one at a time, with buttons to go to the previous and next elements without rendering everything at the beginning. This can be done using render properties, where you pass a component into another component to be rendered eventually, but not right away.
There are several ways to simplify handling the three fetch states. You can make a custom useFetch hook that exposes three different objects for loading data and error, which can then be used to render the component. Another way is by creating a fetch component that abstracts the fetch and automatically displays a loading image before rendering the data when it becomes available.
One issue with HTTP requests is managing the order in which they are made. An antipattern is waterfall requests, where a series of requests depend on the previous one and require this to be done sequentially. On a slow network, this can be really slow. Nested components inside each other that depend on the previous component to start rendering will lead to a series of sequential requests, so it is better to put components side by side instead of nesting them, then the requests will automatically be made in parallel. Another class of issues is timing bugs arising when you mount or unmount components based on the state, eg: showing a different component when the search result is empty — bugs can occur if the network request resolves after a component has already been unmounted, which occurs more often on a slow network connection.
Chapter 9. Error Boundary is a component that catches any errors in child components and renders them in a friendly way, rather than crashing the whole app. In large applications, lazy import is useful to delay importing the code for a component until it is actually needed, rather than loading everything at the start. React Suspense is a relatively new feature that handles both lazy loading of components and error handling, it is a wrapper around a child component, which may return a success, error, or pending state. The Suspense component has a fallback component that is displayed during the pending and error states. When it’s pending, the child component can throw a promise instead of an error, and Suspense will execute this promise before trying again, so it’s a good way of cleanly implementing retry logic. React 16 has also altered the way rerendering operates, so that reconciliation (determining which part of the tree to update) is decoupled from DOM rendering. This is accomplished asynchronously using React Fiber to create a more responsive and non-blocking feel.
Chapter 10. ESLint checks source code for syntax and style issues, it supports a great deal of customization and plugins, for example, to check for React Hook best practices. Prettier handles code formatting and is usually used as an ESLint plugin. Several tools are available to check types, including PropTypes, which has a field on the component to annotate what types it is supposed to have, and Flow, developed by Facebook. The most powerful tool is TypeScript, developed by Microsoft, which has a lot of type inference abilities. Jest is a framework for unit testing, and you can simulate the browser with JSDom and events for unit testing components, and it can also generate a coverage report.
Chapter 11: React Router is a component that swaps out components based on the URL. You can have a wildcard route to handle a 404 error. Nested routes can conditionally render an entire component tree depending on each component matching a URL. A route can capture parameters that will be exposed to the components using the useParams hook.
Chapter 12: Isomorphic or Universal JavaScript is a subset that can run on both the client and the server. For example, the fetch function only exists on the browser, but there is an isomorphic fetch so that the same code can run on the server. This is useful for building server-side rendering. Components are automatically isomorphic, so you can run them on the server and render them to a string rather than to the DOM. Some setup with Babel and Webpack is needed to serve the pre-rendered build. Next JS does this automatically for you, using server-side rendering and a file structure for routing instead of the React Router component. Gatsby is an alternative for static sites.