Stop Breaking React: Why You Should Compute Values Instead of Storing Everything in State


How I fixed infinite re-renders and discovered a simple rule that changed how I write React components
Last week, I spent three hours debugging what seemed like a simple dashboard feature. Users could filter data by date range, and I needed to show charts based on the filtered results. Sounds easy, right?
Wrong. My component was stuck in an infinite rendering loop, the browser was freezing, and I was questioning my life choices.
But this frustrating bug taught me one of the most important React performance lessons: if you can compute it, don't store it in state.
The Bug That Started It All
Picture this: I'm building a dashboard where users can select date ranges to filter their advertising data. The component needs to:
Filter data based on the selected date range
Group the filtered data by category
Display charts with the grouped data
My first instinct was to manage everything with useState
. After all, if data changes, it should be in state, right?
const Dashboard = ({ adsData }) => {
const [dateRange, setDateRange] = useState({
since: '2025-01-01',
until: '2025-06-04'
});
const [visibleData, setVisibleData] = useState([]);
const [groupedData, setGroupedData] = useState([]);
// Filter data when date range changes
useEffect(() => {
const filtered = adsData.filter(item =>
item.date >= dateRange.since && item.date <= dateRange.until
);
setVisibleData(filtered);
}, [adsData, dateRange]);
// Group data when visible data changes
useEffect(() => {
const grouped = groupByCategory(visibleData);
setGroupedData(grouped);
}, [visibleData]);
return (
<div>
<DatePicker
value={dateRange}
onChange={setDateRange}
/>
<Chart data={groupedData} />
</div>
);
};
This code looks reasonable, but it's a ticking time bomb. Here's what happens:
User changes date range
dateRange
state updatesFirst
useEffect
runs, updatesvisibleData
Second
useEffect
runs, updatesgroupedData
Component re-renders because state changed
Sometimes this triggers the effects again
Infinite loop nightmare begins
The Simple Fix That Changed Everything
The solution was embarrassingly simple: stop storing computed data in state.
const Dashboard = ({ adsData }) => {
const [dateRange, setDateRange] = useState({
since: '2025-01-01',
until: '2025-06-04'
});
// Compute filtered data directly
const visibleData = useMemo(() =>
adsData.filter(item =>
item.date >= dateRange.since && item.date <= dateRange.until
), [adsData, dateRange]
);
// Compute grouped data from filtered data
const groupedData = useMemo(() =>
groupByCategory(visibleData),
[visibleData]
);
return (
<div>
<DatePicker
value={dateRange}
onChange={setDateRange}
/>
<Chart data={groupedData} />
</div>
);
};
No more useEffect
. No more infinite loops. Just clean, predictable code.
The Golden Rule: Compute Don't Store
Here's the rule that saved my sanity: If data can be calculated from existing state or props, calculate it directly instead of storing it in state.
Think of it like this: your component's state should only contain the "source of truth" - the core data that can't be derived from anything else. Everything else should be computed.
Real-World Examples to Drive This Home
Let me show you several scenarios where this principle applies:
Example 1: Shopping Cart
❌ Bad: Storing computed totals in state
const ShoppingCart = ({ items }) => {
const [cartItems, setCartItems] = useState(items);
const [subtotal, setSubtotal] = useState(0);
const [tax, setTax] = useState(0);
const [total, setTotal] = useState(0);
useEffect(() => {
const newSubtotal = cartItems.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
);
setSubtotal(newSubtotal);
setTax(newSubtotal * 0.1);
setTotal(newSubtotal * 1.1);
}, [cartItems]);
// Rest of component...
};
✅ Good: Computing totals directly
const ShoppingCart = ({ items }) => {
const [cartItems, setCartItems] = useState(items);
const subtotal = useMemo(() =>
cartItems.reduce((sum, item) =>
sum + (item.price * item.quantity), 0
), [cartItems]
);
const tax = subtotal * 0.1;
const total = subtotal + tax;
// Rest of component...
};
Example 2: Search and Filter
❌ Bad: Multiple state variables for derived data
const ProductList = ({ products }) => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const [filteredProducts, setFilteredProducts] = useState(products);
const [searchResults, setSearchResults] = useState(products);
useEffect(() => {
let filtered = products;
if (selectedCategory !== 'all') {
filtered = filtered.filter(p => p.category === selectedCategory);
}
setFilteredProducts(filtered);
}, [products, selectedCategory]);
useEffect(() => {
const searched = filteredProducts.filter(p =>
p.name.toLowerCase().includes(searchTerm.toLowerCase())
);
setSearchResults(searched);
}, [filteredProducts, searchTerm]);
};
✅ Good: One computation chain
const ProductList = ({ products }) => {
const [searchTerm, setSearchTerm] = useState('');
const [selectedCategory, setSelectedCategory] = useState('all');
const displayedProducts = useMemo(() => {
return products
.filter(p => selectedCategory === 'all' || p.category === selectedCategory)
.filter(p => p.name.toLowerCase().includes(searchTerm.toLowerCase()));
}, [products, selectedCategory, searchTerm]);
};
Example 3: Form Validation
❌ Bad: Storing validation results in state
const SignupForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isEmailValid, setIsEmailValid] = useState(false);
const [isPasswordValid, setIsPasswordValid] = useState(false);
const [doPasswordsMatch, setDoPasswordsMatch] = useState(false);
const [isFormValid, setIsFormValid] = useState(false);
useEffect(() => {
setIsEmailValid(email.includes('@') && email.includes('.'));
}, [email]);
useEffect(() => {
setIsPasswordValid(password.length >= 8);
}, [password]);
useEffect(() => {
setDoPasswordsMatch(password === confirmPassword);
}, [password, confirmPassword]);
useEffect(() => {
setIsFormValid(isEmailValid && isPasswordValid && doPasswordsMatch);
}, [isEmailValid, isPasswordValid, doPasswordsMatch]);
};
✅ Good: Computing validation on the fly
const SignupForm = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const isEmailValid = email.includes('@') && email.includes('.');
const isPasswordValid = password.length >= 8;
const doPasswordsMatch = password === confirmPassword;
const isFormValid = isEmailValid && isPasswordValid && doPasswordsMatch;
};
When to Use What: A Simple Decision Tree
Here's how I decide what approach to use:
Use useState
when:
Data comes from user input (form fields, toggles, selections)
Data is fetched from an API
Data represents the "source of truth" that can't be derived
Use useMemo
when:
Calculation is expensive (sorting large arrays, complex math)
Data is derived from state/props and used in multiple places
You want to prevent unnecessary re-calculations
Use regular variables when:
Calculation is simple and cheap
Result is only used once
No performance concerns
The Performance Win
This approach doesn't just prevent bugs - it actually improves performance:
Fewer re-renders: No cascading state updates
Predictable updates: Clear dependency tracking with
useMemo
Less memory: No extra state variables storing derived data
Easier debugging: Simpler component lifecycle
My New Mental Model
I now think of React components like this:
State = The essential data that defines what the component shows
Computed values = Everything else that can be calculated from state
Effects = Side effects like API calls, not data transformations
This mental shift has made my components more reliable and easier to understand.
The Bottom Line
Next time you reach for useState
to store some data, ask yourself: "Can I calculate this from existing state or props?"
If yes, compute it instead of storing it. Your future self (and your users) will thank you.
The infinite re-render bug that frustrated me for hours taught me one of the most valuable React lessons: the best state is the state you don't have to manage.
What's your go-to pattern for handling derived data in React? Have you run into similar infinite loop situations? I'd love to hear about your experiences in the comments!
Subscribe to my newsletter
Read articles from Aviral Sharma directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Aviral Sharma
Aviral Sharma
Driven 3rd year BTech (IT) student passionate about front-end development. Eager to leverage skills in React, Redux, JavaScript, Next.js, and Tailwind CSS to contribute to a dynamic team.