DEV Community

Cover image for Don't Sync State. Derive It! - The Mental Model That Fixes Half Your React Bugs
Mohamad Msalme
Mohamad Msalme

Posted on

Don't Sync State. Derive It! - The Mental Model That Fixes Half Your React Bugs

Know WHY β€” Let AI Handle the HOW πŸ€–

Ever find yourself writing useEffect hooks just to copy data from one state variable to another? What if I told you that most state synchronization is actually a design mistake - and there's a better way that eliminates entire categories of bugs?

πŸ€” Let's Start With a Common Mistake

You're building a user dashboard that shows server data with some client-side filtering:

function UserDashboard({ userId }) {
  const [users, setUsers] = useState([]);
  const [filteredUsers, setFilteredUsers] = useState([]);
  const [searchTerm, setSearchTerm] = useState('');

  // Fetch users from server
  useEffect(() => {
    fetchUsers(userId).then(setUsers);
  }, [userId]);

  // Sync filtered users when users or search term changes
  useEffect(() => {
    const filtered = users.filter(user => 
      user.name.toLowerCase().includes(searchTerm.toLowerCase())
    );
    setFilteredUsers(filtered);
  }, [users, searchTerm]);

  return (
    <div>
      <input 
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {filteredUsers.map(user => <UserCard key={user.id} user={user} />)}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

The Problem: You've just created two sources of truth for the same data. What could go wrong?

🧠 Think Like a Database for a Moment

In database design, there's a principle: Don't store what you can calculate.

Why? Because the moment you store calculated data separately:

  • It can get out of sync with the source
  • You need complex synchronization logic
  • Bugs multiply like rabbits

Your React components are mini-databases. The same principle applies.

πŸ”„ The Synchronization Tax

Every time you copy state, you pay the "synchronization tax":

  • Race Conditions: What if users update before the sync effect runs?
  • Extra Re-renders: Your component renders twice - once with stale data, once with synced data
  • Bug Surface Area: Every sync effect is a potential bug waiting to happen
  • Mental Overhead: You have to remember which state is "real" and which is "derived"
// The synchronization tax in action
const [data, setData] = useState([]);
const [sortedData, setSortedData] = useState([]);
const [filteredData, setFilteredData] = useState([]);
const [paginatedData, setPaginatedData] = useState([]);

// Four pieces of state that can all get out of sync!
Enter fullscreen mode Exit fullscreen mode

✨ The Derivation Solution

Here's the mindset shift: Don't store derived state. Calculate it on every render.

function UserDashboard({ userId }) {
  const [users, setUsers] = useState([]);
  const [searchTerm, setSearchTerm] = useState('');

  useEffect(() => {
    fetchUsers(userId).then(setUsers);
  }, [userId]);

  // Derive, don't sync!
  const filteredUsers = users.filter(user => 
    user.name.toLowerCase().includes(searchTerm.toLowerCase())
  );

  return (
    <div>
      <input 
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
      />
      {filteredUsers.map(user => <UserCard key={user.id} user={user} />)}
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

One source of truth. Zero synchronization bugs.

πŸ’‘ Real-World Examples Where Developers Sync Instead of Derive

1. The Props-to-State Copy

// ❌ Syncing props into state
function UserProfile({ user }) {
  const [currentUser, setCurrentUser] = useState(user);

  useEffect(() => {
    setCurrentUser(user); // This will break!
  }, [user]);

  return <div>{currentUser.name}</div>;
}

// βœ… Just use the props directly
function UserProfile({ user }) {
  return <div>{user.name}</div>;
}
Enter fullscreen mode Exit fullscreen mode

2. The Server Data Duplication

// ❌ Copying server data into multiple state variables
const { data: posts } = useQuery(['posts'], fetchPosts);
const [featuredPosts, setFeaturedPosts] = useState([]);

useEffect(() => {
  setFeaturedPosts(posts?.filter(post => post.featured) || []);
}, [posts]);

// βœ… Derive it every time
const { data: posts } = useQuery(['posts'], fetchPosts);
const featuredPosts = posts?.filter(post => post.featured) || [];
Enter fullscreen mode Exit fullscreen mode

3. The Manual Calculation Cache

// ❌ Manually tracking calculated values
const [items, setItems] = useState([]);
const [totalPrice, setTotalPrice] = useState(0);

useEffect(() => {
  const total = items.reduce((sum, item) => sum + item.price, 0);
  setTotalPrice(total);
}, [items]);

// βœ… Calculate on each render
const [items, setItems] = useState([]);
const totalPrice = items.reduce((sum, item) => sum + item.price, 0);
Enter fullscreen mode Exit fullscreen mode

⚑ "But What About Performance?"

Here's the mental model shift: React is already optimized for this pattern.

  • React's reconciliation is fast at detecting when derived values actually change
  • Most calculations are cheaper than you think
  • If performance becomes an issue, use useMemo selectively
// Only optimize when you actually need to
const expensiveCalculation = useMemo(() => {
  return reallyExpensiveFunction(largeDataSet);
}, [largeDataSet]);
Enter fullscreen mode Exit fullscreen mode

🏠 The Perfect Analogy: Your Home's Temperature

Think of state synchronization like having multiple thermometers in your house that you manually update whenever the temperature changes.

The Sync Approach:

  • Kitchen thermometer shows 72Β°F
  • Living room thermometer shows 72Β°F
  • Temperature changes to 75Β°F
  • You manually update ALL thermometers

The Derive Approach:

  • One central temperature sensor
  • All displays read from the same source
  • Temperature changes? All displays automatically show the correct value

Which approach is more reliable?

🎯 The Mental Model That Changes Everything

Stop thinking: "I need to keep this data in sync"

Start thinking: "I need to calculate this data from the source"

  • Server data β†’ Derive client views
  • Props β†’ Derive component state
  • User input β†’ Derive validation messages
  • Form fields β†’ Derive submit button enabled state

πŸ” How to Spot Sync State in Your Code

Look for these patterns in your codebase:

// 🚨 Red flags
useEffect(() => {
  setSomething(derivedFromSomethingElse);
}, [somethingElse]);

const [originalData, setOriginalData] = useState([]);
const [processedData, setProcessedData] = useState([]);

// Two pieces of state that represent the same information
Enter fullscreen mode Exit fullscreen mode

🧠 The Bigger Picture

When you derive instead of sync:

  • Single Source of Truth: Your data flow becomes predictable
  • Fewer Bugs: Impossible to have out-of-sync state
  • Simpler Code: No complex synchronization effects
  • Better Performance: React optimizes derived calculations automatically

Your derivation strategy IS your data consistency strategy.

πŸ’­ The Takeaway

learn the HOW: "Use useEffect to keep state in sync."

When you understand the WHY: "Derived state eliminates synchronization bugs and creates predictable data flow," you gain a powerful tool that makes your React code more reliable and easier to reason about.

Next time you feel like syncing state, stop and ask: "Can I derive this instead?"

Remember: Know the WHY behind React's data flow principles, and the HOW becomes a natural extension of that understanding.

Top comments (0)