Intermediate React, v5
Table of Contents
IntroductionBrian Holt begins the course by walking through the course website and sharing some recommended development tools. Lessons are independent and each have their own standalone code example.
Hooks in Depth
useRefBrian introduces the useRef hook which can store references to DOM elements. This way the component can guarantee the same element is referenced during any re-render cycles. Combining useRef with the memo method will eliminate re-rendering and increase performance for expensive components.
useReducerBrian demonstrates how useReducer can create Redux-style reducers inside a hook. This approach creates very testable reducers since the function receives state and action parameters and returns a new state.
useMemoBrian explains how useMemo can add performance optimizations to a component by eliminating re-rendering in the DOM. This can help keep animations smooth and reduce studdering or janky behavior.
useCallbackBrian demonstrates that similar to useMemo, the useCallback hook memoizes a callback function. This allows a resource-intensive function to be isolated so it will not run on every render.
useLayoutEffect & useIDBrian explains that useLayoutEffect is called immediately after the render function so any calls to setState inside that effect will result in only one re-render instead of two.
Additional HooksBrian walks through a some additional hooks that are not frequently used, but helpful in specific situations. These hooks include useImperativeHandle, useDebugValue, useSyncExternalStore, and useInsertionEffect.
AdoptMe Project SetupBrian walks through the AdoptMe project and installs the dependencies. The application used in this course picks up where the Complete Introduction to React, v8 course left off.
Installing Tailwind CSSBrian installs and configures Tailwind CSS and begins applying CSS classes to the project. Tailwind CSS is a utility-first CSS framework with classes like p-0, text-center, and rotate-90 that can be composed to build any design, directly in markup.
Styling with Tailwind CSSBrian adds more CSS styling to the project with Tailwind CSS classes. Styling includes text sizing, gradients, shadows, rounded corners, and flexbox layout.
Adding a Tailwind CSS Form PluginBrian installs the tailwindcss/forms plugin which adds default styling to form elements. The plugin is configured in the tailwind.config.js file. Styling rules target "type" attribute of a form element. Additional styling can be added with CSS classes.
Extending Tailwind CSSBrian demonstrates how the @apply directive in Tailwind CSS allows multiple CSS classes to be combined into a single selector. This creates a custom layer which can be added to multiple elements eliminating the need to duplicate long class lists.
Layout with Tailwind CSSBrian applies Tailwind CSS grid layout classes to the project. The classes have the ability to specify which classes should apply to which breakpoints. Relative positioning is also demonstrated in this lesson.
Advanced React Performance
Code SplittingBrian introduces code splitting which allows individual components to be lazy-loaded when they are required instead of with the initial application bundle. This technique can help meet performance budgets and improves user experience on slower connections.
Server Side RenderingBrian explains how server-side rendering runs React code on a Node.js server before the response is sent to the user. This allows the initial portion of the application to be rendered and sent back to the user while the server creates a readable stream of the remaining content.
Node Streams with ExpressBrian creates an Express server in Node.js to server-side render the React application. The HTML document is split into two parts. The first part is sent back to the user immediately while the remaing part is returned once the React application is hydrated.
Server-Side FetchingBrian demonstrates server-side fetching, which is an experimental feature available in Node version 18. Without any code modifications, Brian configures the project to run the fetch request on the server so the results are rendered server-side and sent to the user with the rest of the server-rendered markup.
Low Priority Re-Rendering
useDeferredValueBrian demonstrates the useDeferredValue hook and explains how it can reduce re-rendering by caching data, like the results of a fetch request. If the current render is the result of an urgent update, React will return the previous value and then render the new value after the urgent render has completed.
useTransitionBrian uses the useTransition hook to defer showing a loading state in the UI until all other high-priority rendering is completed.
Setup TypeScriptBrian explains the benefits of TypeScript while configuring the application. The TypeScript npm package is installed along with the React @types packages. These enable type-checking since the React framework is not coded natively in TypeScript. A minimalistic tsconfig.json configuration file is also provided.
Typing Modal ComponentBrian begins the TypeScript migration with the Modal Component. The component file is renamed to a tsx extension which triggers the TypeScript checking. Types are added throughout the file to eliminate type errors.
Typing an API ResponseBrian continues refactory the project to use TypeScript. The Details component receives responses from an API call. An APIResponsesTypes file is created to declare the types expected from the API.
Typing Details & Context ComponentsBrian refactors the Details and Context components to use TypeScript. When typing the createContext method, a default value is given making it easy to run unit tests with context.
Typing Carousel ComponentBrian adds types to the Carousel component. The DOM event type is a MouseEvent. An interface for the shape of the state is not required because TypeScript can infer the types from the setState method implementation.
Typing Pet Component & Error BoundaryBrian adds types to the Pet and ErrorBoundary components. The QueryFunction type is also imported and used in fetchPet and fetchSearch which explicitly describes both the shape of the API response and the expected keys passed to the API.
Typing Component with React QueryBrian continues the TypeScript refactor of the codebase with fetchBreedList and useBreedList. QueryFunction is used to type the fetch request. Return types are added to the useBreedList hook to ensure the type and order of the values is enforced.
Typing Component with a FormBrian completes the refactoring of the application to TypeScript by adding types to the form data in the SearchParams component. The Details and App components are also refactored.
Setup Redux ToolkitBrian introduces Redux and the Redux Toolkit (RTK). Redux removes state-management from React and moves it to a separate store which makes the code easier to test than code that uses context.
Creating a SliceBrian uses the Redux Toolkit to create an adoptPetSlice which is a combination of reducers, state, and action creators. It defines an initial state and the actions created by the React Toolkit are exported along with the reducer.
Wiring Redux to ComponentsBrian adds the Redux store to the Details component. The useDispatch hook will send the new value to the store. In the SearchParams component, the useSelector hook will update the adoptedPet property anytime it is updated in the store.
Adding Search Params to ReduxBrian creates another slice for storing the search parameters. An "all" action is created which will update all the search parameter when they are dispatched from the SearchParams component.
Redux Toolkit Query & MiddlewareBrian adds the RTK query to the petApiService which allows all the necessary endpoints to be built around a base URL. Parameters for each endpoint can be specified as well. A middleware is also added which allows for additional caching of reponses directly in the Redux store.
Redux Dev ToolsBrian demonstrates time-traveling debugging in the Redux Dev Tools browser extension. Actions can be inspected and tests can be generated directly in the extension.
Setup React Testing Library & VitetestBrian installs the Vitest test runner and happy-dom which is an alternative to js-dom. A library like happy-dom is required to simulate the browser environment. This is faster than spinning up a browser with a tool like Puppeteer.
Testing a ComponentBrian creates a __tests__ directory to hold all test files. Tests are written for the Pet component to ensure the component displays a default thumbnail when no image is provided and a non-default thumbnail when there is an image provided.
Testing User InteractionBrian writes a test to ensure the proper image is displayed and the active class is applied when a user clicks on a thumbnail. The Carousel component is rendered and supplied an array of images. The click() method is called to simulate a user click event.
Testing Custom HooksBrian tests the useBreedList custom hook. First, the hook is tested with an older sytax which involved providing a temporary React component to run the hook. The code is then refactored to use the renderHook() method which simplifies the test.
Testing MocksBrian uses the mockReponseOnce() method to test an API request. A mocked breedlist is passed to the request. When the result is received, the test validates the request was successful and checks the breeds returned are equal to the mocked data.
SnapshotsBrian demonstrates snapshot tests and explains why they are low confidence, low cost ways of writing tests. A copy of the rendered markup is saved in a __snapshops__ directory. When the test is run again, the current markup is compared to the saved copy. If something has changed, the test fails.
Testing CoverageBrian demonstrates how to generate a test coverage report. The report summarizes the percentage of statements, functions, and lines of code covered by a test. A web interface is also generated inside the src/coverage directory which allows developers to click on source files and view which lines are not covered.
Vitest VSCode ExtensionBrian demonstartes the Vitest VSCode extension. It adds a panel which displays each test and allows developers to individually view or run a test.