Stop Exploiting Boolean States in React. | by Shreyash | Jul, 2026
That’s a Whole Lotta States!
To see how bad this gets, look at the sheer amount of boilerplate code required just to handle a basic fetch request with independent booleans:
const fetchData = async () => {
setIsLoading(true);
setIsError(false);
setIsSuccess(false);try {
const response = await api.getDashboardData();
setData(response);
setIsSuccess(true);
} catch (error) {
setIsError(true);
} finally {
setIsLoading(false);
}
};
Look at just how much code is dedicated purely to manage the data state . You have three separate lines just turning flags on and off.
The real nightmare happens when you or a colleague revisits this file weeks later to add a new requirement — say like a “retry” button. If you forget to reset isError back to false when the user retries, or if you update the state variables in the wrong order, you create an invalid state.
An invalid state means your data flags contradict each other. For instance, isLoading is true, but isError is also true.
Get Shreyash’s stories in your inbox
Join Medium for free to get updates from this writer.
When you write your conditional rendering down in the JSX, it looks like this:
return (
{isLoading && }
{isError && }
{isSuccess && }
);
If an invalid state occurs, your app will literally render the loading and the error banner on the screen at the exact same time.
The Fix
Booleans are binary — they are either true or false. When you write three separate boolean states together, you open the door to a bunch of overlapping, messy combinations. But an API call can only ever be in one distinct phase at a specific moment of time. It is either waiting to start, actively loading, successfully finished, or failed.
Instead of tracking multiple separate flags, we can represent the actual phase of the operation with a single status string.
const [data, setData] = useState(null);
const [status, setStatus] = useState('idle'); // 'idle' | 'loading' | 'success' | 'error'
Look at how much cleaner the data fetching logic becomes when you switch to this pattern:
const fetchData = async () => {
setStatus('loading');try {
const response = await api.getDashboardData();
setData(response);
setStatus('success');
} catch (error) {
setStatus('error');
}
};
The finally block is gone, and the manual resetting is completely eliminated. Because a string can only hold one value at a time, it is physically impossible to be in a state of 'loading' and 'error' simultaneously.
Keeping Your JSX Code Clean
This pattern completely cleans up the bottom of your file. Instead of writing complex boolean checks, you can read the status directly or use a good-old switch statement:
if (status === 'loading') return ;
if (status === 'error') return ;
if (status === 'success') return ;return ;
If keeping the conditionals inline inside the main return block is your vibe:
return (
{status === 'loading' && }
{status === 'error' && }
{status === 'success' && }
);