🧠 React UI Control: State vs Ref vs CSS (With a Real Example)

Before diving into examples, it's important to clearly understand the difference between state and ref in React.

πŸ”΅ State vs πŸ”΄ Ref (Quick Intuition)

πŸ”΅ State represents data that defines your UI. Changes trigger a re-render, React fully controls the UI, and it is used for application logic. πŸ‘‰ If it affects what the user should see based on app status, it should be state.

πŸ”΄ Ref is a direct reference to a DOM element or a mutable value. It does not trigger a re-render, React does not track its changes, and it is used for imperative actions. πŸ‘‰ If it's just a direct DOM interaction or a temporary effect, a ref is enough.

πŸ”΅ 1. State β€” React-controlled UI

State means React renders the UI based on data changes. Use cases: like/unlike, modal open/close, delete state, form handling.

const [isLiked, setIsLiked] = useState(false);

<button onClick={() => setIsLiked(!isLiked)}>
  {isLiked ? "❀️ Liked" : "🀍 Like"}
</button>

πŸ‘‰ The UI is fully driven by state.

πŸ”΄ 2. Ref β€” Direct DOM control

A ref lets you manipulate DOM elements without re-rendering React. Use cases: focusing inputs, scrolling, imperative animations, temporary DOM effects.

const btnRef = useRef(null);

btnRef.current.style.transform = "scale(0.9)";

πŸ‘‰ React is not aware of this change.

🟒 3. CSS β€” Best for visual effects

CSS is the simplest and most efficient way to create UI effects: hover effects, transitions, animations and click feedback.

.delete-btn:active {
  transform: scale(0.9);
  background-color: red;
}

πŸ‘‰ No JavaScript needed.

βš–οΈ The Key Insight

Not everything in the UI should be state. Choosing the wrong approach leads to unnecessary re-renders, messy logic and hard-to-debug UI behavior.

🧩 Real Example: Delete Button Flash

In a React project, I needed a delete button that shows a quick red flash before removing an item.

❌ Wrong: using state for everything

const [isFlashing, setIsFlashing] = useState(false);

Problem: an unnecessary re-render β€” overkill for a 200 ms visual effect.

❌ Mixed: state + ref

// effect on the button
btnRef.current.style.backgroundColor = "red";

// delete the whole component
setIsDeleting(true);

Problem: two sources of truth and inconsistent UI behavior.

βœ… Correct: CSS + class toggle

const handleDeleteClick = (e) => {
  const btn = e.currentTarget;
  btn.classList.add("flash");
  setIsDeleting(true);

  setTimeout(() => {
    btn.classList.remove("flash");
    handleDelete(id);
  }, 200);
};
.delete-btn.flash {
  background-color: red;
  transform: scale(0.9);
}

🎯 Final Takeaway

  • State β†’ for real application data
  • Ref β†’ for direct DOM manipulation
  • CSS β†’ for simple visual effects
πŸ’‘ If something does not represent application state, it probably should not be state.