LogoTRUONG PHAM
Home
Projects
Portfolio
Blogs
YouTube
Contact

Newsletter

Stay updated with technical artifacts and engineering insights.

LogoTRUONG PHAM

Building scalable software and sharing insights on technology & life.

Sitemap

  • Home
  • Projects
  • Portfolio
  • Blogs
  • YouTube
  • Contact

Connect

  • GitHub
  • LinkedIn
  • Email
  • YouTube

© 2024 TRUONG PHAM. © All rights reserved.

Privacy PolicyTerms of Service
Back/50 FRONTEND LESSONS – HARD-EARNED EXPERIENCES
Blog #26: Don't let 'Single Source of Truth' become blind dogma

Blog #26: Don't let 'Single Source of Truth' become blind dogma

I used to believe every variable in an application must reside in a single Store. That was a costly mistake of thousands of lines of redundant code.

June 2, 2024·3 min read

I used to be a fanatical follower of the slogan: "Single Source of Truth." In the early days of getting familiar with Redux, I believed that for an application to be predictable, every state—from User info to whether a dropdown is open or closed—must be centrally managed in the Global Store.

I believed it because it sounded like very "standard architecture." "If everything is in one place, I can easily debug with Redux DevTools, can Time Travel, and never fear data being out of sync." That was theory. But reality gave me a cold shower in a complex financial management Dashboard project.

1. Context: When the Store becomes a "trash heap"

The system at the time had dozens of charts and data tables. Because I believed in a Single Source of Truth, I pushed isSidebarOpen, isModalLoading, and even the inputValue of each search box into Redux.

The result? Every time a user typed a character, the entire massive Store updated, leading to 70% of unrelated components on the screen having to re-render because they were "listening" to some part of the Store. The project was so laggy that typing wasn't even smooth.

2. Concept: State Locality

The problem was that I had confused Global Truth (Data that truly needs sharing) and Ephemeral State (Temporary state that disappears when the component unmounts).

// How I used to do it (Sounds "correct" but is wrong)
const SearchComponent = () => {
  const value = useSelector(state => state.search.tempValue);
  const dispatch = useDispatch();

  return <input value={value} onChange={(e) => dispatch(updateSearch(e.target.value))} />;
};

Why did this sound right? Because it followed the "One-way data flow" rule perfectly. But in reality, forcing an input to go all the way to the Global Store before returning to display itself is a massive waste of resources.

3. Common Mistake and Balance

A Junior will also often do what I did: fearing that state will be "scattered," so they push everything to Context or Store. They consider useState something "amateur" compared to Redux.

Comparison between two ways:

  • Doing it "by the book" (dogmatically): Everything in the Store -> Easy to debug with tools but slow app, long code because of having to write Action/Reducer for even small things.
  • Doing it practically (Balanced): State sits at the closest place where it's used.
    • Dropdown open? useState at that component.
    • Product filtering data? Local state of that page.
    • Login info? Global Store.

Lesson Learned

I realized that "Single Source of Truth" does not mean "Single Object of Everything."

  • When the old way is still correct: In very small apps, or data that truly needs to be synchronized across 3-4 different screens.
  • When to avoid: Avoid pushing pure UI states (open/close, hover, input text) into the Global Store. It only makes your system heavier and harder to reuse components.

The truth doesn't lie in a single place; it sits at the most appropriate place for each component.


Notes on the day I learned to place values correctly.

Previous: Blog #25: Context API Perf Hit – When the 'standard React' solution betrays youAll posts in this seriesNext: Blog #27: useEffect is not the place for doing all synchronizing logic