feat: add react debugging video ids and transcripts (#59632)

Co-authored-by: Dario-DC <105294544+Dario-DC@users.noreply.github.com>
Co-authored-by: Zaira <33151350+zairahira@users.noreply.github.com>
This commit is contained in:
Kristofer Koishigawa
2025-04-16 01:04:12 +09:00
committed by GitHub
parent f0ba8441d5
commit f8c5a86285
4 changed files with 532 additions and 16 deletions
@@ -2,13 +2,118 @@
id: 67d1ad82cff954a854bcbcaa
title: What Is Prop Drilling?
challengeType: 11
videoId: nVAaxZ34khk
videoId: 83LkOesFkWI
dashedName: what-is-prop-drilling
---
# --description--
Watch the video lecture and answer the questions below.
Watch the video or read the transcript and answer the questions below.
# --transcript--
What is prop drilling?
Prop drilling is the most basic approach to state management in React applications. It looks simple, but can get messy quickly, and is very hard to scale.
Let's look at what prop drilling is, why it's a problem, and a good replacement for it as an application grows.
Prop drilling is the process of passing props from a parent component to deeply nested child components, even when some of the child components don't need the props.
For example, say you have three components named `Parent`, `Child`, and `Grandchild`. If you want to use some data in the `Grandchild` component, but it's in the `Parent` component, you'd need to pass it from the `Parent` to the `Child` component, then from the `Child` to the `Grandchild` component.
Or if the data is even further up the chain, the data might have to be passed to the `Parent` component, too.
Here, the data I want to display is the string `Hello, Prop Drilling!`. It's assigned to the `greeting` variable in the root `App` component:
```jsx
import "./App.css";
import Parent from "./Parent";
function App() {
const greeting = "Hello, Prop Drilling!";
return <Parent greeting={greeting} />;
}
export default App;
```
You can see the `Parent` component is also receiving the `greeting` variable as the value of a `greeting` prop. Here's the `Parent` component passing it into the `Child` component as the value of another `greeting` prop in the `Child`:
```jsx
import Child from "./Child";
const Parent = ({ greeting }) => {
return <Child greeting={greeting} />;
};
export default Parent;
```
And here's the `Child` component that passes it to the `Grandchild` component:
```jsx
import Grandchild from "./Grandchild";
const Child = ({ greeting }) => {
return <Grandchild greeting={greeting} />;
};
export default Child;
```
And finally the `Grandchild` component receives the greeting and uses it as the content of an `h1` element:
```jsx
const Grandchild = ({ greeting }) => {
return <h1>{greeting}</h1>;
};
export default Grandchild;
```
In the browser, you'll see a page with a single `h1` element that has the text `Hello, Prop Drilling!`.
At first, prop drilling might not seem like such a big deal. But as your app grows, it gets harder to understand, debug, and maintain.
If you need to pass props around, try to keep them all in a single parent component. This approach of centralizing all necessary data is called the "single source of truth".
For instance, say you want to add a new `response` to go with your `greeting`, and that you want to use both of them in the `Grandchild` component. Since `greeting` is already in the `App` component, it makes sense to put `response` there, too, and pass both of them down the chain:
```jsx
function App() {
const greeting = "Hello, Prop Drilling!";
const response = "I'm not here to play!";
return <Parent greeting={greeting} response={response} />;
}
const Parent = ({ greeting, response }) => {
return <Child greeting={greeting} response={response} />;
};
const Child = ({ greeting, response }) => {
return <Grandchild greeting={greeting} response={response} />;
};
const Grandchild = ({ greeting, response }) => {
return (
<>
<h1>{greeting}</h1>
<h2>{response}</h2>
</>
);
};
export default App;
```
In the browser, you'll see a page with an `h1` element that has the text `Hello, Prop Drilling!` and an `h2` element that has the text `I'm not here to play!`.
To avoid prop drilling, especially in large, complex applications, consider using the Context API or state management libraries like Redux and Redux Toolkit, Zustand, Recoil, and others.
You'll learn more about these in the coming lectures.
# --questions--
@@ -2,13 +2,199 @@
id: 67d2f5b78609f97400923f7f
title: What Are State Management Libraries, and When Should You Use Them?
challengeType: 11
videoId: nVAaxZ34khk
videoId: 9GBjI8LDauU
dashedName: what-are-state-management-libraries-and-when-should-you-use-them
---
# --description--
Watch the lecture video and answer the questions below.
Watch the video or read the transcript and answer the questions below.
# --transcript--
What are state management libraries, and when should you use them?
As your app grows, managing how data flows between components can become complex.
When starting out, React's `useState` hook might be sufficient, but as you add features, you might encounter issues with:
- Passing props through components that don't need them, also known as prop drilling
- Keeping data in sync across different parts of your app
- Handling complex updates that affect multiple components simultaneously
These and other challenges may arise, which can lead to a codebase that's harder to maintain, debug, and test. That's where state management libraries come in they provide a centralized place where components can get or update the data they need.
Let's take a look at a few different state management options you have, and when to use them.
The Context API is a state manager built into React that lets you share state across components without using a third-party library. It's a well-established upgrade over the `useState` hook, so it is perfect for cases like theme toggling or user authentication status.
However, the Context API does not handle frequent updates well, and can cause unnecessary re-renders, making it less suitable for complex state needs in applications like eCommerce and social media platforms. 
Here's a counter component that demonstrates the basic usage of the Context API:
```jsx
import { useState, createContext } from 'react';
const CounterContext = createContext();
const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
return (
<CounterContext.Provider value={{ count, setCount }}>
{children}
</CounterContext.Provider>
);
};
export { CounterContext, CounterProvider };
```
This code creates a context and a provider to share a `count` state across the application.
`CounterProvider` uses the `useState` hook to initialize and manage the `count` state and its setter. Both are then passed into child components through the `Provider`.
So, when you wrap your whole app with the `CounterProvider`, the `count` state is available everywhere in your application.
Here's how you can wrap `CounterProvider` around your application:
```jsx
import { CounterProvider } from './context/CounterContext';
function App() {
return (
<CounterProvider>
{/* App components */}
</CounterProvider>
);
}
export default App;
```
And here's how you can use the `count` state:
```jsx
import React, { useContext } from 'react';
import { CounterContext } from '../context/CounterContext';
const Counter = () => {
const { count, setCount } = useContext(CounterContext);
return (
<>
<div style={{ textAlign: 'center' }}>
<h1>Context API Counter</h1>
<button style={{ marginRight: '5px' }} onClick={() => setCount(count - 1)}>
Decrease
</button>
<span>{count}</span>
<button style={{ marginLeft: '5px' }} onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
</>
);
};
export default Counter;
```
As you can see, the `count` and its setter function, `setCount`, are initialized through the `useContext` function.
The current `count` state is then displayed, and `setCount` is used to increase and decrease the `count` state when the user clicks the decrement and increment buttons respectively.
Another popular state management library is Redux, which is one of the most popular state management libraries to use with React. It's been around for a long time, and is ideal for larger applications like eCommerce and social media platforms, forums, and so on.
Redux handles state management by providing a central store and strict control over state updates. It uses a predictable pattern with actions, reducers, and middleware.
Actions are payloads of information that send data from your application to the Redux store, often triggered by user interactions.
Reducers are functions that specify how the state should change in response to those actions, ensuring the state is updated in an immutable way.
Middleware, on the other hand, acts as a bridge between the action dispatching and the reducer, allowing you to extend Redux's functionality (for example, logging, handling async operations) without modifying the core flow.
The most common complaint about Redux is with all the boilerplate code you need to get started. In response, the Redux team introduced "Redux Toolkit" and "RTK Query", which simplify the setup process quite a bit.
You typically define both actions and reducers in a single file using the `createSlice()` function. It's common to name the file so it ends with the word `Slice`, for example, `productSlice`, `userSlice`, `counterSlice`, and so on.
Here's a `counterSlice` file to show you the basics:
```jsx
import { createSlice } from '@reduxjs/toolkit';
const counterSlice = createSlice({
name: 'counter',
initialState: { count: 0 },
reducers: {
increment: (state) => {
state.count += 1;
},
decrement: (state) => {
state.count -= 1;
},
},
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
```
From here, you then need to wrap the entire app with the `Provider`, select a piece of state from the slice with `useSelector()`, then use `useDispatch()` to make the state active.
Another option to consider is Zustand.
Zustand is a lightweight state management library with a simple API. It is based on hooks, so there's less boilerplate compared to Redux, making it easier and quicker to set up.
Zustand is ideal for small to medium-scale applications. It works by using a `useStore` hook to access access state directly in components and pages. This lets you modify and access data without needing actions, reducers, or a provider.
Here's a `useCounterStore` that implements another counter functionality:
```jsx
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
export default useCounterStore;
```
And here's how to initialize and use the states in your app:
```jsx
// Import the useCounterStore (it's just a hook)
import useCounterStore from '../useCounterStore';
const Counter = () => {
// Initialize the states with the useCounterStore hook
const { count, increment, decrement } = useCounterStore();
return (
<>
<div style={{ textAlign: 'center' }}>
<h1>Zustand Counter</h1>
<button style={{ marginRight: '5px' }} onClick={() => decrement()}>
Decrease
</button>
<span>{count}</span>
<button style={{ marginLeft: '5px' }} onClick={() => increment()}>
Increase
</button>
</div>
</>
);
};
export default Counter;
```
Even though the frontend ecosystem is constantly evolving and new state management libraries regularly emerge, the ones we've discussed are widely used in the industry.
# --questions--
@@ -86,35 +272,35 @@ Axios
## --text--
What is the popular complaint about Redux, and how was it addressed?
What was a common complaint about Redux, and how was it addressed?
## --answers--
Limited browser support; addressed by creating polyfills.
It had limited browser support, which was addressed by creating polyfills.
### --feedback--
Think about improvements made to reduce setup complexity.
Think about the improvements that were made to reduce setup complexity.
---
Performance issues; addressed by optimizing middleware.
It had performance issues, which were addressed by optimizing its middleware.
### --feedback--
Think about improvements made to reduce setup complexity.
Think about the improvements that were made to reduce setup complexity.
---
Complexity of boilerplate code; addressed with Redux Toolkit and RTK Query.
It required a lot of complex boilerplate code, which was addressed by Redux Toolkit and RTK Query.
---
Lack of documentation; addressed by adding more examples.
There was a lack of documentation, which was addressed by adding more examples.
### --feedback--
Think about improvements made to reduce setup complexity.
Think about the improvements that were made to reduce setup complexity.
## --video-solution--
@@ -2,13 +2,97 @@
id: 67d2f5dacd5e0c749e5d534c
title: How Can You Debug Your React Components Using the React DevTools?
challengeType: 11
videoId: nVAaxZ34khk
videoId: ghTDVD-hPuc
dashedName: how-can-you-debug-your-react-components-using-react-devtools
---
# --description--
Watch the lecture video and answer the questions below.
Watch the video or read the transcript and answer the questions below.
# --transcript--
How can you debug your React components using the React DevTools?
The browser has built-in developer tools you can use to debug HTML, CSS, and JavaScript.
However, they're not great for finding and fixing bugs in React apps. So the React team developed a tool called "React Developer Tools" (AKA React DevTools) so you can inspect, debug, and profile React apps.
React DevTools is available as a browser extension for Chrome, Edge, and Firefox. If you're on Chrome or Edge, head over to the Chrome web store, search for "React Developer Tools”, and add it to your browser.
And if you use Firefox, head over to the Firefox Add-ons page, search for the tool, and add it to your browser.
If you use Safari, you can install React DevTools from npm by running `npm install -g react-devtools` or `yarn global add react-devtools`.
After installing and enabling React DevTools, if you open a React app in your browser, then open your browser's developer tools, you should see two extra tabs: Components and Profiler.
The Components tab displays each component for you in a tree view format. With it, you can: 
- View the app's component hierarchy.
- Check and modify props, states, and context values in real time.
- Check the source code for each selected component.
- Log the component data to the console.
- Inspect the DOM elements for the component.
On the other hand, the Profiler tab helps you record and analyze component performance so you can identify unnecessary re-renders, view commit durations, and things you can optimize.
Here's a simple app to show you how you can inspect components and any props and state they have. This is similar to the code we used in a previous lecture on prop drilling:
```jsx
import { useState } from "react";
export default function App() {
const greeting = "Hello, Prop Drilling!";
const response = "I'm not here to play!";
return <Parent greeting={greeting} response={response} />;
}
const Parent = ({ greeting, response }) => {
return <Child greeting={greeting} response={response} />;
};
const Child = ({ greeting, response }) => {
return <Grandchild greeting={greeting} response={response} />;
};
const Grandchild = ({ greeting, response }) => {
const [count, setCount] = useState(0);
return (
<>
<h1>{greeting}</h1>
<h2>{response}</h2>
<button onClick={() => setCount(count + 1)}>Increase Count</button>
<h2>Count: {count}</h2>
<button onClick={() => setCount(count - 1)}>Decrease Count</button>
</>
);
};
```
If you look in the Components tab in React DevTools, you can see the tree view of the components. The `App` component is at the top, followed by the `Parent`, `Child`, and `Grandchild` components.
If you select any of these components, you can see the props and state in them. If you select the `Parent` component, you can see the `greeting` and `response` props, which are `Hello, Prop Drilling!` and `I'm not here to play!`, respectively.
You can see the props and update state in real-time, and change them if necessary. For instance, you can select the `Grandchild` component and change the `greeting` prop from `Hello, Prop Drilling!` to `Hello, Welcome to Prop Drilling!`, and see it reflected on the page immediately.
To log data in a component to the console, inspect the matching DOM elements and view the source code of the component. The icons in the top right corner let you do that. If you select the `Grandchild` component and click the `Log the component data to the console` button, it will log the props, state, hooks, nodes, and other data in the console.
A common bug you might encounter in React is called props mismatch.
For example, say that for the `Child` component, you mistakenly pass in `reply` as the prop instead of `response`:
```jsx
const Child = ({ greeting, response }) => {
return <Grandchild greeting={greeting} reply={response} />;
};
```
Remember that `Grandchild` expects a `response` prop. Because the component receives a different prop, it can't display that text on the page, and just adds and empty `h2` to the DOM. Instead, you'll just see the `h1` element with the text `Hello, Prop Drilling!`, along with the other buttons and text already on the page. The empty `h2` element is still there, but because it's empty, you can't see it without inspecting the DOM.
To fix this, you can inspect the prop progression from the `Parent` component down to the `Child` and edit the prop name directly. If you go to the Components tab, select the `Child` component, and change the `reply` prop to `response`, you'll see the `h2` element on the page with the text `I'm not here to play!`.
# --questions--
@@ -2,13 +2,154 @@
id: 67d2f5f19a0f0b75343f1905
title: What Are React Server Components, and How Do They Work?
challengeType: 11
videoId: nVAaxZ34khk
videoId: weeLaRgDBmg
dashedName: what-are-react-server-components-and-how-do-they-work
---
# --description--
Watch the lecture video and answer the questions below.
Watch the video or read the transcript and answer the questions below.
# --transcript--
What are React Server Components, and how do they work?
React Server Components (RSCs) is a new trend that has changed the way React developers approach things. With RSCs, more work shifts to the server, which has a lot of benefits.
Let's take a look at what server components are, how they work, and what led to the introduction of server components.
React Server Components are React components that render exclusively on the server, which sends only the final HTML to the client. This means those components can directly access server-side resources and dramatically reduce the amount of JavaScript sent to the browser.
React apps have traditionally used a "client component" system that handles everything in a typical React app, such as rendering, interactivity, and side effects. The term "client component" was rarely used until the introduction of React server components recently.
But the client component system comes with some drawbacks like large JavaScript bundles and slower initial load times.
React frameworks like Next.js and Gatsby found workarounds to offload some processes to the server in order to fix those problems, but none of them were standardized. If you've used either framework, you've probably heard about `getServerSideProps` and `getServerData`.
Then came React Server Components, which let you run some components entirely on the server so you can do things like data fetching and computation before any code runs in the user's browser.
Server components were first popularized and are readily available in Next.js. Other frameworks like Remix and Gatsby are catching up, and there's an experimental plugin for Vite called `vite-plugin-react-server` which lets you build server components.
So how do server components work?
One of the best ways to demonstrate React Server Components is with data fetching.
In traditional React client components, you let the browser handle API requests. Since data fetching is a side effect, you make that API call in a `useEffect` hook.
It's also good practice to set state variables like loading, data, and error so you can indicate that the data is loading, display the data when it's ready, or display an error in your app.
With React Server Components, you can move the entire component to the server and fetch data there without having to use `useState` or `useEffect`:
```jsx
const Users = async () => {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const users = await res.json();
return (
<>
<h1 className="text-4xl text-center mt-6">Users</h1>
<ul className="text-center mt-3">
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default Users;
```
Because React Server Components only run on the server, you can just fetch data from an API and render just once. Also, since data fetching happens on the server, closer to the source, your app may perform better, especially for people with slow network connections.
Once major gotcha is that all the code for server components remain on the server, and doesn't get shipped to the browser. That means you can't use React hooks with them, and they don't have access to Web APIs or browser event listeners. So how can you add interactivity?
In the Next.js app router, all components are server components by default. If you want to add interactivity, you need to mark the component as a client component with the `"use client"` directive.
Let's say you want to make the previous example a client component. Here's how you can do that:
```jsx
"use client";
import { useState, useEffect } from "react";
const Users2 = () => {
const [status, setStatus] = useState({
users: [],
loading: true,
error: null,
});
async function fetchUsers() {
try {
const res = await fetch("https://jsonplaceholder.typicode.com/users");
const data = await res.json();
setStatus((prevStatus) => ({
...prevStatus,
users: data,
loading: false,
}));
} catch (err) {
setStatus((prevStatus) => ({
...prevStatus,
error: err.message,
loading: false,
}));
}
}
useEffect(() => {
fetchUsers();
}, []);
if (status.loading) {
return <p>Loading Users...</p>;
}
if (status.error) {
return <p>Error getting users: {status.error}</p>;
}
return (
<>
<h1 className="text-4xl text-center mt-6">Users</h1>
<ul className="text-center mt-3">
{status.users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</>
);
};
export default Users2;
```
If you want to add interactivity like click events, the component also has to be marked as a client component:
```jsx
"use client";
import { useState } from "react";
const Counter = () => {
const [count, setCount] = useState(0);
return (
<>
<h1>Counter</h1>
<button onClick={() => setCount(count + 1)}>Increment</button>
<h2>{count}</h2>
<button onClick={() => setCount(count - 1)}>Decrement</button>
</>
);
};
export default Counter;
```
If you don't add the use client directive to the component, you get an error with a message that says "You're importing a server component that needs `useState`. This React hook only works in a client component. To fix, mark the file (or its parent) with the `"use client"` directive."
The main benefits that come with React Server Components are that data fetching becomes simpler, the code is easier to read, and client complexity is reduced.
# --questions--