Skip to main content

Creating a Single Source of Truth #60

📖 Introduction

Following our practical examples of Lifting State Up, it's time to formalize the core design principle that underpins it: creating a Single Source of Truth (SSOT). This principle is one of the most important concepts in React for building robust, predictable, and maintainable applications. It dictates that for any given piece of data, there should be only one component that "owns" and manages it.


📚 Prerequisites

Before we begin, please ensure you have a solid grasp of the following concepts:

  • The "Lifting State Up" pattern
  • Component state (useState) and props
  • Basic component composition

🎯 Article Outline: What You'll Master

In this article, you will learn:

  • Foundational Theory: What a "Single Source of Truth" is and why it's critical for preventing bugs.
  • The Benefits: Understanding how SSOT leads to more predictable and easier-to-debug applications.
  • Identifying the "Owner": How to decide which component should own a particular piece of state.
  • Practical Application: Refactoring a simple e-commerce UI to use a single source of truth for its shopping cart.

🧠 Section 1: The Core Concept: What is a Single Source of Truth?

In any application, you'll have data that changes over time. For example, the items in a shopping cart, whether a user is logged in, or the text in a search field. The Single Source of Truth principle states that the state for each of these pieces of data should live in only one place.

Why is this so important?

Imagine you have two components that both display the number of items in a shopping cart. If each component managed its own cartItemCount state, what would happen?

  • If a user adds an item, you'd have to remember to update the state in both components.
  • If you forget to update one, the UI becomes inconsistent and displays conflicting information. This is a common source of bugs.
  • As the application grows, this problem gets exponentially worse.

By establishing a single source of truth, we eliminate this entire class of problems. There is only one cartItemCount state, and any component that needs to display it will receive it from that single, authoritative source.


💻 Section 2: Identifying the State's "Owner"

The key to implementing a single source of truth is to identify the correct "owner" for each piece of state. The owner is the component that will manage the state and pass it down.

To find the owner, ask yourself:

  1. Which components need to read this state?
  2. Find their closest common ancestor in the component tree.

This common ancestor is the perfect place to own the state. It can then pass the state down as props to all the components that need it.


🛠️ Section 3: Project-Based Example: A Shopping Cart UI

Let's consider a simple e-commerce UI. We have a list of products and a "cart summary" in the header that shows how many items are in the cart.

The Problem: Two Sources of Truth

Here's how the application might look if we don't follow the SSOT principle.

// This is an example of what NOT to do.

// ProductList.jsx
function ProductList() {
const [cartItems, setCartItems] = useState([]);
// ... logic to add items to cartItems ...
return <div>{/* ... product list ... */}</div>;
}

// CartSummary.jsx
function CartSummary() {
const [cartItemCount, setCartItemCount] = useState(0);
// ... how does this get updated? ...
return <div>Cart: {cartItemCount} items</div>;
}

// App.jsx
function App() {
return (
<div>
<CartSummary />
<ProductList />
</div>
)
}

The ProductList has its own cartItems state, and CartSummary has its own cartItemCount state. They are completely disconnected. This is a recipe for bugs.

The Solution: Lifting State to a Common Ancestor

The closest common ancestor of ProductList and CartSummary is App. Therefore, App should own the shopping cart state.

Let's refactor it correctly.

// App.jsx - The Single Source of Truth

import React, { useState } from 'react';

// Child components would be in their own files
function ProductList({ onAddToCart }) {
const products = ['Apples', 'Oranges', 'Bananas'];
return (
<div>
<h2>Products</h2>
<ul>
{products.map((product, index) => (
<li key={index}>
{product} <button onClick={() => onAddToCart(product)}>Add to Cart</button>
</li>
))}
</ul>
</div>
);
}

function CartSummary({ cartItemCount }) {
return <div>Cart: {cartItemCount} items</div>;
}


function App() {
const [cartItems, setCartItems] = useState([]);

const handleAddToCart = (product) => {
setCartItems([...cartItems, product]);
};

return (
<div>
<CartSummary cartItemCount={cartItems.length} />
<hr />
<ProductList onAddToCart={handleAddToCart} />
</div>
);
}

export default App;

Walkthrough:

  1. State Ownership: The App component now owns the cartItems array. This is our single source of truth.
  2. Data Flows Down:
    • App renders CartSummary and passes it the number of items in the cart (cartItems.length).
    • CartSummary simply displays the number it receives.
  3. Events Flow Up:
    • App renders ProductList and passes down the handleAddToCart function.
    • When a user clicks "Add to Cart" in the ProductList, it calls the handleAddToCart function, which updates the state in the App component.
  4. Predictable Re-renders: When the state in App changes, it re-renders, passing the new cartItems.length to CartSummary, ensuring the UI is always consistent.

✨ Section 5: Best Practices

  • Keep State Local First: Don't start by putting all your state in the top-level component. Always start by keeping state as local as possible. Only lift it up when you have a clear need to share it between components.
  • SSOT Applies to All State: This principle isn't just for complex data. It applies to everything, from simple boolean flags (like isMenuOpen) to user input in forms.

💡 Conclusion & Key Takeaways

The Single Source of Truth principle is a simple but powerful idea that will dramatically improve the quality of your React applications.

Let's summarize the key takeaways:

  • One State to Rule Them All: For any piece of data, there should be one, and only one, component that owns and manages it.
  • Find the Common Ancestor: To determine which component should own the state, find the closest common ancestor of all the components that need to access it.
  • SSOT Prevents Bugs: By eliminating duplicate and out-of-sync state, you make your application more predictable and easier to debug.

Challenge Yourself: Extend the shopping cart example. Add a CartDetails component that displays the full list of items in the cart. Since App is the single source of truth, you should be able to pass the cartItems array down to this new component easily.


➡️ Next Steps

Now that you understand the importance of designing your components around a single source of truth, we will look closer at the mechanism that makes it all work. In the next article, "Inverse Data Flow: Child-to-Parent Communication (Part 1)", we will take a deeper dive into how child components can communicate with their parents using callbacks.

Thank you for your dedication. Stay curious, and happy coding!


glossary

  • Single Source of Truth (SSOT): A design principle in which the state for any given piece of data is managed in a single, authoritative location within the application.

Further Reading