Common Pitfalls and Best Practices with useState: Chacha’s Lessons Learned

Deepesh AgrawalDeepesh Agrawal
4 min read

Imagine you're at a bustling chai stall in a small Indian town, where the vendor, Chacha, is juggling orders like a pro. He’s got a chalkboard where he scribbles the number of chai orders—sometimes 5, sometimes 10. Every time a customer shouts, "Chacha, ek chai aur!" he updates the count. This chalkboard is his way of keeping track of the state of his stall—how many cups to pour. Now, think of React’s useState as Chacha’s chalkboard: a simple, reliable way to manage and update the "state" of your app. Let’s dive into the concept of useState from scratch, with a story to make it crystal clear, and a sprinkle of Hindi to keep it relatable.

Back at Chacha’s Chai Stall, business is booming, but Chacha’s new app starts acting up. Sometimes the order count doesn’t update, or the app behaves like Chacha forgot the recipe for masala chai! This is where understanding the quirks of useState saves the day. Let’s explore some common mistakes developers make with useState and how to avoid them, with Chacha’s chai stall as our guide.

Pitfall 1: Forgetting How setOrders Works

One day, Chacha tries to add three chais at once by calling setOrders multiple times in a row:

const addThreeOrders = () => {
  setOrders(orders + 1); // Add 1
  setOrders(orders + 1); // Add 1 again?
  setOrders(orders + 1); // One more?
};

Chacha expects the order count to jump from 0 to 3, but it only increases by 1! Why? Because setOrders doesn’t update orders immediately. React batches state updates for performance, and each setOrders call uses the same initial orders value from the current render. It’s like Chacha writing “+1” on his chalkboard three times but only looking at the original number each time.

Fix: Use the callback form of setOrders, which gives you the latest state:

const addThreeOrders = () => {
  setOrders(prevOrders => prevOrders + 1);
  setOrders(prevOrders => prevOrders + 1);
  setOrders(prevOrders => prevOrders + 1);
};

Now, each setOrders call builds on the previous state, ensuring the count goes up by 3.

Arre, Chacha, ab toh teeno chai count hongi!

Pitfall 2: Mutating State Directly

Chacha decides to track chai flavors in an array:

const [chaiFlavors, setChaiFlavors] = useState(['regular']);
const addFlavor = () => {
  chaiFlavors.push('masala'); // Bad move!
  setChaiFlavors(chaiFlavors);
};

The app doesn’t update! Why? Because chaiFlavors.push modifies the array directly, but React doesn’t notice since the array reference hasn’t changed. It’s like Chacha scribbling a new flavor on his chalkboard but not telling anyone to look at it.

Fix: Always create a new array or object when updating state:

Pitfall 3: Overusing useState

Chacha wants to track orders, chai type, and customer name separately:

javascript

CollapseWrapRun

Copy

const [orders, setOrders] = useState(0);
const [chaiType, setChaiType] = useState('regular');
const [customer, setCustomer] = useState('Unknown');

This works, but it’s like Chacha using three chalkboards when one could do. Managing multiple useState calls can get messy, especially if they’re related.

Best Practice: Combine related state into a single object:

javascript

CollapseWrapRun

Copy

const [order, setOrder] = useState({
  count: 0,
  type: 'regular',
  customer: 'Unknown'
});

const addOrder = () => {
  setOrder({ ...order, count: order.count + 1 });
};

const updateCustomer = (name) => {
  setOrder({ ...order, customer: name });
};

This keeps things organized and reduces the number of state variables. Ek hi chalkboard, Chacha, sab kuch likh do!

Pitfall 4: Not Handling Async Updates

Chacha adds a feature to fetch the day’s special chai from an API and update the state:

const fetchSpecialChai = async () => {
  const response = await fetch('https://api.chai.com/special');
  const data = await response.json();
  setOrder({ ...order, type: data.special }); // Problem!
};

Sometimes, the order value is stale because fetchSpecialChai runs asynchronously, and order might not reflect the latest state. It’s like Chacha writing the special before checking if new orders came in.

Fix: Use the callback form again to ensure you’re working with the latest state:

const fetchSpecialChai = async () => {
  const response = await fetch('https://api.chai.com/special');
  const data = await response.json();
  setOrder(prevOrder => ({ ...prevOrder, type: data.special }));
};

This guarantees the update uses the current state, no matter when the API responds.

Best Practice: Keep State Minimal

Only store what you need in state. If Chacha can calculate the total price based on the number of orders (say, 10 rupees per chai), don’t store the price in state:

const totalPrice = order.count * 10; // Derive it, don’t store it

This avoids unnecessary state updates and keeps your app simple. Chacha, bas order count rakho, baaki hisaab apne aap ho jayega!

Conclusion

By avoiding these pitfalls—batch updates, direct mutations, overusing state, and async issues—you can make your useState code as smooth as Chacha’s chai. Use the callback form for updates, create new objects/arrays, combine related state, and keep state minimal. With these tricks, your React app will run like a well-oiled chai stall, ready to serve up orders with a smile. Ab jao, aur apna code perfect karo, Chacha ki tarah!

10
Subscribe to my newsletter

Read articles from Deepesh Agrawal directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Deepesh Agrawal
Deepesh Agrawal

building shits