Ask whether the value affects rendering

If a value determines the text, layout or controls shown on screen, it usually belongs in state or is derived from state and props. A ref can remember a mutable value without requesting a render. That makes it useful for operation handles, but dangerous as an invisible replacement for data the visitor expects to see update. Choose by responsibility rather than by perceived performance.

Compare two timer values

A stopwatch needs an elapsed-time display and an interval identifier. The displayed time changes what React renders, so state is a suitable home. The interval identifier exists so code can later clear that timer; changing it should not redraw the screen. A ref can hold that handle across renders while keeping it available to event handlers and cleanup logic.

Remember that state is a render snapshot

Calling a state setter schedules a later render; it does not rewrite the local variable captured by the current handler. When the next value depends on the previous value, a functional update can make that dependency explicit. A ref’s current property is mutable immediately, but using it to bypass the state model can create a screen that displays an outdated value.

Use refs for stable external ownership

A DOM node, pending animation-frame identifier or current AbortController can be appropriate ref values. These are handles into work outside the declarative screen. Keep creation and cleanup aligned with the operation that owns them. A ref persisting across renders does not automatically cancel an old timer, disconnect an observer or make an external object safe to reuse.

Keep rendering pure

Avoid reading or changing mutable ref values during rendering when the component’s output depends on those changes. Such behavior makes the output harder to predict and can conflict with React’s rendering assumptions. Prefer state for visible decisions and effects or events for external coordination. A ref should clarify an escape hatch rather than turn the component into a collection of hidden mutable fields.

Verify visible and invisible updates separately

For the stopwatch example, start, stop and restart several times. Confirm the number changes while running, stops reliably and does not create duplicate intervals. Navigate away and verify cleanup. Then inspect whether changing an operation handle causes unnecessary work. The useful distinction is not which hook is faster in isolation, but whether the code accurately represents the screen and its resource lifecycle.

Further reading

Put the idea to work.

Explore free browser tools for images, text and developer workflows.

Try a free tool