React provides several built-in components that solve common problems without requiring you to create your own component implementation.
Some of these components are used every day, while others are more specialized.
The built-in components covered in this chapter are:
<Fragment>
<StrictMode>
<Suspense>
<Profiler>
<Activity>
<ViewTransition>
Each one has a different purpose.
For example:
<Fragment>groups elements without adding an extra DOM element.<StrictMode>helps identify problems during development.<Suspense>displays fallback UI while supported content is waiting.<Profiler>measures React rendering performance.<Activity>hides and restores UI while preserving its state.<ViewTransition>helps animate UI changes using the browser View Transition API.
Understanding when to use each component helps you write cleaner and more capable React applications.
<Fragment>
<Fragment> lets you group multiple React elements without adding an extra element to the browser DOM.
For example:
function UserProfile() {
return (
<Fragment>
<h1>Alex</h1>
<p>Web Developer</p>
</Fragment>
);
}
The resulting DOM does not contain an additional wrapper created by the Fragment.
You can also use the shorthand syntax:
function UserProfile() {
return (
<>
<h1>Alex</h1>
<p>Web Developer</p>
</>
);
}
The shorthand:
<>
...
</>
is equivalent to:
<Fragment>
...
</Fragment>
for normal Fragment usage.
Why Do We Need Fragments?
A React component can return multiple elements as long as they are grouped into a single React node.
Without a Fragment, developers often add an unnecessary wrapper:
return (
<div>
<h1>Products</h1>
<p>Browse our products.</p>
</div>
);
The <div> becomes part of the DOM.
Sometimes that is perfectly fine.
But sometimes the extra wrapper can interfere with:
- CSS layout
- Flexbox
- Grid
- semantic HTML
- table structure
- styling
- accessibility
A Fragment solves this without introducing another DOM element:
return (
<>
<h1>Products</h1>
<p>Browse our products.</p>
</>
);
Fragment Inside Lists
Fragments become particularly useful when rendering multiple elements from a list.
For example:
const users = [
{
id: 1,
name: "Alex",
role: "Developer"
},
{
id: 2,
name: "Sam",
role: "Designer"
}
];
function UserList() {
return (
<dl>
{users.map(user => (
<Fragment key={user.id}>
<dt>{user.name}</dt>
<dd>{user.role}</dd>
</Fragment>
))}
</dl>
);
}
Here, each user produces two sibling elements:
<dt>Alex</dt>
<dd>Developer</dd>
<dt>Sam</dt>
<dd>Designer</dd>
A Fragment lets them stay siblings without adding a wrapper.
Why Can’t We Use the Shorthand Here?
This will not work:
<>
<dt>{user.name}</dt>
<dd>{user.role}</dd>
</>
if you need to provide a key.
The shorthand Fragment syntax does not support a key.
Instead, use the explicit syntax:
<Fragment key={user.id}>
<dt>{user.name}</dt>
<dd>{user.role}</dd>
</Fragment>
React’s documentation specifically notes that an explicit <Fragment> is required when you need a Fragment key.
Fragments and Refs
Modern React also supports refs on explicit Fragments.
For example:
import { Fragment, useRef } from "react";
function Example() {
const fragmentRef = useRef(null);
return (
<Fragment ref={fragmentRef}>
<button>One</button>
<button>Two</button>
</Fragment>
);
}
This is an advanced feature that can be useful when you need to interact with the DOM nodes represented by a Fragment without introducing a wrapper element.
For most everyday React code, however, the shorthand:
<>
...
</>
is all you need.
<StrictMode>
<StrictMode> is a development tool that helps identify common problems in React applications.
You can enable it like this:
import { StrictMode } from "react";
root.render(
<StrictMode>
<App />
</StrictMode>
);
Strict Mode does not produce a visible DOM element.
Instead, it enables additional development checks for the component tree inside it.
Current React documentation describes checks including extra development rendering, extra Effect setup and cleanup, extra callback-ref setup and cleanup, and checks for deprecated APIs.
Why Use Strict Mode?
React components are expected to behave predictably.
For example:
function Greeting({ name }) {
return <h1>Hello, {name}</h1>;
}
This component is pure.
Given the same input:
name = "Alex"
it produces the same UI.
Strict Mode helps reveal code that does not follow these expectations.
Strict Mode and Extra Rendering
During development, Strict Mode may call certain functions more than once to expose accidental side effects.
For example:
function Product({ product }) {
console.log("Rendering product");
return <h2>{product.name}</h2>;
}
You may see the message more than once in development.
This is intentional.
You should not write application logic that depends on a component rendering exactly once.
Strict Mode and Effects
Strict Mode can also run an additional Effect setup and cleanup cycle in development.
Consider:
useEffect(() => {
const connection = createConnection();
connection.connect();
return () => {
connection.disconnect();
};
}, []);
The important part is the cleanup:
return () => {
connection.disconnect();
};
The Effect can safely be started and stopped.
This makes the component more resilient.
Strict Mode Is Development-Only
The extra Strict Mode checks are development behavior.
They do not mean users will experience the same extra checks in your production build.
For new applications, React recommends using Strict Mode for the application during development.
<Suspense>
<Suspense> lets you display fallback UI while supported child content is waiting to become ready.
Basic syntax:
<Suspense fallback={<Loading />}>
<Profile />
</Suspense>
If Profile suspends, React can display:
<Loading />
until the content is ready.
A Simple Suspense Example
import { Suspense } from "react";
function App() {
return (
<Suspense fallback={<p>Loading...</p>}>
<Profile />
</Suspense>
);
}
The important part is that Suspense does not simply detect any asynchronous operation.
The child needs to use a React-supported mechanism that can suspend.
What Does “Suspending” Mean?
Suspending means that a component’s rendering cannot currently finish because it is waiting for something React knows how to coordinate.
Modern React supports Suspense with mechanisms such as:
- lazy-loaded components
- reading Promises with
use - Suspense-enabled frameworks
- streamed server-rendered content
- certain resource loading scenarios
React’s current documentation specifically notes that Suspense does not automatically detect data fetching performed inside an ordinary Effect or event handler.
Suspense with lazy
One common use is lazy-loading component code.
import { lazy, Suspense } from "react";
const Dashboard = lazy(
() => import("./Dashboard.jsx")
);
function App() {
return (
<Suspense fallback={<p>Loading dashboard...</p>}>
<Dashboard />
</Suspense>
);
}
When the Dashboard code has not loaded yet, the fallback can be displayed.
Once the component is ready, React displays the Dashboard.
Suspense Boundaries
A Suspense boundary is the area wrapped by:
<Suspense fallback={...}>
...
</Suspense>
You can create multiple boundaries:
<>
<Suspense fallback={<HeaderSkeleton />}>
<Header />
</Suspense>
<Suspense fallback={<ProductsSkeleton />}>
<ProductList />
</Suspense>
</>
This allows different parts of an application to have different loading experiences.
Instead of showing one large loading screen, the application can progressively reveal independent parts.
Choosing a Good Fallback
A fallback should represent what the user is waiting for.
For example:
<Suspense fallback={<ProductSkeleton />}>
<ProductList />
</Suspense>
is often better than:
<Suspense fallback={<p>Loading...</p>}>
<ProductList />
</Suspense>
when the application can provide a useful skeleton.
The goal is not merely to show a loading message.
The fallback should provide a good transition between unavailable and available content.
Suspense and Server Rendering
Suspense also plays an important role in modern server rendering.
Suspense boundaries can participate in streaming server rendering and selective hydration, allowing parts of a server-rendered application to become interactive independently.
This becomes particularly important in larger applications and React frameworks.
<Profiler>
<Profiler> is a built-in component used to measure rendering performance programmatically.
It is useful when you want to understand how much time a part of your React application spends rendering.
Basic usage:
import { Profiler } from "react";
function App() {
return (
<Profiler
id="Dashboard"
onRender={handleRender}
>
<Dashboard />
</Profiler>
);
}
The Profiler requires:
- an
id - an
onRendercallback
React calls the callback when the profiled tree commits an update.
Creating an onRender Callback
function handleRender(
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
) {
console.log({
id,
phase,
actualDuration,
baseDuration,
startTime,
commitTime
});
}
This gives you information about the rendering work.
Understanding phase
The phase tells you what kind of render occurred.
You may see values such as:
"mount"
"update"
A mount occurs when the component tree is initially added.
An update occurs when it renders again.
Understanding actualDuration
actualDuration estimates the time spent rendering the profiled tree for that update.
For example:
actualDuration: 12.5
means React spent approximately 12.5 milliseconds rendering that profiled tree for that update.
This can help identify expensive rendering work.
Understanding baseDuration
baseDuration estimates the cost of rendering the tree without optimizations such as memoization.
Comparing it with actualDuration can help you understand whether optimizations are reducing rendering work.
Profiling Different Areas
You can profile different parts of an application:
function App() {
return (
<>
<Profiler
id="Sidebar"
onRender={handleRender}
>
<Sidebar />
</Profiler>
<Profiler
id="Content"
onRender={handleRender}
>
<Content />
</Profiler>
</>
);
}
You can also nest Profilers when measuring more specific areas.
Profiler vs React DevTools
You do not always need to use <Profiler>.
React Developer Tools includes an interactive Profiler that can help investigate rendering performance.
The <Profiler> component is useful when you specifically need programmatic measurements or want to integrate measurements into your own performance analysis.
Profiler Performance Considerations
Profiling adds overhead.
React’s documentation notes that profiling is disabled in the production build by default, and production profiling requires a special profiling-enabled build.
Therefore, do not wrap your entire application in <Profiler> permanently without a reason.
Use it when you are investigating performance.
<Activity>
<Activity> is a newer React component designed for UI that needs to be hidden and later restored while preserving its internal state.
Basic syntax:
<Activity mode={isVisible ? "visible" : "hidden"}>
<Sidebar />
</Activity>
The mode can be:
"visible"
"hidden"
If no mode is provided, it defaults to "visible".
Why Do We Need Activity?
Consider a tab interface:
{activeTab === "home" && <Home />}
{activeTab === "settings" && <Settings />}
When the user switches from Home to Settings, the Home component is removed from the tree.
If Home contains state:
const [search, setSearch] = useState("");
that state is lost when the component is unmounted.
When the user returns to Home, the component starts again.
Sometimes that is exactly what you want.
But sometimes you want the UI to disappear while keeping its state available.
That is where Activity can help.
Activity Preserves State
Consider:
function App() {
const [activeTab, setActiveTab] = useState("home");
return (
<>
<button onClick={() => setActiveTab("home")}>
Home
</button>
<button onClick={() => setActiveTab("settings")}>
Settings
</button>
<Activity mode={activeTab === "home" ? "visible" : "hidden"}>
<Home />
</Activity>
<Activity mode={activeTab === "settings" ? "visible" : "hidden"}>
<Settings />
</Activity>
</>
);
}
When the Home Activity becomes hidden, React can preserve its internal state.
When it becomes visible again, the previous state can be restored.
Activity vs Conditional Rendering
Compare:
{activeTab === "home" && <Home />}
with:
<Activity mode={activeTab === "home" ? "visible" : "hidden"}>
<Home />
</Activity>
The first approach mounts and unmounts the component.
The second approach allows React to hide the UI while preserving its state.
This makes Activity useful for UI that users are likely to return to.
What Happens to Effects?
Activity does something particularly important with Effects.
When an Activity becomes hidden, React cleans up the Effects inside it.
When it becomes visible again, React recreates those Effects.
Conceptually:
Visible
↓
Effects active
↓
Hidden
↓
Effects cleaned up
↓
Visible again
↓
Effects recreated
The component’s state can remain preserved while its external side effects are paused.
Activity Is Not the Same as CSS display: none
You might think:
<div style={{ display: "none" }}>
<Sidebar />
</div>
does the same thing.
It does not.
With a normal CSS-based approach, the component remains mounted and its Effects continue to exist.
Activity provides React-aware behavior.
When hidden, React visually hides the content and cleans up its Effects while retaining the component’s state for restoration.
Activity Use Cases
Activity can be useful for:
- tab interfaces
- sidebars
- navigation panels
- editors
- dashboards
- recently visited views
- UI that users frequently switch between
For example, imagine a form:
Personal Information
Email
Address
Phone
The user fills out several fields and then switches to another tab.
If the form is hidden with Activity, the entered state can remain available when the user returns.
Activity and Expensive UI
Activity can also be useful when you want to keep UI state but avoid keeping all external work active while the UI is hidden.
This can make it a useful tool for building richer interfaces with multiple views.
However, you should still consider whether a hidden component needs to remain in memory.
Do not use Activity everywhere simply because it is available.
<ViewTransition>
<ViewTransition> is a newer React component for coordinating UI animations with React Transitions and Suspense.
It allows React to participate in the browser’s View Transition capabilities.
Basic usage:
import { ViewTransition } from "react";
function App() {
return (
<ViewTransition>
<Page />
</ViewTransition>
);
}
React’s current documentation describes <ViewTransition> as a component that can animate a component tree with Transitions and Suspense.
Why View Transitions Matter
Traditional UI updates can sometimes feel abrupt.
For example:
Page A
↓
Page B
The old page disappears and the new page appears.
A view transition can help create a smoother visual transition between these states.
Instead of simply changing the UI:
Old UI → New UI
the browser can visually animate the transition.
Basic ViewTransition Example
import { ViewTransition } from "react";
function Profile() {
return (
<ViewTransition>
<div className="profile">
<h2>Alex</h2>
<p>Web Developer</p>
</div>
</ViewTransition>
);
}
The exact animation is controlled through the browser’s View Transition mechanisms and React’s integration with them.
ViewTransition with State Updates
View transitions become especially useful when combined with React transitions.
For example:
import {
ViewTransition,
startTransition,
useState
} from "react";
function App() {
const [showDetails, setShowDetails] = useState(false);
function handleClick() {
startTransition(() => {
setShowDetails(value => !value);
});
}
return (
<>
<button onClick={handleClick}>
Toggle Details
</button>
<ViewTransition>
{showDetails ? (
<div>
<h2>Details</h2>
<p>More information...</p>
</div>
) : (
<div>
<h2>Summary</h2>
<p>Short overview...</p>
</div>
)}
</ViewTransition>
</>
);
}
The transition tells React that the update can participate in transition behavior, while <ViewTransition> provides the animation boundary.
ViewTransition and Suspense
<ViewTransition> can also work with <Suspense>.
For example:
<ViewTransition>
<Suspense fallback={<ProfileSkeleton />}>
<Profile />
</Suspense>
</ViewTransition>
React can coordinate the transition with the Suspense content.
This becomes especially useful when content is loaded asynchronously.
React’s documentation also describes how the placement of <ViewTransition> relative to a Suspense boundary changes whether fallback/content are treated as an update or as separate enter/exit animations.
ViewTransition and Images
Modern View Transitions can coordinate with resources such as images and fonts.
When React uses <ViewTransition> for a Suspense reveal, React can wait for relevant resources to be ready before starting the animation, helping avoid visual flashes during the transition.
ViewTransition and Lists
View transitions do not replace keys.
Consider:
{items.map(item => (
<ViewTransition key={item.id}>
<Card item={item} />
</ViewTransition>
))}
Stable keys remain important for React identity.
Keys tell React which item is which.
View transitions control how visual changes can be animated.
These are separate responsibilities.
ViewTransition and Browser Support
View transitions depend on browser capabilities and the React version/environment supporting the feature.
Therefore, treat <ViewTransition> as a modern enhancement rather than assuming every browser has identical support.
For production applications, test the desired animation behavior across the browsers you support and provide a sensible experience when the transition is unavailable.
Comparing the Built-in Components
These components solve very different problems.
| Component | Main Purpose |
|---|---|
<Fragment> | Group elements without an extra DOM wrapper |
<StrictMode> | Find bugs during development |
<Suspense> | Show fallback while supported content is waiting |
<Profiler> | Measure React rendering performance |
<Activity> | Hide UI while preserving its state |
<ViewTransition> | Animate UI changes with React transitions and Suspense |
Knowing this distinction prevents you from using one component for a problem it was not designed to solve.
<Fragment> vs Wrapper Elements
Use a Fragment when you need grouping but do not need a DOM element.
<>
<Header />
<Content />
</>
Use a real element when you need:
- styling
- layout
- semantic meaning
- attributes
- event handling
- a DOM reference
For example:
<section className="content">
<Header />
<Content />
</section>
A Fragment cannot replace an element when the element itself has a purpose.
<StrictMode> vs <Profiler>
These two components can sometimes be confused because both are related to development.
But they have completely different purposes.
<StrictMode>:
<StrictMode>
<App />
</StrictMode>
helps find bugs.
<Profiler>:
<Profiler id="App" onRender={handleRender}>
<App />
</Profiler>
measures rendering performance.
Think:
StrictMode → Find problems
Profiler → Measure performance
<Suspense> vs <Activity>
These components can both affect what users see while content is not currently visible, but they solve different problems.
Suspense
Suspense is primarily about content that is not ready yet.
<Suspense fallback={<Loading />}>
<Dashboard />
</Suspense>
The fallback communicates:
“The requested content is not ready yet.”
Activity
Activity is primarily about content that is temporarily hidden.
<Activity mode="hidden">
<Dashboard />
</Activity>
The idea is:
“This UI is not currently visible, but we may want to restore it with its state later.”
This distinction is extremely useful.
<Activity> vs Conditional Rendering
Conditional rendering:
{showDashboard && <Dashboard />}
can remove the component from the tree.
Activity:
<Activity mode={showDashboard ? "visible" : "hidden"}>
<Dashboard />
</Activity>
allows React to preserve its state while hiding it.
Use conditional rendering when you actually want the component to be removed.
Use Activity when preserving the UI’s state across hiding and showing is valuable.
<ViewTransition> vs CSS Transitions
CSS transitions are still extremely useful.
For simple property changes:
.button {
transition: transform 200ms ease;
}
CSS is often the simplest solution.
<ViewTransition> becomes useful when you want React to coordinate transitions across UI changes, particularly when elements enter, exit, move, or are revealed through Suspense.
Think of them as complementary tools rather than competing technologies.
Choosing the Right Built-in Component
When you encounter a requirement, ask what problem you are actually solving.
Need to return multiple elements?
Use:
<>
...
</>
Need development checks?
Use:
<StrictMode>
...
</StrictMode>
Need a fallback while supported content is waiting?
Use:
<Suspense fallback={<Loading />}>
...
</Suspense>
Need to measure rendering performance?
Use:
<Profiler id="Section" onRender={handleRender}>
...
</Profiler>
Need to hide UI while preserving its state?
Use:
<Activity mode="hidden">
...
</Activity>
Need animated UI transitions coordinated with React?
Use:
<ViewTransition>
...
</ViewTransition>
Common Mistakes
Adding a <div> Everywhere
Not every group of elements needs a wrapper.
If the wrapper has no semantic or layout purpose, a Fragment may be cleaner.
Using Strict Mode as a Production Feature
Strict Mode is primarily a development tool.
Do not build application behavior around its development-only checks.
Expecting Suspense to Detect Every Fetch
This is an important distinction.
This:
useEffect(() => {
fetch("/api/products");
}, []);
does not automatically activate Suspense.
Suspense needs a Suspense-enabled data mechanism or another supported source of suspension.
Using Profiler Everywhere
Profiling adds overhead.
Use it when measuring performance rather than wrapping every component without a reason.
Using Activity Everywhere
Activity preserves hidden UI state, but keeping large UI trees around can consume memory.
Use it where preserving state and background UI behavior provides a real benefit.
Assuming ViewTransition Replaces Keys
View transitions do not replace React’s identity system.
Continue using stable keys for dynamic lists.
Confusing Hidden with Unmounted
With Activity, hidden children remain represented by React while their Effects are cleaned up.
This is different from simply unmounting the component and losing its state.
Built-in Components Cheat Sheet
<Fragment>
↓
Group JSX
↓
No extra DOM wrapper
<StrictMode>
↓
Development checks
↓
Find bugs earlier
<Suspense>
↓
Wait for supported content
↓
Show fallback
<Profiler>
↓
Measure rendering
↓
Analyze performance
<Activity>
↓
Hide UI
↓
Preserve state
↓
Clean up Effects while hidden
<ViewTransition>
↓
Coordinate visual transitions
↓
React + browser View Transitions
Key Takeaways
React provides several built-in components that solve different problems.
<Fragment> lets you group multiple elements without adding an extra DOM node. The shorthand <>...</> is the most common form. When you need a key, use the explicit <Fragment> syntax.
<StrictMode> enables additional development checks that help reveal problems such as impure rendering, missing Effect cleanup, missing callback-ref cleanup, and deprecated API usage.
<Suspense> lets React display fallback content while supported child content is waiting. It is commonly used with lazy-loaded components and modern Suspense-enabled data-loading patterns. It does not automatically detect ordinary data fetching inside Effects.
<Profiler> provides programmatic measurements of React rendering performance. It can help you investigate expensive rendering work, but it should be used intentionally because profiling adds overhead.
<Activity> provides a way to hide UI while preserving its internal state. When hidden, its Effects are cleaned up, and when it becomes visible again, React restores the UI and recreates its Effects.
<ViewTransition> is a modern React component for coordinating UI animations with React Transitions and Suspense. It can make changes between UI states feel smoother and can coordinate with resources such as images and fonts during supported transitions.
The most important thing is not to memorize all six components.
Instead, understand the problem each one solves:
Need grouping?
→ Fragment
Need development checks?
→ StrictMode
Need loading boundaries?
→ Suspense
Need performance measurements?
→ Profiler
Need hidden UI with preserved state?
→ Activity
Need animated UI transitions?
→ ViewTransition
Once these distinctions are clear, React’s built-in components become practical tools rather than APIs that you simply memorize.