Beginner

Working with Components in React

Implement components, share state with Context, handle errors and improve component UI performance.

GitHub repo: pkellner/pluralsight-working-with-components-in-react
Toolchain: Next.js (Pages Router) · React 18 · JavaScript/JSX


Table of Contents

  1. Course Overview
  2. Implementing Components in a React App
  3. Exploring the React Application (Todo Manager)
  4. Sharing Global State and React Context
  5. Error Handling and Debugging
  6. Improving Component UI Performance
  7. Introduction to Server Components
  8. Reference Tables
  9. 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:

TopicDescription
Prop passing patternsCommunication between components via props
State managementLocal state, Context, HOCs
Error BoundariesCatching and displaying errors gracefully
PerformanceAvoiding over-rendering with memo, useCallback, useMemo
React DevToolsDebugging and profiling components
Server ComponentsIntroduction 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
  • JSX is a special syntax that transpiles to JavaScript
  • The name always starts with an uppercase letter (ToDoItem, not todoItem)
  • 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 / Active
  • important — boolean: show only important items
  • searchText — 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:

ExportTypeDescription
todoListArrayList of todos
createTodoFunctionCreate a todo
updateTodoFunctionUpdate a todo
deleteTodoFunctionDelete a todo
isPendingBooleanLoading state
reFetchFunctionReload 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: useContext runs BEFORE the render. If you place the Provider INSIDE the same component that calls useContext, 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

ApproachAdvantagesDisadvantagesWhen to Use
PropsSimple, explicit, easy to debugProp drilling in deep treesShallow trees, simple data
React ContextNo drilling, accessible from any descendantCan cause unexpected re-renders, information hidingGlobal state: theme, auth, language
HOCReusable, logic encapsulation”Magic” props (invisible), wrapper hell possibleSharing 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: useDebugValue only 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:

  1. Profiler tab → “Start profiling” button
  2. Interact with the app (e.g., toggle an item)
  3. “Stop profiling”
  4. 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?

PlacementEffect
Around the entire appGlobal fallback (entire app goes down)
Around a sectionOnly the section shows the error
Around each list itemOnly 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 memo everywhere! 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!
        />
      ))}
    </>
  );
}
HookMemoizesUse Case
React.memoEntire componentAvoid re-render if props unchanged
useCallbackFunction referenceStabilize callbacks passed as props
useMemoComputed valueAvoid 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:

CharacteristicServer ComponentClient Component
Where it runsNode.js serverBrowser
Local state❌ No✅ Yes
DOM events❌ No✅ Yes
Direct database access✅ Yes❌ No
JS bundle size✅ Zero❌ Added to bundle
use client directiveNot requiredRequired

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

PatternDescriptionUse Case
Function ComponentJS function returning JSXFoundation of every React component
Props DrillingPassing props level by levelSimple, shallow trees
React ContextShared state via Provider/ConsumerTheme, auth, language, preferences
HOCWrapper component adding propsBehavior reuse
Compound ComponentsLinked components sharing implicit stateTab, Accordion, Select
Render PropsPassing a function as a prop for renderingSharing rendering logic
Error BoundaryClass component catching errorsProtecting critical sections
memoMemoizing a component’s renderAvoiding over-rendering

Performance Hooks

HookSignaturePurpose
memomemo(Component, compareFn?)Memoize an entire component
useCallbackuseCallback(fn, deps)Memoize a function reference
useMemouseMemo(() => value, deps)Memoize a computed value
useTransition[isPending, startTransition]Mark updates as non-urgent
useDeferredValueuseDeferredValue(value)Defer the update of a value

Debugging Hooks

HookPurpose
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

ModuleKey Concepts
Basic ComponentsFunction components, JSX, props, state, useState, useEffect
Todo Manager AppComponent hierarchy, Next.js toolchain, data layer with Context
State SharingProps drilling, React Context, HOCs, advantages/disadvantages
DebuggingReact DevTools Components & Profiler, useDebugValue, highlight re-renders
Error HandlingError Boundaries (class component), custom fallback UI
PerformanceOver-rendering, React.memo, useCallback, useMemo, useTransition
Server ComponentsServer 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 memo for measured problems
  • ✅ Name your HOCs with the with prefix (convention: withTheme, withAuth)
  • ✅ Place Error Boundaries as close as possible to the component that may fail
  • ✅ Use useDebugValue in your custom hooks to improve DevTools experience

Resources

ResourceURL
Course GitHub repopkellner/pluralsight-working-with-components-in-react
React DevTools (Chrome)chrome.google.com/webstore
React DevTools (Firefox)addons.mozilla.org
Official React documentationreact.dev
Next.js documentationnextjs.org/docs

Search Terms

components · react · typescript · frontend · development · component · context · state · app · debugging · devtools · error · hooks · performance · props · tab

Interested in this course?

Contact us to book it or get a custom training plan for your team.