This course has been updated! We now recommend you take the Intermediate React, v5 course.
Table of Contents
Introduction (Intermediate)
Introduction
Brian Holt begins the course by sharing his background, the history behind the course, and the course setup instructions. This course shares a codebase and course website with the Complete Intro to React course.Code Setup
Brian clones the course repository and explains how the project is organized. Each lesson is a stand-alone project to allow students to jump into any section. The 12-portals-and-ref directory will be duplicated to begin each lesson.
Hooks in Depth
useState
Brian begins the discussion about React hooks by reviewing useState. The useState hook allows developers to manage state with functions rather than class components. When state is stored in a hook, the component is loaded on every render and the component always has the latest state.useEffect
Brian explains that useEffect recreates the componentDidMount, componentDidUpdate, and componentDidUnmount functionality from React. This hook is useful for updates like AJAX requests or third-party library integrations occuring outside the render method. Handling race conditions and asynchronous code is also discussed in this segment.useContext
Brian reviews how the useContext hook allows data from one component to be available in a subcomponent. This avoids the need to "prop drill" or pass the data from parent to child. Typically, the data used with useContext is application-level state or data that's shared throughout the entire application.useRef
Brian demonstrates the useRef hook and compares it to useState. The useRef hook will always return the current value of the object because it is not subject to the closure's scope like useState.useReducer
Brian demonstrates how useReducer allows for Redux-style reducers to be used inside a hook. A reducer is passed the state and an action. Based on the action's type, a new state is returned. The useReducer hook uses a dispatcher to call the reducer.useMemo
Brian explains the useMemo hook memoizes expensive function calls so they are only re-evaluated when needed.The differences between useMemo and useEffect are also discussed in this segment.useCallback
Brian demonstrates how useCallback is similar to useMemo because it will limit calls to expensive functions as long as the props have not changed. With useMemo, an expensive function could be called when a component is rerendered because the function is redeclared. The useCallback hook solves this issue by ensuring the same function is always reference and only called when a rerender is necessary.useLayoutEffect
Brian demonstrates a couple use cases for using useLayoutEffect instead of useEffect. The useLayoutEffect hook happens immediately after a render so developers know when to expect the code to execute. This is beneficial for measurements or animations. It's recommended to use the useEffect hook for most use cases.useImperativeHandle
Brian explains why the useImperativeHandle is rarely used and only applicable for library development. By combining useImperativeHandle with forwardRef, component methods can be customized and exposed to a parent component.useDebugValue & useId
Brian demonstrates how useDebugValue can be used for debugging custom hooks. This is beneficial for library authors because it makes specific debug information available to the React developer tools. The useId hook which is coming in React 18 is also mentioned in this segment.
TailwindCSS
CSS & React
Brian introduces TailwindCSS and installs it as a development dependency for the project. Directives are used in the CSS file to import specific parts of the TailwindCSS library.Basics & Gradients
Brian begins adding a few TailwindCSS utility classes to the App.js component. Margin and padding are controlled incrementally with different classes. The gradient options in TailwindCSS are also demonstrated in this segment.CSS Libraries
Brian discusses the differences between TailwindCSS and other popular CSS libraries like Bootstrap and Emotion. Choosing the appropriate CSS library for a project often depends on the overall design goals or architecture of the application.Layout Basics
Brian uses a few TailwindCSS layout classes to style and position the SearchParams.js component. Rounded corners and a drop shadow are added. A flexbox layout is used to stack the form elements in a column.Tailwind Plugins
Brian installs the TailwindCSS form plugin which adds some uniform styling to all form elements in the application. Disabled styles are added to the breed input and the submit button has padding added to it.Grid & Breakpoints
Brian uses classes to apply CSS grid layout to the Results component. TailwindCSS also includes breakpoints for its classes. Prefixing class with sizes like sm, md, lg, or xl will apply those styles at a specific breakpoint.Positioning
Brian uses the TailwindCSS utility classes to position elements in the Pet component. Positioning can be absolute or relative. The utility classes provided consistent spacing offsets from the top, left, bottom, and right sides of a container.
Code Splitting & Server Side Rendering
Code Splitting
Brian explains the benefits of code splitting and demonstrates how to code split based on specific routes or individual components. The lazy React module is used to create dynamic imports. The Suspense module allows for the preloading experience to be customized so users see a message while the dependencies are being loaded.Server Side Rendering Overview
Brian introduces server side rendering which runs React on a Node.js server before serving the request to the user. The fully rendered route is then sent to the browser which limits the amount of JavaScript loaded by the user.Server Side Rendering
Brian refactors the application to move any DOM-specific code to a ClientApp module which will be the entry point for the browser once the server side rendered page is loaded. Frontend and backend targets are added to the package.json file to aid Parcel's compilation of the JSX code. As pages of the application are visited, the Node.js server renders out the React application and sends it to the client.Streaming Markup
Brian modifies the application to use HTTP streaming instead of sending the rendered application as one large string of content. Streaming leverages http2's ability to send content in chunks. Once the end of the stream is reached, the connection is closed. Questions about securing API keys, alternatives to Express, and using TypeScript are also covered in this segment.
TypeScript
TypeScript Setup
Brian explains how TypeScript adds static type checking within the code editor. It also can be self-documenting since the types help indicate the intended use of properties and methods. If a library does not use TypeScript, a @types module maintained by the open source community can often be installed to provide the type definitions.Refactor Modal
Brian refactors the Modal component to use TypeScript. The file extension is changed to .jsx. When the return type of a method is unknown, properties storing the return values must have a declared type.TypeScript & ESLint
Brian configures ESLint to work with TypeScript. This will force the codebase to adhere to a common coding style. Once everything is configured, the lint command can be run to view all the errors.ThemeContext
Brian refactors the ThemeContext component. An APIResponseTypes component is also created. This response type component can be imported anywhere API responses are being consumed which ensures proper type checking of the server data.Refactor Details
Brian begins refactoring the Details component. Component props can be typed either inline with the component definition or the props can be declared as a separate object.ErrorBoundary, Carousel & Pet
Brian refactors the ErrorBoundary, Carousel, and Pet components. The MouseEvent type is added to the click event parameter. Conditions are added to ensure the dataset.index property exists prior to updating the state. Questions about default props vs. interfaces are also discussed in this segment.Typing a Custom Hook
Brian refactors the useBreedList custom hook to use TypeScript. An enumerated Status type is added to the component to enforce the specific string values of the status state. A BreedListAPIResponse type is added to the APIResponseTypes component. A question about using the void keyword when handling async functions is also covered in this segment.Typing a Function Component
Brian adds the return type of FunctionalComponent to the SearchParams component since it returns JSX. PetAPIResponse, Animal, and Pet types are also imported and applied.Refactor Results & Add a Type Check Script
Brian completes the refactoring of the application to use TypeScript. Types are added to the Results component. The App component does not need any refactoring since all the imported components are now refactored. A type checking script is also added to the package.json file so the TypeScript checker can be run with a Github action.TypeScript Discussion
Brian shares some general thoughts about TypeScript. While TypeScript can slow down the development workflow due to the additional syntax or tooling, the benefit of the immediate feedback cycle creates more maintainable and readable code. Questions about bundle size and TypeScript/Webpack compatibility are also covered in this segment.
Redux
Redux
Brian introduces Redux and explains how it's an application data store that's easy to write tests against. Testing is easier because state management is pulled out of the React components. The architecture becomes more complex, but the modularity makes the code easier to maintain. Enabling the Redux Dev Tools is also covered in this segment.Reducers
Brian creates location, breed, animal, and theme reducers for the application. Reducers handle changes to state by receiving the current state and an action as parameters. Based on the action type, the state is modified and a new state is returned. The combineReducers method is used to build a single reducer from many sub reducers.Action Creators
Brian explains that action creators are helper functions that receive a payload and return a well-shaped action object to send to a reducer. A question about the order in which actions are dispatched is also addressed in this segment.Providers
Brian integrates Redux with the rest of the application. A Provider is added to App.js. Only the components within the provider will have access to the Redux store.Dispatching Actions
Brian adds the useDispatch hook to the SearchParams component to dispatch the redux actions when there is a change in state. The reducers receive the current state and the action payload and return the updated state to any subscribing components. A question about Rx.js vs Redux is also addressed in this segment.Redux Dev Tools
Brian demonstrates the Redux Dev Tools. Every dispatched action will be visible under the Actions tab. The Redux Dev Tools also allow for time-traveling debugging which gives developers the ability to step backward through each action and see the state of the application at that point in time. Jest, Mocha, and other testing template can be exported for each action to create unit tests.
Testing
Setup Jest & Testing Library
Brian shares his philosophy behind testings and configures Jest and Testing Library to work with the project. The .babelrc config file is updated with the testing configuration. Test and test:watch commands are added to the package.json file.Basic React Testing
Brian writes the first two tests for the Pet component. Using data attributes to give DOM elements test IDs is beneficial for decoupling the UI from the testing logic. The test:watch command is used to rerun tests automatically as the code is updated.Testing UI Interactions
Brian tests the Carousel component to ensure the hero image is updated when a thumbnail is clicked. Jest has an API for invoking JavaScript events like a click or mouse over. Once an event is invoked, tests can be written to verify the UI has been updated correctly.Testing Custom Hooks
Brian tests the useBreedList custom hook. Since hooks can only run inside a React component, a generic component is created within the test to run the hook. Testing Library's renderHook method eliminates the need to create a component by running the hook in a React component context.Mocks
Brian creates a test for the API data coming back from the server. A mock request is used to eliminate the need for the API to be called which can slow down the test suite and cause unnecessary load on the server. The mock is then passed into the custom hook.Snapshots
Brian demonstrates how snapshots save a rendered version of a component to a __snaps__ directory in the project. Future tests of the component will compare what's rendered currently to the snapshot. If the component is different, the test will fail. Shallow renders are also covered in this segment.Test Coverage with Istanbul
Brian walks through how to use Instanbul which provides an interactive viewer of how much code is covered by tests. The Jest extension for Visual Studio Code, which offers introspection into the status of all the tests within a project, is also demonstrated in this segment.