~/priyank$
> Initializing portfolio...
> Loading components... ✓
> Fetching experience... ✓
> Compiling skills... ✓
> Ready.
~/priyank $
~/blog/tech
post.metadata
title: "What Render and Re-Render Actually Mean in React"
date: 13 Aug 2026
readTime: 18 min read
tags: ["React", "Rendering", "Performance", "Hooks"]
author: "Priyank Deep Singh"

What Render and Re-Render Actually Mean in React

Render is not a full reload, and re-render does not always mean DOM changes. A practical guide to React's render pipeline, commits, React.memo, useMemo, and useCallback.

Priyank Deep Singh
Priyank Deep Singh
18 min read · 13 Aug 2026
What Render and Re-Render Actually Mean in React

React developers say "render" all the time. "This rendered." "That re-rendered." "Why did this render again?" Then someone opens the console, sees the same log printed six times, and suddenly the room has the energy of a production incident wearing a hoodie.

The confusing part is that "render" sounds like "paint the screen again" or "reload the page." In React, it usually means something much smaller and more boring: React called your component function to calculate what the UI should look like now.

This article is the clean mental model: what render means, what re-render means, whether the browser reloads, whether the DOM changes, and how React.memo, useMemo, and useCallback affect the process.

The Map
01What render actually means02Render vs re-render03Does render mean full reload?04Does render mean DOM changed?05What triggers re-renders?06Why children render with parents07React.memo in plain English08useCallback and function identity09useMemo vs memo vs useCallback10When optimization is worth it

01What render actually means

In React, rendering is the process of calling your component function and asking it: "Given the current props, state, and context, what UI should exist?"

Your component returns JSX. JSX is not the DOM. It is a description of what you want. React takes that description, compares it with the previous one, and later decides what DOM changes are actually needed.

Greeting.tsxtsx
function Greeting({ name }) {
console.log('Greeting rendered')
return <h1>Hello, {name}</h1>
}

When React renders Greeting, it calls the function. The console log runs. The function returns <h1>Hello, Priyank</h1>. That is render.

Render, Diff, Commit: Three Different Things
Render
React calls your component function and gets a new UI description.
Diff
React compares the new description with the previous one.
Commit
React applies only the required DOM mutations, then runs effects.
💡 Good to Know
Render means "calculate the next UI description." It does not automatically mean page reload, DOM replacement, repaint, or data refetch.

02Render vs re-render

The first time React calls a component, we usually call it the initial render. When React calls the same component again because something changed, we call that a re-render.

InitialAndRerender.tsxtsx
function Counter() {
const [count, setCount] = useState(0)
console.log('Counter rendered with count:', count)
return (
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
)
}

When the component appears on screen, React renders it with count = 0. When you click the button, state changes. React re-renders it with count = 1. Same function, new input, new UI description.

A re-render is not React saying, "Destroy everything, we start from scratch." It is React saying, "Let me recalculate this part of the UI and see what actually changed." Much calmer. Fewer dramatic sound effects.

Render is a snapshot

Every render has its own snapshot of props and state. Event handlers created during that render close over that snapshot. This is why stale state bugs happen.

Snapshot.tsxtsx
function Counter() {
const [count, setCount] = useState(0)
function logLater() {
setTimeout(() => {
console.log(count)
}, 1000)
}
return (
<>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={logLater}>Log later</button>
</>
)
}

If count was 0 in the render that created logLater, that handler remembers 0. React did not lose your state. Your function kept an old snapshot. JavaScript closures are powerful, but they occasionally enjoy making developers stare at the ceiling.

03Does render mean full reload?

No. A React render does not reload the page. It does not refresh the browser tab. It does not refetch your JavaScript bundle. It does not rerun the whole app from the HTML document like a hard refresh.

Render Is Not A Browser Reload
Browser reload
The document is requested again. Scripts, CSS, and app bootstrapping start over.
React render
React calls component functions inside the already-loaded app.
DOM commit
Only needed DOM operations happen after React compares the output.

If a button click causes a React state update, the browser is still on the same page. JavaScript is still running in the same tab. React is just doing work inside the app.

NoReload.tsxtsx
function ToggleTheme() {
const [dark, setDark] = useState(true)
return (
<button onClick={() => setDark((value) => !value)}>
Current theme: {dark ? 'dark' : 'light'}
</button>
)
}

Clicking this button does not reload the page. It updates state, React re-renders the component, then React changes the text if needed.

⚠️ Watch Out
If your app fully reloads after clicking something, it is usually because of browser behavior: a normal form submit, a regular anchor navigation, location assignment, or server navigation. That is different from a React re-render.

04Does render mean DOM changed?

Also no. Render means React calculated a new UI description. The DOM changes only during the commit phase, and only if React finds a real difference.

SameValue.tsxtsx
function SameValue() {
const [count, setCount] = useState(0)
console.log('rendered')
return (
<button onClick={() => setCount(0)}>
Count: {count}
</button>
)
}

If count is already 0 and you set it to 0 again, React can bail out because the state value did not change. No useful DOM update is needed. React is not paid by the DOM mutation.

Render can happen without visible change

A parent can re-render, a child can be called again, React can compare the output, and the DOM can remain exactly the same. That is common. It is not automatically a bug.

NoVisibleChange.tsxtsx
function Parent() {
const [count, setCount] = useState(0)
return (
<section>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<Child />
</section>
)
}
function Child() {
console.log('Child rendered')
return <p>I always say the same thing.</p>
}

Child may render when Parent renders, but its DOM output is identical. React can keep the existing paragraph in place. The console log changed; the screen did not.

Pro Tip
Console logs show function calls. They do not prove DOM changes. For DOM work and render cost, use React DevTools Profiler.

05What triggers re-renders?

React re-renders when one of the inputs to a component may have changed. Most re-renders come from these sources:

  1. State changed. A component calls setState or a hook setter.
  2. Parent rendered. React normally evaluates children when their parent renders.
  3. Context value changed. Components reading that context can render again.
  4. External store changed. Redux, Zustand, query libraries, form stores, and subscriptions can notify React.
  5. Key changed. React may remount instead of re-rendering, which resets local state.

Notice what is not on the list: "the DOM felt like changing." React starts from data changes and component relationships. The DOM is the result, not the trigger.

06Why children render with parents

This is probably the most common surprise: if a parent renders, children often render too, even when their props look unchanged.

ParentChildRender.tsxtsx
function Parent() {
const [count, setCount] = useState(0)
console.log('Parent rendered')
return (
<>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<Child label="Static label" />
</>
)
}
function Child({ label }) {
console.log('Child rendered')
return <p>{label}</p>
}

Click the button and both logs can run. The child's prop is the same, but the child is part of the parent's output. React asks the child for its output again.

This is normal and usually cheap. React components are just functions. Calling a small function again is not a crisis. Performance problems start when the child is expensive: a huge chart, a massive list, expensive formatting, heavy calculations, or lots of nested work.

🎯 Think of it Like This
A parent render is like checking a section of a document again. React may read the paragraphs under that section, but it does not rewrite every paragraph unless the text actually changed.

07React.memo in plain English

React.memo tells React: "If this component receives the same props as last time, you can reuse the previous result and skip calling the component function."

MemoBasic.tsxtsx
const Child = React.memo(function Child({ label }) {
console.log('Child rendered')
return <p>{label}</p>
})
function Parent() {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>
Count: {count}
</button>
<Child label="Static label" />
</>
)
}

Now, when Parent renders because count changed, Child can skip rendering because its label prop is still the same string.

memo compares props shallowly

This detail matters. React.memo does a shallow comparison. Primitive values are easy. Objects, arrays, and functions are compared by reference.

MemoBrokenByNewObject.tsxtsx
const UserCard = React.memo(function UserCard({ user }) {
console.log('UserCard rendered')
return <p>{user.name}</p>
})
function Page() {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>Count {count}</button>
<UserCard user={{ id: 1, name: 'Priyank' }} />
</>
)
}

This still re-renders UserCard. Why? Because { id: 1, name: 'Priyank' } creates a new object every time Page renders. The object looks the same, but the reference is different. React is not reading your mind. Honestly, good.

MemoWithStableObject.tsxtsx
const user = { id: 1, name: 'Priyank' }
function Page() {
const [count, setCount] = useState(0)
return (
<>
<button onClick={() => setCount(count + 1)}>Count {count}</button>
<UserCard user={user} />
</>
)
}

Now the user reference is stable, so React.memo has a chance to do its job.

⚠️ Watch Out
React.memo is not a force field. It only helps when props are stable and the skipped render is worth skipping.

08useCallback and function identity

Every time a component renders, any function declared inside it is created again. That is normal JavaScript. Usually it does not matter. It matters when you pass that function to a memoized child.

MemoBrokenByCallback.tsxtsx
const SaveButton = React.memo(function SaveButton({ onSave }) {
console.log('SaveButton rendered')
return <button onClick={onSave}>Save</button>
})
function Editor() {
const [text, setText] = useState('')
const [theme, setTheme] = useState('dark')
function handleSave() {
saveDocument(text)
}
return (
<>
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle theme
</button>
<textarea value={text} onChange={(event) => setText(event.target.value)} />
<SaveButton onSave={handleSave} />
</>
)
}

Toggle the theme. Editor renders. handleSave is recreated. SaveButton receives a new onSave prop, so React.memo cannot skip it.

useCallback keeps a function reference stable

UseCallback.tsxtsx
const SaveButton = React.memo(function SaveButton({ onSave }) {
console.log('SaveButton rendered')
return <button onClick={onSave}>Save</button>
})
function Editor() {
const [text, setText] = useState('')
const [theme, setTheme] = useState('dark')
const handleSave = useCallback(() => {
saveDocument(text)
}, [text])
return (
<>
<button onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}>
Toggle theme
</button>
<textarea value={text} onChange={(event) => setText(event.target.value)} />
<SaveButton onSave={handleSave} />
</>
)
}

Now handleSave keeps the same reference when theme changes. It only changes when text changes, because text is in the dependency array.

Important: useCallback does not stop the parent render

useCallback does not prevent Editor from rendering. It only helps keep a function prop stable so memoized children can skip rendering.

💡 Good to Know
useCallback(fn, deps) is basically useMemo(() => fn, deps). It memoizes the function reference, not the function's result.

09useMemo vs memo vs useCallback

These three are related, but they do different jobs. Mixing them up is easy because their names sound like they were chosen during a naming meeting that ran out of snacks.

React.memo
Memoizes a component render result based on props.
useMemo
Memoizes a calculated value between renders.
useCallback
Memoizes a function reference between renders.
MemoFamily.tsxtsx
const ExpensiveChart = React.memo(function ExpensiveChart({
data,
onPointClick,
}) {
return <Chart data={data} onPointClick={onPointClick} />
})
function Dashboard({ rows }) {
const [selectedId, setSelectedId] = useState(null)
const chartData = useMemo(() => {
return rows.map((row) => ({
label: row.name,
value: row.revenue,
}))
}, [rows])
const handlePointClick = useCallback((id) => {
setSelectedId(id)
}, [])
return (
<ExpensiveChart
data={chartData}
onPointClick={handlePointClick}
/>
)
}

In this example, useMemo stabilizes the data array, useCallback stabilizes the click handler, and React.memo lets the chart skip rendering if those props did not change.

The dependency array is the truth contract

If a callback or memoized value uses a variable from the component scope, that variable usually belongs in the dependency array. Leaving dependencies out can create stale values. Adding too many unstable dependencies can make memoization useless. This is where React asks you to be honest. Annoying, but fair.

StaleCallback.tsxtsx
function Search({ query }) {
const submit = useCallback(() => {
// BUG: query may be stale if it is missing from deps
searchApi(query)
}, [])
return <button onClick={submit}>Search</button>
}
CorrectCallback.tsxtsx
function Search({ query }) {
const submit = useCallback(() => {
searchApi(query)
}, [query])
return <button onClick={submit}>Search</button>
}

10When optimization is worth it

Not every re-render deserves a fix. Many re-renders are cheap, harmless, and simpler than the memoization code you might add to avoid them. The goal is not to make React render as little as mathematically possible. The goal is to keep rendering work predictable and fast.

optimization-checklist.md
  1. Measure first. Use React DevTools Profiler before sprinkling memoization everywhere.
  2. Memoize expensive children. Charts, large lists, editors, maps, and heavy visual components are good candidates.
  3. Stabilize props for memoized children. Use useMemo for object/array props and useCallback for function props.
  4. Move state down. If only a small component needs state, do not put it high in the tree and re-render half the page.
  5. Split context. Do not put rapidly changing values and rarely changing values in one giant provider.
  6. Prefer clarity until performance asks for more. Memoization adds mental overhead. Spend it where users actually feel the cost.

A tiny decision tree

If a component re-renders, ask: did the page reload? If no, relax. Did the DOM visibly change? If yes, maybe that was the point. Is the render expensive or frequent enough to hurt interaction? If no, keep the code simple. If yes, profile, then optimize the specific path.

MentalModel.tsts
// Render:
component(props, state, context) -> React elements
// Re-render:
same component called again with new inputs
// Commit:
React applies the actual DOM changes
// memo:
skip component render if props are the same
// useCallback:
keep a function prop stable between renders

Once that model clicks, re-renders stop feeling spooky. They become normal bookkeeping. React checks what the UI should be, compares it with what the UI already is, and changes only what needs changing. The trick is not to fear renders. The trick is to make expensive renders rare, intentional, and measurable.

💡 Good to Know
Short version: render is calculation, re-render is recalculation, commit is DOM mutation, reload is a browser-level event, React.memo skips component calls when props are stable, and useCallback helps make function props stable.
Keep Reading
all posts
Why React Re-Renders
22 min read · React / Performance
Code Splitting & Lazy Loading in React
18 min read · React / Performance
Next.js Rendering & Bundle Simulator
16 min read · Next.js / Performance
share.sh
$ echo "Share this article"
Priyank Deep Singh
Priyank Deep Singh

Senior web engineer who loves building fast, accessible, and beautiful web experiences. Writing about React, Next.js, and everything in between.