GitHub repo: pkellner/pluralsight-working-with-components-in-react
Toolchain: Next.js (Pages Router) · React 18 · JavaScript/JSX
Table of Contents
- Course Overview
- Implementing Components in a React App
- Exploring the React Application (Todo Manager)
- Sharing Global State and React Context
- Error Handling and Debugging
- Improving Component UI Performance
- Introduction to Server Components
- Reference Tables
- Summary
1. Course Overview
React is 100% a component-based library. Every React app starts with a component, and everything else flows from there. The analogy: looking at a photo (static HTML page with full-page reloads) vs watching a movie (dynamic React app where components change without page reloads).
What you will learn:
| Topic | Description |
|---|---|
| Prop passing patterns | Communication between components via props |
| State management | Local state, Context, HOCs |
| Error Boundaries | Catching and displaying errors gracefully |
| Performance | Avoiding over-rendering with memo, useCallback, useMemo |
| React DevTools | Debugging and profiling components |
| Server Components | Introduction to React Server Components |
2. Implementing Components in a React App
What is a Component?
A React component is an independent unit of code that serves a specific purpose. Together, they form a hierarchy (tree) to build a complete application.
graph TD
ToDoApp["ToDoApp (root)"]
Layout["Layout"]
Header["Header"]
Container["ToDoContainer"]
StatusBar["StatusBar"]
Toolbar["Toolbar"]
Form["ToDoAddForm"]
EditForm["ToDoEditForm"]
List["ToDoList"]
Item["ToDoItem ×N"]
Footer["Footer"]
ToDoApp --> Layout
Layout --> Header
Layout --> Container
Layout --> StatusBar
Container --> Toolbar
Container --> Form
Container --> EditForm
Container --> List
List --> Item
Layout --> Footer
Deep Dive: A Single Component
// TaskItem.js — Simple component with local state
import { useState } from "react";
function TaskItem({ label, completed, priority }) {
const [labelState, setLabelState] = useState(label);
return (
<div className={completed ? "completed" : ""}>
{priority ? "* " : ""}
{labelState}
</div>
);
}
export default TaskItem;
Key points:
- A component = a JavaScript function that returns JSX
JSXis a special syntax that transpiles to JavaScript- The name always starts with an uppercase letter (
ToDoItem, nottodoItem) - Props are received as the function parameter
Passing Parameters to Components
There are several ways to pass and receive props:
// 1. Explicit individual props
<ToDoItem text="Buy groceries" completed={false} important={true} />
// 2. Receiving with the full props object
function ToDoItem(props) {
return <div>{props.text}</div>;
}
// 3. Destructuring (recommended)
function ToDoItem({ text, completed, important }) {
return <div className={completed ? "completed" : ""}>{text}</div>;
}
// 4. Default value when prop is absent
function ToDoItem({ text, completed = false, important = false }) {
return (
<div className={completed ? "completed" : ""}>
{important ? "* " : ""}
{text}
</div>
);
}
Spread Operator for Props
// JS object → spread to child component
const taskData = {
text: "Buy groceries",
completed: false,
important: true,
};
// Spread passing — equivalent to individual props
<ToDoItem {...taskData} />
// ⚠️ SECURITY WARNING: avoid exposing sensitive data
// If the object contains { secretData: "..." }, it will be passed too!
// Prefer destructuring received props to avoid accidental exposure
function ToDoItem({ text, completed, important }) {
// secretData is NOT accessible here
}
State and Time Travel
import { useState, useEffect } from "react";
function ToDoItem({ text: initialText, completed }) {
// useState: [current value, setter function]
const [textState, setTextState] = useState(initialText);
// useEffect: runs after render (equivalent to DOM load)
useEffect(() => {
setTextState(`${initialText} (added: ${new Date().toLocaleTimeString()})`);
}, []); // [] = run only once on mount
// Click event handler
const handleClick = () => {
setTextState((prev) => `${prev} ✓`);
};
return (
<div
className={completed ? "completed" : ""}
onClick={handleClick}
>
{textState}
</div>
);
}
Principle: When the setter is called (setTextState), React re-renders the component with the new value.
3. Exploring the React Application (Todo Manager)
App Architecture
The Todo Manager application is built with Next.js (Pages Router). The full hierarchy:
graph LR
App["App\n(root - global state)"]
Layout["Layout\n(ThemeProvider)"]
Header["Header\n(ThemeToggle)"]
ToDoManager["ToDoManager\n(CRUD state)"]
ToDoAddForm["ToDoAddForm"]
ToDoEditForm["ToDoEditForm"]
ToDoList["ToDoList"]
ToDoItem["ToDoItem\n(×N)"]
Footer["Footer\n(StatusBar)"]
App --> Layout
Layout --> Header
Layout --> ToDoManager
Layout --> Footer
ToDoManager --> ToDoAddForm
ToDoManager --> ToDoEditForm
ToDoManager --> ToDoList
ToDoList --> ToDoItem
Global states in App:
displayStatus— filter: All / Completed / Activeimportant— boolean: show only important itemssearchText— filter text by description
Next.js Toolchain
# Create a new Next.js app
npx create-next-app@latest
# Important: choose Pages Router (NOT App Router)
# > Would you like to use the App Router? → No
Why Pages Router? With the Pages Router, all components are client components by default — they manage local state and DOM events. With the App Router, components are server components by default (cannot manage state or events).
Data Layer
App
└── TodosDataProvider (React Context)
└── useToDosData (custom hook)
└── useGeneralizedCrudMethods (generic hook)
└── REST API /api/todo (Next.js API route)
└── db.json (persistent store)
Interface exposed by the Context:
| Export | Type | Description |
|---|---|---|
todoList | Array | List of todos |
createTodo | Function | Create a todo |
updateTodo | Function | Update a todo |
deleteTodo | Function | Delete a todo |
isPending | Boolean | Loading state |
reFetch | Function | Reload data |
4. Sharing Global State and React Context
Three Ways to Propagate State
graph TD
A["3 State Sharing Methods"]
B["1. Props\n(simple, explicit)"]
C["2. React Context\n(global, entire tree)"]
D["3. HOC\n(Higher-Order Component)"]
A --> B
A --> C
A --> D
B --> B1["✅ Simple\n✅ Debuggable\n❌ Prop drilling"]
C --> C1["✅ No drilling\n✅ Flexible\n❌ Unexpected re-renders"]
D --> D1["✅ Reusable\n✅ Invisible props\n❌ Hidden complexity"]
Props Drilling
// ❌ Prop drilling — theme must pass through every level
function App() {
const [darkTheme, setDarkTheme] = useState(false);
const toggleTheme = () => setDarkTheme((prev) => !prev);
return (
<Layout toggleTheme={toggleTheme} darkTheme={darkTheme}>
{/* Layout does not use it, it just passes it along */}
</Layout>
);
}
function Layout({ toggleTheme, darkTheme, children }) {
// Layout does not consume toggleTheme, it just passes it to Header
return (
<>
<Header toggleTheme={toggleTheme} darkTheme={darkTheme} />
{children}
</>
);
}
// ✅ Spread operator to reduce boilerplate
function Layout(props) {
return (
<>
<Header {...props} />
{props.children}
</>
);
}
React Context API
Principle: A Context is a “global data share” accessible to all descendants of a component, without passing through props.
graph TD
Provider["ThemeContext.Provider\n(darkTheme, toggleTheme)"]
Layout["Layout"]
Header["Header\nuseContext(ThemeContext)"]
ToDoItem["ToDoItem\nuseContext(ThemeContext)"]
Other["Other components..."]
Provider --> Layout
Layout --> Header
Layout --> Other
Other --> ToDoItem
Implementing React Context
Step 1: Create and export the Context
// contexts/ThemeContext.js
import { createContext, useState, useContext } from "react";
// Create the context (optional initial value)
export const ThemeContext = createContext();
// Dedicated Provider component
export function ThemeProvider({ children }) {
const [darkTheme, setDarkTheme] = useState(false);
const toggleTheme = () => setDarkTheme((prev) => !prev);
return (
<ThemeContext.Provider value={{ darkTheme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
// Custom hook to consume the context (optional but recommended)
export function useTheme() {
return useContext(ThemeContext);
}
Step 2: Wrap the tree with the Provider
// _app.js or parent layout
function App({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}
Step 3: Consume the Context in any descendant
// Header.jsx
import { useContext } from "react";
import { ThemeContext } from "../contexts/ThemeContext";
function Header() {
// No longer need to receive darkTheme/toggleTheme as props!
const { darkTheme, toggleTheme } = useContext(ThemeContext);
return (
<header data-theme={darkTheme ? "dark" : "light"}>
<button onClick={toggleTheme}>Toggle Theme</button>
</header>
);
}
⚠️ Classic pitfall:
useContextruns BEFORE the render. If you place the Provider INSIDE the same component that callsuseContext, the Context will not be available yet at the time of the call.
Higher-Order Components (HOCs)
A HOC is a component that takes another component as a parameter and returns an enriched version with additional props.
graph LR
Original["Original Component\n(no theme state)"]
HOC["withTheme HOC\n(adds darkTheme + toggleTheme)"]
Enhanced["Enhanced Component\n(has darkTheme + toggleTheme)"]
Original -->|"passed as parameter"| HOC
HOC -->|"returns"| Enhanced
Creating a HOC:
// hocs/withTheme.js
import { useState } from "react";
function withTheme(Component) {
// Uppercase name required (React ESLint rule)
function Func(props) {
const [darkTheme, setDarkTheme] = useState(false);
const toggleTheme = () => setDarkTheme((prev) => !prev);
return (
<Component
{...props} // pass existing props
darkTheme={darkTheme} // add darkTheme
toggleTheme={toggleTheme} // add toggleTheme
/>
);
}
return Func;
}
export default withTheme;
Using the HOC:
// Header.jsx
import withTheme from "../hocs/withTheme";
function Header({ darkTheme, toggleTheme, appVersion }) {
// darkTheme and toggleTheme arrive "invisibly" via the HOC
return (
<header data-theme={darkTheme ? "dark" : "light"}>
<button onClick={toggleTheme}>Toggle Theme</button>
<span>v{appVersion}</span>
</header>
);
}
// Replace the default export with the enriched version
export default withTheme(Header);
HOC combined with Context (shared state):
// hocs/withTheme.js — version using Context to share state
import { useContext } from "react";
import { ThemeContext } from "../contexts/ThemeContext";
function withTheme(Component) {
function Func(props) {
const { darkTheme, toggleTheme } = useContext(ThemeContext);
return (
<Component
{...props}
darkTheme={darkTheme}
toggleTheme={toggleTheme}
/>
);
}
return Func;
}
export default withTheme;
Comparing Approaches
| Approach | Advantages | Disadvantages | When to Use |
|---|---|---|---|
| Props | Simple, explicit, easy to debug | Prop drilling in deep trees | Shallow trees, simple data |
| React Context | No drilling, accessible from any descendant | Can cause unexpected re-renders, information hiding | Global state: theme, auth, language |
| HOC | Reusable, logic encapsulation | ”Magic” props (invisible), wrapper hell possible | Sharing behavior between components |
5. Error Handling and Debugging
React DevTools — Components Tab
Chrome/Edge extension available on the Chrome Web Store (search “React Developer Tools”).
Features:
- Visualize the complete component hierarchy
- Inspect props and state of each component in real time
- Navigate to the source code of a component
- Highlight components during re-render (Settings → Highlight updates)
useDebugValue
Built-in hook to expose information in custom hooks via React DevTools.
// hooks/useTheme.js — without useDebugValue
function useTheme() {
const [darkTheme, setDarkTheme] = useState(false);
// In DevTools: just "State: false" — not very informative
return { darkTheme, toggleTheme: () => setDarkTheme((p) => !p) };
}
// hooks/useTheme.js — with useDebugValue
import { useState, useDebugValue } from "react";
function useTheme() {
const [darkTheme, setDarkTheme] = useState(false);
// Simple form: static label + value
useDebugValue(darkTheme ? "dark" : "light");
// Advanced form: 2nd parameter = formatting function
// (runs ONLY when DevTools is open — no cost in production)
useDebugValue(darkTheme, (theme) =>
theme ? "🌙 Dark theme active" : "☀️ Light theme active"
);
return {
darkTheme,
toggleTheme: () => setDarkTheme((prev) => !prev),
};
}
Best practice:
useDebugValueonly runs when DevTools is open. Use it freely — no production impact.
React DevTools — Profiler Tab
The Flamegraph visualizes which components re-rendered and how long each render took.
Flamegraph — Scenario: toggling a ToDoItem
┌──────────────────────────────────────────────┐ App
┌───────────────────────────────────────────┐ Layout
┌──────────────────────────────────────┐ ToDoManager
┌──────────────┐ ToDoList
┌──┐ ┌──┐ ┌──┐ ┌──┐ ┌──┐ ToDoItem ×5
🟡 🟡 🟡 🟡 🟡 (ALL re-render!)
- Grey = did not re-render
- Yellow/Orange/Red = re-rendered (darker = longer)
Profiling workflow:
- Profiler tab → “Start profiling” button
- Interact with the app (e.g., toggle an item)
- “Stop profiling”
- Analyze the flamegraph
Error Boundaries
Error Boundaries catch JavaScript errors in the child component tree and display a fallback UI instead.
Limitation: Error Boundaries must be class components (no equivalent hook for
componentDidCatch).
graph TD
EB["ErrorBoundary\n(class component)"]
TC["ToDoItem\n(may throw an error)"]
FallbackOK["Normal render\n✅"]
FallbackError["Fallback UI\n❌ Error message"]
EB -->|"no error"| FallbackOK
EB -->|"error caught"| FallbackError
TC -->|"wrapped by"| EB
Implementing an Error Boundary:
// components/ErrorBoundary.jsx
import { Component } from "react";
class ErrorBoundary extends Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null };
}
// Called when a descendant throws an error
static getDerivedStateFromError(error) {
return { hasError: true, error };
}
// For logging (Sentry, etc.)
componentDidCatch(error, errorInfo) {
console.error("ErrorBoundary caught:", error, errorInfo);
// logErrorToService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// Show fallback UI passed as prop, or a default message
return this.props.errorUI || <h2>Something went wrong.</h2>;
}
return this.props.children;
}
}
export default ErrorBoundary;
Usage with a custom fallback UI:
// components/ToDoItem.jsx
import ErrorBoundary from "./ErrorBoundary";
// Fallback component specific to ToDoItem
function ToDoErrorFallback(props) {
return (
<div className="alert alert-danger">
<strong>Error for this item:</strong>
<pre>{JSON.stringify(props, null, 2)}</pre>
</div>
);
}
function ToDoItem({ todoItem }) {
return (
<ErrorBoundary
errorUI={<ToDoErrorFallback {...todoItem} />}
>
<ToDoItemContent todoItem={todoItem} />
</ErrorBoundary>
);
}
Where to place Error Boundaries?
| Placement | Effect |
|---|---|
| Around the entire app | Global fallback (entire app goes down) |
| Around a section | Only the section shows the error |
| Around each list item | Only the problematic item shows the error ✅ |
6. Improving Component UI Performance
Over-rendering: The Problem
Over-rendering = a component re-renders when its props have not changed, solely because its parent re-rendered.
// Parent
function ToDoList({ todos }) {
const [filter, setFilter] = useState("all");
return (
<>
<FilterBar onFilterChange={setFilter} />
{todos.map((todo) => (
// ToDoItem re-renders EVERY TIME filter changes
// even if the todo itself has not changed!
<ToDoItem key={todo.id} todoItem={todo} />
))}
</>
);
}
Why does React do this? Because a component may have side effects tied to timing (e.g., displaying the time of the last render). React cannot know in advance whether the component needs to re-render or not — unless we tell it explicitly.
React.memo
React.memo is a built-in Higher-Order Component that memoizes the render result. The component re-renders ONLY if its props change.
import { memo } from "react";
// ❌ Without memo — re-renders on every parent render
function TaskLabel({ text, priority }) {
return (
<span>
{priority ? "★ " : ""}
{text}
</span>
);
}
// ✅ With memo — re-renders only if text or priority changes
const TaskLabel = memo(function TaskLabel({ text, priority }) {
return (
<span>
{priority ? "★ " : ""}
{text}
</span>
);
});
// Alternative: wrapping at export
export default memo(TaskLabel);
Custom comparison with memo:
// memo with a custom comparator
const TaskLabel = memo(
function TaskLabel({ text, priority }) {
return <span>{priority ? "★ " : ""}{text}</span>;
},
// Returns true if props are "equal" (no re-render)
(prevProps, nextProps) => {
return prevProps.text === nextProps.text &&
prevProps.priority === nextProps.priority;
}
);
⚠️ Don’t use
memoeverywhere! Each usage adds code and complexity. Measure first with the Profiler, then optimize only when necessary.
useCallback and useMemo
Problem with memo and functions:
// ❌ This pattern BREAKS memo!
function ToDoList({ todos }) {
// handleToggle is recreated on every ToDoList render
// So even with memo, ToDoItem re-renders because handleToggle !== handleToggle
const handleToggle = (id) => {
// toggle todo...
};
return todos.map((todo) => (
<ToDoItem
key={todo.id}
todoItem={todo}
onToggle={handleToggle} // new reference on every render!
/>
));
}
import { useCallback, useMemo } from "react";
function ToDoList({ todos }) {
// ✅ useCallback: memoizes the function, same reference between renders
const handleToggle = useCallback((id) => {
// toggle todo...
}, []); // dependencies: recreate only if these values change
// ✅ useMemo: memoizes a computed value
const completedCount = useMemo(
() => todos.filter((t) => t.completed).length,
[todos] // recompute only if todos changes
);
return (
<>
<p>Completed: {completedCount}</p>
{todos.map((todo) => (
<ToDoItem
key={todo.id}
todoItem={todo}
onToggle={handleToggle} // same reference → memo works!
/>
))}
</>
);
}
| Hook | Memoizes | Use Case |
|---|---|---|
React.memo | Entire component | Avoid re-render if props unchanged |
useCallback | Function reference | Stabilize callbacks passed as props |
useMemo | Computed value | Avoid expensive recalculations |
useTransition
useTransition marks state updates as non-urgent, allowing React to prioritize urgent updates (e.g., user input) before slow updates (e.g., rendering a long list).
import { useState, useTransition } from "react";
function SearchComponent({ todos }) {
const [query, setQuery] = useState("");
const [filteredTodos, setFilteredTodos] = useState(todos);
const [isPending, startTransition] = useTransition();
const handleSearch = (e) => {
const value = e.target.value;
// Urgent update: input remains immediately reactive
setQuery(value);
// Non-urgent update: can be deferred
startTransition(() => {
const filtered = todos.filter((t) =>
t.text.toLowerCase().includes(value.toLowerCase())
);
setFilteredTodos(filtered);
});
};
return (
<>
<input value={query} onChange={handleSearch} placeholder="Search..." />
{isPending && <span>Loading...</span>}
<ul>
{filteredTodos.map((todo) => (
<li key={todo.id}>{todo.text}</li>
))}
</ul>
</>
);
}
7. Introduction to Server Components
graph LR
SC["Server Component\n(Node.js server)"]
CC["Client Component\n(browser)"]
DB["Database / API"]
DB -->|"data"| SC
SC -->|"HTML + serialized data\n(via network boundary)"| CC
CC -->|"DOM interactions\nevents, state"| CC
Server Components vs Client Components:
| Characteristic | Server Component | Client Component |
|---|---|---|
| Where it runs | Node.js server | Browser |
| Local state | ❌ No | ✅ Yes |
| DOM events | ❌ No | ✅ Yes |
| Direct database access | ✅ Yes | ❌ No |
| JS bundle size | ✅ Zero | ❌ Added to bundle |
use client directive | Not required | Required |
Server Component example:
// app/speakers/page.jsx — Server Component (Next.js App Router)
// No "use client" → Server Component by default
async function SpeakersPage() {
// Direct server-side fetch (no useEffect, no useState)
const speakers = await fetch("https://api.example.com/speakers").then(
(r) => r.json()
);
return (
<div>
{speakers.map((speaker) => (
// SpeakerCard can be a Client Component for interactivity
<SpeakerCard key={speaker.id} speaker={speaker} />
))}
</div>
);
}
export default SpeakersPage;
Client Component with use client:
// components/SpeakerCard.jsx
"use client"; // ← required directive for client components
import { useState } from "react";
function SpeakerCard({ speaker }) {
const [isFavorite, setIsFavorite] = useState(false);
return (
<div className="card">
<h3>{speaker.firstName} {speaker.lastName}</h3>
<button onClick={() => setIsFavorite((f) => !f)}>
{isFavorite ? "❤️" : "🤍"}
</button>
</div>
);
}
export default SpeakerCard;
Next.js App Router vs Pages Router: In this course, the Pages Router is used to simplify learning (all components are client components by default). The App Router, recommended for new apps, enables Server Components by default.
8. Reference Tables
Component Patterns
| Pattern | Description | Use Case |
|---|---|---|
| Function Component | JS function returning JSX | Foundation of every React component |
| Props Drilling | Passing props level by level | Simple, shallow trees |
| React Context | Shared state via Provider/Consumer | Theme, auth, language, preferences |
| HOC | Wrapper component adding props | Behavior reuse |
| Compound Components | Linked components sharing implicit state | Tab, Accordion, Select |
| Render Props | Passing a function as a prop for rendering | Sharing rendering logic |
| Error Boundary | Class component catching errors | Protecting critical sections |
| memo | Memoizing a component’s render | Avoiding over-rendering |
Performance Hooks
| Hook | Signature | Purpose |
|---|---|---|
memo | memo(Component, compareFn?) | Memoize an entire component |
useCallback | useCallback(fn, deps) | Memoize a function reference |
useMemo | useMemo(() => value, deps) | Memoize a computed value |
useTransition | [isPending, startTransition] | Mark updates as non-urgent |
useDeferredValue | useDeferredValue(value) | Defer the update of a value |
Debugging Hooks
| Hook | Purpose |
|---|---|
useDebugValue(value, formatFn?) | Display info in React DevTools |
Lifecycle (Function Components)
import { useState, useEffect, useRef } from "react";
function LifecycleDemo() {
const [count, setCount] = useState(0);
const renderCount = useRef(0);
renderCount.current++;
// Mount (componentDidMount equivalent)
useEffect(() => {
console.log("Component mounted");
return () => {
// Cleanup (componentWillUnmount equivalent)
console.log("Component unmounted");
};
}, []);
// Update (componentDidUpdate equivalent)
useEffect(() => {
console.log(`count changed: ${count}`);
}, [count]); // dependency = count
return (
<div>
<p>Renders: {renderCount.current}</p>
<button onClick={() => setCount((c) => c + 1)}>+1</button>
</div>
);
}
When to Use What?
flowchart TD
Q1{"Need to share\nstate between components?"}
Q2{"Tree depth?"}
Q3{"Reusable logic\nacross multiple components?"}
Q4{"Identified\nperformance issue?"}
Props["✅ Simple Props"]
Context["✅ React Context"]
HOC["✅ HOC or Custom Hook"]
Memo["✅ memo + useCallback"]
Q1 -->|No| Q4
Q1 -->|Yes| Q2
Q2 -->|"Shallow\n(1-2 levels)"| Props
Q2 -->|"Deep\n(3+ levels)"| Q3
Q3 -->|No| Context
Q3 -->|Yes| HOC
Q4 -->|Yes| Memo
Q4 -->|No| Props
9. Summary
What You Learned
| Module | Key Concepts |
|---|---|
| Basic Components | Function components, JSX, props, state, useState, useEffect |
| Todo Manager App | Component hierarchy, Next.js toolchain, data layer with Context |
| State Sharing | Props drilling, React Context, HOCs, advantages/disadvantages |
| Debugging | React DevTools Components & Profiler, useDebugValue, highlight re-renders |
| Error Handling | Error Boundaries (class component), custom fallback UI |
| Performance | Over-rendering, React.memo, useCallback, useMemo, useTransition |
| Server Components | Server vs Client components, use client directive, Next.js App Router |
Best Practices
- ✅ Use destructuring to receive props (avoids accidental data exposure)
- ✅ Extract Context logic into dedicated files (
contexts/ThemeContext.js) - ✅ Start with simple props, add Context only when necessary
- ✅ Profile before optimizing — only add
memofor measured problems - ✅ Name your HOCs with the
withprefix (convention:withTheme,withAuth) - ✅ Place Error Boundaries as close as possible to the component that may fail
- ✅ Use
useDebugValuein your custom hooks to improve DevTools experience
Resources
| Resource | URL |
|---|---|
| Course GitHub repo | pkellner/pluralsight-working-with-components-in-react |
| React DevTools (Chrome) | chrome.google.com/webstore |
| React DevTools (Firefox) | addons.mozilla.org |
| Official React documentation | react.dev |
| Next.js documentation | nextjs.org/docs |
Search Terms
components · react · typescript · frontend · development · component · context · state · app · debugging · devtools · error · hooks · performance · props · tab