---
title: "02 — Props, State, and Refs — The Three Ways a Component Holds Data"
uid: props-state-refs
tags: ["state", "react", "roadmap:react", "refs", "props", "fundamentals"]
excerpt: "Props are inputs from the parent, state is the component's own memory that drives re-renders, refs are the escape hatch that persists without triggering them. Three channels, three jobs."
date: 2026-08-13T03:27:46+0000
source: https://www.aveshina.my.id/en/blog/props-state-refs
---

Props, state, and refs blurred into "just variables the component knows about" until I separated them by job. The split that made them click: **props are inputs handed down by the parent, state is the component's own memory that drives re-renders, and refs are an escape hatch for values that must persist across renders without triggering them.** [1][2] Each one exists for a different reason, and mixing them up is the source of most beginner confusion.

## Props: the inputs a parent hands down

Props (short for properties) are the channel a parent uses to configure a child [1]. They're the function-argument equivalent — when I write <Card title="Hello" />, title is a prop the Card component receives. Three things define props:

- **They flow top-down.** A parent passes them; the child reads them. Data moves parent → child, never the reverse, unless the parent explicitly passes a callback prop the child invokes.
- **They're read-only.** A component must never mutate its own props [1]. If a value needs to change, that value is state, not props.
- **They trigger renders.** New prop values from the parent cause the child to re-render.

That second rule is the one I had to take seriously. "Treat props as immutable" is what keeps the component a pure function of its inputs — same props in, same JSX out. The moment I'm tempted to write to a prop, I've actually discovered I need state, or I need to lift the state up to the parent.

## State: the component's own memory

State is the component's private memory — values it owns and can change, where each change schedules a re-render [2]. Where props come from outside, state lives inside the component. In a functional component, state is created with the useState hook (covered in its own notes); for now the conceptual point matters more than the syntax:

- State changes are the engine of re-rendering. Mutating a state variable directly does nothing visible — I have to call the setter React gives me, which queues a new render.
- State is per-instance. Two <Counter /> components on the page each keep their own count.
- Updates may be asynchronous and batched, so I read the latest value through the setter's callback form when the next value depends on the previous.

```
function Counter() {
  // count is state: private to this instance, changing it re-renders
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}
```

The contrast with props is the whole story: **props are configuration from the parent, state is memory the component controls.** A common rookie move is to duplicate a prop into state ("I'll copy initialName into a state variable and edit that"). That's almost always wrong — it creates two sources of truth that drift apart. If the parent's value should drive the child, read the prop directly; if the child owns the editable value, the prop is just a seed.

## Refs: the escape hatch that doesn't re-render

Refs are the third channel, and the one I resisted longest. A ref holds a mutable value that **persists across renders but does not trigger a re-render when it changes** [3][4]. There are two jobs refs are actually for:

- **Reaching into the DOM.** To focus an input, measure an element, or integrate with a non-React library, I need the actual DOM node. A ref attached via <input ref={myRef} /> gives me that node imperatively [3].
- **Storing a value that shouldn't cause renders.** A debounce timer ID, a "has this effect run" flag, a cached value — things I need to remember across renders but where a re-render would be pointless or harmful.

```
function FocusInput() {
  const inputRef = useRef(null);
  const handleClick = () => inputRef.current.focus(); // imperative DOM access
  return (
    <>
      <input ref={inputRef} />
      <button onClick={handleClick}>Focus</button>
    </>
  );
}
```

The rule I follow for refs: **if the value should change the UI when it changes, it's state, not a ref.** Refs are deliberately outside React's render loop — that's their power and their danger. Every ref write is a place React isn't watching, so it's a place bugs hide. I reach for a ref only when state genuinely can't do the job (DOM access, or a value that must not cause renders).

```figure
<svg viewBox="0 0 720 300" xmlns="http://www.w3.org/2000/svg" class="my-6 w-full max-w-2xl" role="img" aria-label="A single Component box in the center with three data channels. From above, an arrow labeled props enters the top (read-only input from parent). Inside the box, a chip labeled state (private memory, changes trigger re-render). On the side, a door labeled refs (escape hatch: persists across renders, no re-render).">
  <defs>
    <marker id="psarrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto">
      <path d="M0,0 L10,5 L0,10 z" fill="#64748b"/>
    </marker>
  </defs>
  <g font-family="ui-sans-serif, system-ui, sans-serif" text-rendering="geometricPrecision">
    <!-- parent box -->
    <rect x="280" y="20" width="160" height="40" rx="8" fill="#e0e7ff" stroke="#6366f1" stroke-width="1.5"/>
    <text x="360" y="44" font-size="12" font-weight="700" fill="#1e1b4b" text-anchor="middle">Parent</text>

    <!-- props arrow down -->
    <path d="M360,60 L360,108" fill="none" stroke="#6366f1" stroke-width="2" marker-end="url(#psarrow)"/>
    <text x="372" y="86" font-size="11" fill="#4338ca" font-weight="700">props</text>
    <text x="372" y="100" font-size="9.5" fill="#64748b">read-only · top-down</text>

    <!-- component box -->
    <rect x="220" y="110" width="280" height="120" rx="10" fill="#1f2937" stroke="#374151" stroke-width="1.5"/>
    <text x="360" y="132" font-size="13" font-weight="700" fill="#f9fafb" text-anchor="middle">Component</text>

    <!-- state chip inside -->
    <rect x="244" y="150" width="120" height="46" rx="8" fill="#fef9c3" stroke="#ca8a04" stroke-width="1.5"/>
    <text x="304" y="170" font-size="12" font-weight="700" fill="#422006" text-anchor="middle">state</text>
    <text x="304" y="186" font-size="9" fill="#422006" text-anchor="middle">private · re-renders</text>

    <!-- refs door on side -->
    <rect x="376" y="150" width="110" height="46" rx="8" fill="#e2e8f0" stroke="#64748b" stroke-width="1.5" stroke-dasharray="4 3"/>
    <text x="431" y="170" font-size="12" font-weight="700" fill="#334155" text-anchor="middle">refs</text>
    <text x="431" y="186" font-size="9" fill="#334155" text-anchor="middle">escape hatch · no render</text>

    <!-- re-render arrow from state -->
    <path d="M304,196 C304,224 360,236 360,236" fill="none" stroke="#ca8a04" stroke-width="1.5" marker-end="url(#psarrow)"/>
    <text x="318" y="232" font-size="9.5" fill="#ca8a04" font-style="italic">setter → re-render</text>

    <!-- refs goes outside -->
    <path d="M486,173 C540,173 560,173 600,173" fill="none" stroke="#64748b" stroke-width="1.5" stroke-dasharray="4 3" marker-end="url(#psarrow)"/>
    <text x="540" y="166" font-size="9.5" fill="#64748b" text-anchor="middle">DOM node / value</text>

    <text x="360" y="275" font-size="10.5" fill="#9ca3af" text-anchor="middle" font-style="italic">props in from parent · state owned · refs out of band</text>
  </g>
</svg>
```

## How I use this

Before I add any data to a component, I run it through the three-question check:

- Does this value come from the parent? → **props.** Don't copy it into state.
- Does this value belong to this component, and should the UI change when it does? → **state.** Use the setter, never mutate directly.
- Does this value need to persist across renders but _not_ re-render when it changes (a DOM node, a timer, a cache)? → **ref.**

That check resolves almost every "where does this data go" question. The remaining one — state shared between siblings — is "lift it up to the common parent," which is also why I reach for context or a state library once the lifting gets deep.

## References

[1] React team, "Passing props to a component," react.dev, 2024. [Online]. Available: [https://react.dev/learn/passing-props-to-a-component](https://react.dev/learn/passing-props-to-a-component)

[2] React team, "State: a component's memory," react.dev, 2024. [Online]. Available: [https://react.dev/learn/state-a-components-memory](https://react.dev/learn/state-a-components-memory)

[3] React team, "Referencing values with refs," react.dev, 2024. [Online]. Available: [https://react.dev/learn/referencing-values-with-refs](https://react.dev/learn/referencing-values-with-refs)

[4] React team, "Manipulating the DOM with refs," react.dev, 2024. [Online]. Available: [https://react.dev/learn/manipulating-the-dom-with-refs](https://react.dev/learn/manipulating-the-dom-with-refs)

[5] R. Wieruch, "What is the difference between state and props in React?," Stack Overflow, 2023. [Online]. Available: [https://stackoverflow.com/questions/27991366/what-is-the-difference-between-state-and-props-in-react](https://stackoverflow.com/questions/27991366/what-is-the-difference-between-state-and-props-in-react)

[6] D. Pavlutin, "The complete guide to useRef() and refs in React," dmitripavlutin.com, 2023. [Online]. Available: [https://dmitripavlutin.com/react-useref-guide/](https://dmitripavlutin.com/react-useref-guide/)

```quiz
Q: Which data channel is read-only and flows only from parent to child?
- Props
- State
- Refs
correct: 0
explain: Props are inputs from the parent and must never be mutated by the child. State is the component's own memory; refs are an out-of-band escape hatch.

Q: Copying a prop into a state variable so the child can edit it is usually…
- correct, the cleanest pattern
- wrong — it creates two sources of truth that drift apart
correct: 1
explain: Duplicating a prop into state splits the source of truth. If the parent's value should drive the child, read the prop; if the child owns the value, the prop is only a seed.

Q: What distinguishes a ref from state?
- A ref persists across renders and does NOT trigger a re-render when it changes
- A ref triggers a re-render every time it changes, just like state
- A ref is reset to null on every render
correct: 0
explain: Refs hold mutable values that survive re-renders but live outside React's render loop — changing a ref never schedules a render.

Q: You need to focus an <input> imperatively when a button is clicked. Reach for…
- a state variable holding the focus boolean
- a ref attached to the input, calling .focus() on its current node
- a prop passed from the parent
correct: 1
explain: DOM access is imperative and outside React's data flow — that's exactly the job a ref is designed for.

Q: State updates that depend on the previous value should use…
- the setter's callback form: setX(prev => prev + 1)
- direct mutation of the variable
- a ref, because state is too slow
correct: 0
explain: Updates can be batched and asynchronous, so reading the latest value through the setter callback avoids stale-closure bugs.
```
