DEV Community

Cover image for React Explained: The Complete Guide
Hamed Farazi
Hamed Farazi

Posted on

React Explained: The Complete Guide

React is one of the most widely used libraries for building modern user interfaces.

Whether you're building a small interactive website, a dashboard, an e-commerce application, or a complex web platform, React provides a component-based approach that helps you build reusable, maintainable UI.

But learning React isn't just about memorizing hooks or writing JSX.

To understand React properly, you need to understand how components work, how data flows through an application, how state changes the UI, and how React manages rendering.

In this guide, we'll explore the most important React concepts — from the fundamentals to advanced patterns — using practical examples.

This article is designed as a roadmap and reference for developers learning React.


Table of Contents

  1. What Is React?
  2. Why React?
  3. Declarative vs. Imperative Programming
  4. Creating a React Application
  5. Understanding JSX
  6. Components
  7. Props
  8. State
  9. Events
  10. Conditional Rendering
  11. Rendering Lists and Keys
  12. Component Composition
  13. State Management and Data Flow
  14. React Hooks
  15. useState
  16. useEffect
  17. useRef
  18. useContext
  19. useReducer
  20. Custom Hooks
  21. Forms in React
  22. API Requests
  23. React Router
  24. State Management Libraries
  25. React Performance
  26. React Internals
  27. Virtual DOM and Reconciliation
  28. React Fiber
  29. React 19
  30. Server Components
  31. Error Boundaries and Suspense
  32. Testing React Applications
  33. React Best Practices
  34. Common React Mistakes
  35. React Learning Roadmap
  36. Final Thoughts

1. What Is React?

React is a JavaScript library for building user interfaces.

It was developed at Facebook, now Meta, and is maintained by Meta and the React community.

React helps developers build interfaces using small, reusable pieces called components.

Instead of manually manipulating the DOM every time something changes, developers describe what the UI should look like based on the current data.

React then manages updating the interface.

For example, imagine a dashboard containing:

  • A navigation bar
  • A sidebar
  • A user profile
  • A notification panel
  • A list of products

Each section can be represented by a separate component.

function App() {
  return (
    <div>
      <Navbar />
      <Sidebar />
      <Dashboard />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This approach makes applications easier to organize, reuse, and maintain.


2. Why React?

Before React, developers often used JavaScript to manipulate the DOM directly.

const button = document.querySelector("button");

button.addEventListener("click", () => {
  document.querySelector("h1").textContent = "Hello!";
});
Enter fullscreen mode Exit fullscreen mode

This works, but as an application grows, manually managing DOM changes can become difficult.

React introduces a different approach.

Instead of telling the browser exactly how to update the DOM, we describe the UI based on the current state.

function App() {
  const [name, setName] = React.useState("John");

  return (
    <div>
      <h1>Hello, {name}</h1>

      <button onClick={() => setName("Sarah")}>
        Change Name
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

When the state changes, React updates the relevant UI.

Key advantages

  • Component-based architecture
  • Reusable UI
  • Declarative rendering
  • Efficient updates
  • Strong ecosystem
  • Large developer community
  • Integration with modern frontend tools

React is a library, not a complete framework by itself. Routing, data fetching strategies, and other application-level features may come from additional libraries or frameworks.


3. Declarative vs. Imperative Programming

This is one of the most important concepts in React.

Imperative programming

Imperative programming describes how to perform a task step by step.

const heading = document.createElement("h1");

heading.textContent = "Hello React";

document.body.appendChild(heading);
Enter fullscreen mode Exit fullscreen mode

We explicitly tell the browser:

  1. Create an element.
  2. Set its text.
  3. Add it to the document.

Declarative programming

Declarative programming describes what the UI should look like.

function App() {
  return <h1>Hello React</h1>;
}
Enter fullscreen mode Exit fullscreen mode

React handles the necessary DOM operations.

The difference becomes especially important when data changes.

With React, you generally update the state and let React determine how the UI should change.


4. Creating a React Application

A common way to start a React project is with Vite.

Vite provides a development server and build tooling for modern frontend applications.

Create a project:

npm create vite@latest my-react-app
Enter fullscreen mode Exit fullscreen mode

Choose React and JavaScript or TypeScript.

Then:

cd my-react-app
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

For a React + TypeScript project, choose the TypeScript template.

A typical project structure:

my-react-app/
├── public/
├── src/
│   ├── assets/
│   ├── components/
│   ├── App.tsx
│   ├── main.tsx
│   └── index.css
├── package.json
└── vite.config.ts
Enter fullscreen mode Exit fullscreen mode

The exact structure may vary depending on the template.

What is main.tsx?

This is commonly the entry point of a Vite React application.

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "./App";

createRoot(document.getElementById("root")!).render(
  <StrictMode>
    <App />
  </StrictMode>
);
Enter fullscreen mode Exit fullscreen mode

React renders the App component inside the root DOM element.


5. Understanding JSX

JSX stands for JavaScript XML.

It allows developers to write HTML-like syntax inside JavaScript.

const element = <h1>Hello React</h1>;
Enter fullscreen mode Exit fullscreen mode

JSX is not HTML, even though it looks similar.

It is transformed into JavaScript by the build system.

JSX expressions

You can use JavaScript expressions inside curly braces.

function App() {
  const name = "John";

  return <h1>Hello, {name}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Output:

Hello, John
Enter fullscreen mode Exit fullscreen mode

You can also use calculations:

<p>{10 + 20}</p>
Enter fullscreen mode Exit fullscreen mode

JSX rules

Return a single parent element

Incorrect:

return (
  <h1>Hello</h1>
  <p>Welcome</p>
);
Enter fullscreen mode Exit fullscreen mode

Correct:

return (
  <div>
    <h1>Hello</h1>
    <p>Welcome</p>
  </div>
);
Enter fullscreen mode Exit fullscreen mode

Or use a Fragment:

return (
  <>
    <h1>Hello</h1>
    <p>Welcome</p>
  </>
);
Enter fullscreen mode Exit fullscreen mode

Use className instead of class

<div className="container">
  Hello
</div>
Enter fullscreen mode Exit fullscreen mode

Close elements

<img src="/logo.png" alt="Logo" />

<input />
Enter fullscreen mode Exit fullscreen mode

Use camelCase for many JSX attributes

<button onClick={handleClick}>
  Click me
</button>
Enter fullscreen mode Exit fullscreen mode

6. Components

Components are the building blocks of React applications.

A component is usually a JavaScript function that returns React elements.

function Welcome() {
  return <h1>Welcome to React</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Use it inside another component:

function App() {
  return (
    <div>
      <Welcome />
      <Welcome />
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Welcome component can be reused wherever needed.

Component naming

React component names must start with an uppercase letter.

Correct:

function UserCard() {
  return <h2>User Card</h2>;
}
Enter fullscreen mode Exit fullscreen mode

Incorrect:

function userCard() {
  return <h2>User Card</h2>;
}
Enter fullscreen mode Exit fullscreen mode

React treats lowercase JSX names as HTML elements.

Components should be pure

A component should generally return the same UI for the same inputs.

Avoid changing external values directly during rendering.

function Greeting({ name }) {
  return <h1>Hello, {name}</h1>;
}
Enter fullscreen mode Exit fullscreen mode

React components should not perform side effects during render.


7. Props

Props allow components to receive data from their parent.

Think of props as the inputs of a component.

function UserCard({ name, age }) {
  return (
    <div>
      <h2>{name}</h2>
      <p>Age: {age}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Usage:

function App() {
  return (
    <UserCard
      name="John"
      age={25}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

The parent passes data to the child.

Important: Props are read-only

A child component should not directly modify its props.

Instead, the parent controls the data and can pass callback functions when the child needs to request a change.

Props with TypeScript

interface UserCardProps {
  name: string;
  age: number;
}

function UserCard({ name, age }: UserCardProps) {
  return (
    <div>
      <h2>{name}</h2>
      <p>{age}</p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

TypeScript helps ensure the component receives the correct data.


8. State

State is data that a component remembers between renders.

Unlike ordinary local variables, updating state causes React to render the component again.

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h1>{count}</h1>

      <button onClick={() => setCount(count + 1)}>
        Increment
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Here:

  • count is the current state value.
  • setCount updates the state.
  • 0 is the initial value.

Why not use a normal variable?

function Counter() {
  let count = 0;

  function increment() {
    count++;
    console.log(count);
  }

  return <button onClick={increment}>{count}</button>;
}
Enter fullscreen mode Exit fullscreen mode

This variable does not persist correctly across renders and does not tell React to update the UI.

State solves that problem.

State updates

React state is a snapshot for a particular render.

When updating state based on its previous value, use the functional updater form:

setCount((previousCount) => previousCount + 1);
Enter fullscreen mode Exit fullscreen mode

This is especially useful when updates depend on the previous state.


9. Events

React supports event handling using JSX props.

function App() {
  function handleClick() {
    console.log("Button clicked");
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Notice that we pass the function:

onClick={handleClick}
Enter fullscreen mode Exit fullscreen mode

We don't execute it during render:

// Incorrect
onClick={handleClick()}
Enter fullscreen mode Exit fullscreen mode

The incorrect version calls the function immediately.

Passing arguments

function App() {
  function greet(name) {
    console.log(`Hello, ${name}`);
  }

  return (
    <button onClick={() => greet("John")}>
      Greet
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Event objects

function App() {
  function handleClick(event) {
    console.log(event.target);
  }

  return (
    <button onClick={handleClick}>
      Click me
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

React events provide information about the interaction.

In TypeScript, event handlers can be typed with React event types.


10. Conditional Rendering

React allows you to render different UI depending on a condition.

Using if

function Greeting({ isLoggedIn }) {
  if (isLoggedIn) {
    return <h1>Welcome back!</h1>;
  }

  return <h1>Please log in.</h1>;
}
Enter fullscreen mode Exit fullscreen mode

Using the ternary operator

function Greeting({ isLoggedIn }) {
  return (
    <h1>
      {isLoggedIn ? "Welcome back!" : "Please log in."}
    </h1>
  );
}
Enter fullscreen mode Exit fullscreen mode

Using logical AND

function Notifications({ count }) {
  return (
    <div>
      {count > 0 && <p>You have notifications.</p>}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Be careful when using && with numbers:

{count && <p>Notifications</p>}
Enter fullscreen mode Exit fullscreen mode

If count is 0, React may render 0.

A safer expression:

{count > 0 && <p>Notifications</p>}
Enter fullscreen mode Exit fullscreen mode

11. Rendering Lists and Keys

React can render arrays of data using JavaScript's map() method.

function App() {
  const users = ["John", "Sarah", "Alex"];

  return (
    <ul>
      {users.map((user) => (
        <li key={user}>
          {user}
        </li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

Why do keys matter?

Keys help React identify which items changed, were added, or were removed.

Use stable, unique identifiers whenever possible.

const users = [
  { id: 1, name: "John" },
  { id: 2, name: "Sarah" },
];
Enter fullscreen mode Exit fullscreen mode
{users.map((user) => (
  <UserCard
    key={user.id}
    name={user.name}
  />
))}
Enter fullscreen mode Exit fullscreen mode

Avoid using array indexes as keys when the list can be reordered, inserted into, or deleted from.

Keys are not passed to the component as props.


12. Component Composition

Composition means building complex UI by combining smaller components.

Instead of creating one huge component, break the interface into reusable pieces.

function Header() {
  return <header>My Website</header>;
}

function Sidebar() {
  return <aside>Navigation</aside>;
}

function Content() {
  return <main>Main Content</main>;
}

function App() {
  return (
    <>
      <Header />

      <div className="layout">
        <Sidebar />
        <Content />
      </div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Composition is one of React's most important architectural ideas.

The children prop

function Card({ children }) {
  return (
    <div className="card">
      {children}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Usage:

<Card>
  <h2>Profile</h2>
  <p>User information</p>
</Card>
Enter fullscreen mode Exit fullscreen mode

This makes components flexible and reusable.


13. State Management and Data Flow

React follows a pattern commonly called one-way data flow.

Data usually flows from parent components to child components through props.

Parent
  ↓ Props
Child
  ↓ Props
Grandchild
Enter fullscreen mode Exit fullscreen mode

When a child needs to request a change, the parent can pass a callback.

function Parent() {
  const [message, setMessage] = useState("Hello");

  return (
    <Child
      message={message}
      onChangeMessage={setMessage}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode
function Child({ message, onChangeMessage }) {
  return (
    <div>
      <p>{message}</p>

      <button onClick={() => onChangeMessage("Updated!")}>
        Update
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Lifting state up

When multiple components need the same state, move it to their nearest common parent.

This is called lifting state up.

It helps keep shared data consistent.


14. React Hooks

Hooks allow function components to use React features such as state, context, refs, and effects.

Common hooks include:

Hook Purpose
useState Local component state
useEffect Synchronization with external systems
useRef Persistent values and DOM references
useContext Access shared context
useReducer Complex state logic
useMemo Cache a calculation
useCallback Cache a function definition
useId Generate IDs for accessibility
useTransition Mark updates as non-blocking
useDeferredValue Defer a non-urgent value
useSyncExternalStore Subscribe to external stores

Rules of Hooks

Hooks must follow specific rules:

  1. Call hooks only at the top level.
  2. Don't call hooks inside conditions or loops.
  3. Call hooks from React components or custom hooks.
  4. Don't call hooks from ordinary JavaScript functions.

Incorrect:

if (isLoggedIn) {
  useState(true);
}
Enter fullscreen mode Exit fullscreen mode

Correct:

const [isLoggedIn, setIsLoggedIn] = useState(false);
Enter fullscreen mode Exit fullscreen mode

Hooks must be called in the same order on every render.


15. useState

useState allows a component to store and update state.

import { useState } from "react";

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Updating objects

State should be treated as immutable.

Incorrect:

user.name = "Sarah";
setUser(user);
Enter fullscreen mode Exit fullscreen mode

Correct:

setUser((previousUser) => ({
  ...previousUser,
  name: "Sarah",
}));
Enter fullscreen mode Exit fullscreen mode

Updating arrays

Incorrect:

items.push(newItem);
setItems(items);
Enter fullscreen mode Exit fullscreen mode

Correct:

setItems((previousItems) => [
  ...previousItems,
  newItem,
]);
Enter fullscreen mode Exit fullscreen mode

React relies on state updates to determine when to render again. Creating new objects and arrays helps make changes detectable.


16. useEffect

useEffect allows a component to synchronize with external systems.

Examples include:

  • Browser APIs
  • Subscriptions
  • Timers
  • External widgets
  • Network requests
import { useEffect } from "react";

function Example() {
  useEffect(() => {
    console.log("Effect executed");

    return () => {
      console.log("Cleanup");
    };
  }, []);

  return <p>Hello</p>;
}
Enter fullscreen mode Exit fullscreen mode

The cleanup function runs when the effect needs to clean up, such as before re-running with changed dependencies or when the component unmounts.

Dependency array

useEffect(() => {
  console.log(count);
}, [count]);
Enter fullscreen mode Exit fullscreen mode

This effect synchronizes with the current count value.

Important: Don't use Effects for everything

Effects are not a general-purpose way to calculate values.

For example, avoid:

useEffect(() => {
  setFullName(`${firstName} ${lastName}`);
}, [firstName, lastName]);
Enter fullscreen mode Exit fullscreen mode

Instead, calculate derived data during rendering:

const fullName = `${firstName} ${lastName}`;
Enter fullscreen mode Exit fullscreen mode

If something can be calculated from existing props or state, it usually doesn't need another state variable or effect.


17. useRef

useRef stores a value between renders without causing a re-render when that value changes.

It is commonly used for DOM access.

import { useRef } from "react";

function SearchInput() {
  const inputRef = useRef(null);

  function handleFocus() {
    inputRef.current?.focus();
  }

  return (
    <>
      <input ref={inputRef} />

      <button onClick={handleFocus}>
        Focus
      </button>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Unlike state, changing a ref doesn't automatically update the UI.

Use refs when you need to:

  • Access DOM elements
  • Store timer IDs
  • Keep mutable values between renders
  • Interact with browser APIs

Don't use refs as a replacement for state when the value should affect what the user sees.


18. useContext

Context allows data to be shared across a component tree without passing props through every intermediate component.

It is useful for:

  • Theme
  • Authentication information
  • Locale
  • Shared application settings
import { createContext, useContext } from "react";

const ThemeContext = createContext("light");

function Button() {
  const theme = useContext(ThemeContext);

  return (
    <button className={theme}>
      Click me
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

A provider supplies the value:

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <Button />
    </ThemeContext.Provider>
  );
}
Enter fullscreen mode Exit fullscreen mode

Context can reduce prop drilling, but it doesn't automatically replace every state management solution.


19. useReducer

useReducer is useful when state transitions are more complex.

import { useReducer } from "react";

function reducer(state, action) {
  switch (action.type) {
    case "increment":
      return { count: state.count + 1 };

    case "decrement":
      return { count: state.count - 1 };

    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, {
    count: 0,
  });

  return (
    <div>
      <h1>{state.count}</h1>

      <button onClick={() => dispatch({ type: "increment" })}>
        Increment
      </button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

useReducer separates state transition logic from the component UI.

It can be helpful for:

  • Complex forms
  • Multi-step workflows
  • State with many related actions
  • State transitions that are easier to describe explicitly

20. Custom Hooks

Custom hooks allow developers to reuse stateful logic.

A custom hook is a function whose name starts with use.

import { useState } from "react";

function useToggle(initialValue = false) {
  const [value, setValue] = useState(initialValue);

  function toggle() {
    setValue((previousValue) => !previousValue);
  }

  return {
    value,
    toggle,
  };
}
Enter fullscreen mode Exit fullscreen mode

Usage:

function App() {
  const { value, toggle } = useToggle();

  return (
    <button onClick={toggle}>
      {value ? "ON" : "OFF"}
    </button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Custom hooks help organize logic and avoid duplication.

They share logic, not automatically the same state instance. Each component calling a custom hook normally gets its own hook state.


21. Forms in React

React forms can be controlled or uncontrolled.

Controlled components

A controlled input receives its value from React state.

import { useState } from "react";

function LoginForm() {
  const [email, setEmail] = useState("");

  return (
    <input
      value={email}
      onChange={(event) => setEmail(event.target.value)}
    />
  );
}
Enter fullscreen mode Exit fullscreen mode

React controls the input value.

Uncontrolled components

An uncontrolled input stores its value in the DOM.

function Form() {
  return (
    <form>
      <input name="email" defaultValue="" />
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

Refs or FormData can be used to access the input values.

Controlled components are useful when the UI needs to respond to input changes immediately.

Uncontrolled components can be simpler for certain forms.


22. API Requests

React applications frequently communicate with APIs.

The browser's fetch API can be used to request data.

import { useEffect, useState } from "react";

function Users() {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    async function fetchUsers() {
      try {
        const response = await fetch("/api/users");

        if (!response.ok) {
          throw new Error("Failed to fetch users");
        }

        const data = await response.json();

        setUsers(data);
      } catch (error) {
        setError(error.message);
      } finally {
        setLoading(false);
      }
    }

    fetchUsers();
  }, []);

  if (loading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>{error}</p>;
  }

  return (
    <ul>
      {users.map((user) => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}
Enter fullscreen mode Exit fullscreen mode

A real application should also consider cancellation, stale requests, caching, and error handling.

For more complex server-state management, tools such as TanStack Query can help.


23. React Router

React itself doesn't provide a complete routing system.

React Router is a popular library for handling navigation in React applications.

Install it:

npm install react-router
Enter fullscreen mode Exit fullscreen mode

Example:

import {
  BrowserRouter,
  Routes,
  Route,
  Link,
} from "react-router";

function Home() {
  return <h1>Home Page</h1>;
}

function About() {
  return <h1>About Page</h1>;
}

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}
Enter fullscreen mode Exit fullscreen mode

Routing allows applications to display different pages based on the URL.

Check the current React Router documentation for the setup appropriate to your chosen version and mode.


24. State Management Libraries

As applications grow, managing state across many components can become difficult.

React provides built-in tools such as:

  • useState
  • useReducer
  • useContext

External libraries can help with more complex use cases.

Redux Toolkit

Redux Toolkit is the recommended modern approach for Redux applications.

It provides:

  • Centralized state management
  • Predictable state updates
  • Developer tools
  • Reducers and actions
  • Integration with Redux DevTools

Zustand

Zustand is a lightweight state management library.

It provides a simple store-based API.

TanStack Query

TanStack Query is designed primarily for server state.

It helps with:

  • Fetching data
  • Caching
  • Refetching
  • Loading states
  • Error handling
  • Synchronizing server data

Client state vs. server state

Client state includes:

  • Theme
  • Modal visibility
  • Selected tab
  • Local form values

Server state includes:

  • Users fetched from an API
  • Product lists
  • Orders
  • Remote application data

These two categories often require different approaches.


25. React Performance

React applications can become slow when unnecessary work occurs.

However, optimization should be based on actual performance problems rather than assumptions.

React.memo

React.memo can skip re-rendering a component when its props are unchanged.

import { memo } from "react";

const UserCard = memo(function UserCard({ name }) {
  return <h2>{name}</h2>;
});
Enter fullscreen mode Exit fullscreen mode

useMemo

useMemo caches the result of a calculation.

const filteredUsers = useMemo(() => {
  return users.filter((user) =>
    user.name.includes(search)
  );
}, [users, search]);
Enter fullscreen mode Exit fullscreen mode

useCallback

useCallback caches a function definition.

const handleClick = useCallback(() => {
  console.log("Clicked");
}, []);
Enter fullscreen mode Exit fullscreen mode

These tools are useful in specific situations.

They are not required for every component.

Performance guidelines

  • Keep component responsibilities clear.
  • Avoid unnecessary state.
  • Use stable keys.
  • Avoid expensive calculations during rendering when they become a real bottleneck.
  • Profile before optimizing.
  • Consider code splitting for large applications.

React Compiler can also automate certain memoization optimizations in supported configurations. It doesn't eliminate the need to understand component behavior and performance.


26. React Internals

Understanding React internals helps developers reason about performance and rendering.

At a high level, React performs work in stages.

A simplified model:

State or props change
        ↓
React renders components
        ↓
React determines necessary updates
        ↓
React commits updates
        ↓
Browser displays the UI
Enter fullscreen mode Exit fullscreen mode

Rendering means React calls components and calculates what the UI should look like.

The commit phase applies necessary changes to the host environment, such as the browser DOM.

Not every render results in DOM changes.

React may render a component and determine that the resulting DOM is unchanged.


27. Virtual DOM and Reconciliation

The Virtual DOM is a common term used when discussing React.

Conceptually, React creates an in-memory representation of the UI.

When state changes, React calculates the new UI representation and compares it with the previous one.

This process is commonly described as reconciliation.

React determines which updates are necessary and commits them.

Important clarification

The Virtual DOM is not simply a magical faster version of the DOM.

React's performance depends on many factors:

  • Component structure
  • Rendering behavior
  • Reconciliation
  • Browser DOM operations
  • Application complexity
  • Developer implementation

React's main benefit is often its declarative programming model and predictable UI architecture, not a guarantee that every operation will be faster than manual DOM manipulation.


28. React Fiber

Fiber is React's internal reconciliation architecture.

It was introduced to help React organize rendering work more flexibly.

At a high level, Fiber allows React to:

  • Represent work associated with components
  • Organize rendering tasks
  • Prioritize certain updates
  • Pause and resume work in supported situations
  • Support concurrent rendering capabilities

Fiber is an implementation detail.

Most React developers don't need to interact with Fiber directly.

However, understanding its purpose helps explain why React can support features such as transitions and interruptible rendering.


29. React 19

React 19 introduced several important features.

Some of the most notable include:

  • Actions
  • useActionState
  • useOptimistic
  • use
  • Form Actions
  • useFormStatus
  • Improved support for custom elements
  • Ref as a prop for function components

Actions

Actions help manage asynchronous operations such as form submissions and data mutations.

They can integrate pending states, errors, and optimistic updates.

useActionState

useActionState manages the result and pending status of an action.

import { useActionState } from "react";

async function updateName(previousState, formData) {
  const name = formData.get("name");

  await saveName(name);

  return name;
}

function NameForm() {
  const [name, action, isPending] = useActionState(
    updateName,
    ""
  );

  return (
    <form action={action}>
      <input name="name" />

      <button disabled={isPending}>
        {isPending ? "Saving..." : "Save"}
      </button>

      <p>{name}</p>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

This is a simplified example. A production implementation should also handle validation and errors.

useOptimistic

useOptimistic helps display an optimistic UI update while an asynchronous operation is in progress.

For example, a user may see a comment appear immediately while the server request is still pending.

use

React 19 introduces the use API, which can read resources such as promises or context in supported situations.

When reading a promise, React can suspend rendering until the promise resolves.

These features are particularly relevant to modern React applications and framework integrations.


30. Server Components

React Server Components allow certain components to render on the server.

They can help reduce client-side JavaScript and allow server-side access to resources.

Server Components are different from traditional Client Components.

In frameworks such as Next.js, components may run in different environments.

Server Components

  • Render on the server.
  • Can access server-side resources.
  • Don't use client-only hooks such as useState.
  • Don't directly handle browser interactions.

Client Components

  • Support state and event handlers.
  • Can use browser APIs.
  • Are interactive in the browser.

In Next.js App Router, a file marked with:

"use client";
Enter fullscreen mode Exit fullscreen mode

creates a Client Component boundary.

This directive is specific to frameworks and environments that support React Server Components.

Important: React Server Components are not the same thing as server-side rendering.

Server-side rendering produces HTML on the server.

Server Components define a component model that can execute on the server and integrate with a framework's rendering and data-loading system.


31. Error Boundaries and Suspense

React provides mechanisms for handling rendering errors and asynchronous UI.

Error Boundaries

Error Boundaries catch certain errors during rendering and display fallback UI.

They can prevent a component failure from crashing the entire interface.

Traditionally, Error Boundaries are implemented using class components or framework/library-specific abstractions.

Suspense

Suspense allows React to display fallback UI while a supported child is waiting.

import { Suspense } from "react";

function App() {
  return (
    <Suspense fallback={<p>Loading...</p>}>
      <SomeComponent />
    </Suspense>
  );
}
Enter fullscreen mode Exit fullscreen mode

Suspense works with supported data-loading integrations and lazy-loaded components.

It does not automatically detect every asynchronous operation in an ordinary component.


32. Testing React Applications

Testing helps ensure components behave correctly.

Common testing tools include:

  • Vitest
  • Jest
  • React Testing Library

Example:

import { render, screen } from "@testing-library/react";
import { describe, expect, it } from "vitest";

function Greeting() {
  return <h1>Hello React</h1>;
}

describe("Greeting", () => {
  it("renders the greeting", () => {
    render(<Greeting />);

    expect(
      screen.getByRole("heading", {
        name: "Hello React",
      })
    ).toBeInTheDocument();
  });
});
Enter fullscreen mode Exit fullscreen mode

React Testing Library encourages testing components from the user's perspective.

Instead of testing internal implementation details, focus on what users can see and interact with.


33. React Best Practices

Keep components focused

A component should have a clear responsibility.

If a component becomes too large, consider extracting smaller components.

Don't mutate props or state

Treat them as immutable.

Use new objects and arrays when updating state.

Use meaningful names

function ProductCard() {}
Enter fullscreen mode Exit fullscreen mode

is clearer than:

function Box() {}
Enter fullscreen mode Exit fullscreen mode

Avoid unnecessary effects

Use effects for synchronization with external systems.

Don't use them for every calculation or state update.

Use stable keys

key={user.id}
Enter fullscreen mode Exit fullscreen mode

is usually better than using the array index.

Avoid unnecessary state

If a value can be calculated from existing state or props, consider calculating it instead.

Keep logic reusable

Extract reusable logic into custom hooks or utility functions when appropriate.

Use TypeScript

TypeScript helps document component contracts and catch mistakes during development.

Don't optimize prematurely

Use React's profiling tools to identify actual bottlenecks.

Follow accessibility practices

Use semantic HTML, accessible labels, keyboard support, and appropriate ARIA attributes when necessary.


34. Common React Mistakes

Mistake 1: Calling a component as a normal function

Incorrect:

UserCard();
Enter fullscreen mode Exit fullscreen mode

Correct:

<UserCard />
Enter fullscreen mode Exit fullscreen mode

React should manage component invocation.

Mistake 2: Mutating state

Incorrect:

items.push(newItem);
setItems(items);
Enter fullscreen mode Exit fullscreen mode

Correct:

setItems((previousItems) => [
  ...previousItems,
  newItem,
]);
Enter fullscreen mode Exit fullscreen mode

Mistake 3: Using state for every value

Not every value belongs in state.

If a value doesn't affect rendering and doesn't need to persist between renders, a normal variable or ref may be more appropriate.

Mistake 4: Using useEffect for derived data

Avoid storing values that can be calculated from existing props or state.

Mistake 5: Ignoring keys

Lists should have stable keys.

Mistake 6: Overusing memoization

useMemo, useCallback, and React.memo are optimization tools, not mandatory syntax.

Mistake 7: Treating React as a complete framework

React focuses on UI.

Production applications may need routing, data management, server rendering, authentication, and other infrastructure.


35. React Learning Roadmap

If you're learning React, here's a practical order.

Phase 1: JavaScript Fundamentals

Before React, understand:

  • Variables
  • Functions
  • Arrow functions
  • Arrays
  • Objects
  • Destructuring
  • Spread syntax
  • map
  • filter
  • reduce
  • Modules
  • Promises
  • Async/await
  • Closures
  • Scope
  • DOM fundamentals

Phase 2: React Fundamentals

Learn:

  • JSX
  • Components
  • Props
  • State
  • Events
  • Conditional rendering
  • Lists and keys
  • Component composition
  • State lifting

Phase 3: React Hooks

Learn:

  • useState
  • useEffect
  • useRef
  • useContext
  • useReducer
  • Custom hooks

Phase 4: TypeScript

Learn:

  • Basic types
  • Objects and arrays
  • Interfaces and type aliases
  • Union types
  • Generics
  • Component props
  • Event types
  • Hook typing

Phase 5: Real-World React

Build projects involving:

  • API requests
  • Forms
  • Routing
  • Authentication flows
  • Loading and error states
  • Responsive UI
  • Reusable components

Phase 6: Advanced React

Learn:

  • Performance profiling
  • Memoization
  • Suspense
  • Error boundaries
  • React internals
  • React 19 features
  • Server Components

Phase 7: Ecosystem

Explore:

  • Next.js
  • React Router
  • Redux Toolkit
  • Zustand
  • TanStack Query
  • React Hook Form
  • Testing Library

36. Final Thoughts

React is more than a library for writing UI components.

It introduces a different way of thinking about application development.

Instead of manually changing the DOM, you describe the interface based on the current state.

Instead of building one massive application component, you compose smaller reusable components.

Instead of manually synchronizing every UI update, you let React manage rendering.

The most important concepts to master are:

  • Components
  • Props
  • State
  • JSX
  • Events
  • Hooks
  • Component composition
  • One-way data flow
  • Rendering
  • State management
  • React performance

You don't need to understand every advanced feature before building your first application.

Start with the fundamentals, build projects, make mistakes, and gradually explore the deeper parts of React.

The best way to learn React is not to memorize every hook.

It's to understand why React works the way it does and how to use that knowledge to build real applications.


What's Next?

This guide is the starting point for a deeper React learning journey.

In future articles, we'll explore:

  • React Components, Props, and State in depth
  • React Hooks: The Complete Practical Guide
  • Understanding React Rendering and Reconciliation
  • React Performance Optimization
  • React 19: Actions, useOptimistic, and useActionState
  • Building Production-Ready React Applications
  • React Architecture and Best Practices

If you're learning React, save this guide as a reference and follow along with the series.

Happy coding!

React #JavaScript #TypeScript #FrontendDevelopment #WebDevelopment

Top comments (0)