⚡ Why useRef Can Outperform useState in React Native

 Most React Native developers reach for useState by default.

Need to store something?
๐Ÿ‘‰ useState

Need to update something?
๐Ÿ‘‰ useState

But here’s the truth ๐Ÿ‘‡
Not everything belongs in state.

And in performance-sensitive React Native apps,
useRef can be significantly faster than useState.

Let’s understand why — without jargon.


๐Ÿง  First, Understand What Triggers a Re-render

In React (and React Native):

Any setState call schedules a re-render

That’s not a bug.
That’s the core design.

const [count, setCount] = useState(0); setCount(count + 1);

This causes:

  1. React schedules a re-render

  2. Component function runs again

  3. Hooks are re-evaluated

  4. Virtual tree is re-created

  5. Diffing happens

  6. Updates are committed to native views

Even if nothing visible changes, the work still happens.


๐Ÿงต What Threads Are Involved?

Let’s simplify React Native’s threading model:

https://miro.medium.com/v2/resize%3Afit%3A1200/1%2AHZldb5hV93zkdNXFLfnL_w.png     https://reactnative.dev/assets/images/render-pipeline-3-db2c1aa465ae7d76346b879966938b3d.png

https://engineering.rently.com/wp-content/uploads/2022/09/React1-1024x495.jpg

๐ŸŸจ JS Thread

  • Runs your component code

  • Executes hooks (useState, useRef)

  • Handles rendering logic

  • Schedules updates

๐ŸŸฆ Native / UI Thread

  • Draws views

  • Handles gestures

  • Talks directly to iOS / Android OS

๐Ÿ“Œ If JS thread is busy → UI feels laggy


๐Ÿ”„ What Happens When You Use useState

Example:

const [timer, setTimer] = useState(0);

When you update it:

setTimer(prev => prev + 1);

Internally, React does this:

  1. JS thread receives setState

  2. Update is queued

  3. Component is marked “dirty”

  4. Component function re-runs

  5. New virtual tree is created

  6. Diffing compares old vs new tree

  7. Commit phase updates native views (if needed)

⚠️ Even if the value is not used in UIsteps 1–6 still happen.

This is why unnecessary state updates are expensive.


๐ŸงŠ What Makes useRef Different?

Now compare with:

const timerRef = useRef(0);

Update it like this:

timerRef.current += 1;

What happens internally?

๐Ÿ‘‰ Almost nothing

  • No re-render

  • No virtual tree recreation

  • No diffing

  • No commit phase

  • JS thread continues smoothly

useRef is just a mutable container that React ignores for rendering.


๐Ÿง  Simple Mental Model

Think of it like this:

HookReact Reacts?Re-render?
useStateYes✅ Always
useRefNo❌ Never

๐ŸŽฏ Why This Matters More in React Native

In web React:

  • Browser handles layout

  • Re-render cost is often lower

In React Native:

  • Rendering crosses JS ↔ Native boundary

  • JS thread performance is critical

  • Animations, gestures, lists depend on it


https://miro.medium.com/1%2A7LOihJdZ1RRXn1RxlY-RPg.jpeg


https://miro.medium.com/v2/resize%3Afit%3A1400/0%2AmNxL4Ccgoc2vkpBg.png


Unnecessary re-renders:

  • Block JS thread

  • Delay event handling

  • Cause scroll jank

  • Affect animations


❌ Common useState Misuse

const [intervalId, setIntervalId] = useState(null);
setIntervalId(setInterval(() => { // do something }, 1000));

Problem:

  • UI does not depend on intervalId

  • Yet every update triggers a re-render


✅ Correct Approach with useRef

const intervalRef = useRef(null); intervalRef.current = setInterval(() => { // do something }, 1000);

Now:

  • No re-renders

  • No wasted work

  • JS thread stays free


๐Ÿงฉ When useRef Is the Better Tool

Use useRef for:

  • Timers (setTimeout, setInterval)

  • Animation values

  • Mutable counters

  • Previous values

  • Flags that don’t affect UI

  • Storing instance-like data


⚠️ Important Rule (Very Important)

Never use useRef for UI data

If the UI depends on it, it must be state.

// ❌ Wrong textRef.current = "Hello"; <Text>{textRef.current}</Text>

React will not re-render → UI won’t update.


๐Ÿ—️ Architecture Note (Old vs New)

  • Old architecture: re-renders were more expensive

  • New architecture (Fabric): rendering is faster

  • ❗ But unnecessary renders are still unnecessary

No architecture removes the cost of wasted work.


๐Ÿ’ก Final Rule

If changing a value should update the UIuseState
If changing a value should not update the UIuseRef

Using the right hook is not micro-optimization —
it’s respecting how React Native works internally.

Comments

Popular posts from this blog

Monorepo Setup Guide

๐ŸŒ Why console.log Slows Down React Native Apps

๐Ÿง  React Native New Architecture Explained