This course has been updated! We now recommend you take the Intermediate React, v5 course.
Table of Contents
IntroductionBrian 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 SetupBrian 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
useStateBrian 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.
useEffectBrian 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.
useContextBrian 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.
useRefBrian 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.
useReducerBrian 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.
useMemoBrian 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.
useCallbackBrian 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.
useLayoutEffectBrian 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.
useImperativeHandleBrian 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 & useIdBrian 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.
CSS & ReactBrian 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 & GradientsBrian 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 LibrariesBrian 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 BasicsBrian 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 PluginsBrian 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 & BreakpointsBrian 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.
PositioningBrian 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 SplittingBrian 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 RenderingBrian 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 MarkupBrian 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 SetupBrian 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 ModalBrian 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 & ESLintBrian 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.
ThemeContextBrian 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 DetailsBrian 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 & PetBrian 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 HookBrian 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 ComponentBrian 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 ScriptBrian 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 DiscussionBrian 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.
ReduxBrian 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.
ReducersBrian 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 CreatorsBrian 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.
ProvidersBrian 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 ActionsBrian 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 ToolsBrian 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.
Setup Jest & Testing LibraryBrian 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 TestingBrian 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 Custom HooksBrian 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.
MocksBrian 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.
SnapshotsBrian 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 IstanbulBrian 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.