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:
- Which components need to read this state?
- 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:
- State Ownership: The
App
component now owns thecartItems
array. This is our single source of truth. - Data Flows Down:
App
rendersCartSummary
and passes it the number of items in the cart (cartItems.length
).CartSummary
simply displays the number it receives.
- Events Flow Up:
App
rendersProductList
and passes down thehandleAddToCart
function.- When a user clicks "Add to Cart" in the
ProductList
, it calls thehandleAddToCart
function, which updates the state in theApp
component.
- Predictable Re-renders: When the state in
App
changes, it re-renders, passing the newcartItems.length
toCartSummary
, 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.