DEV Community

Prince Patel
Prince Patel

Posted on

React State vs Server State: Why You Shouldn't Store Everything in useState

I used to think fetching API data in React was pretty straightforward.

Fetch the data → put it in useState → render it.
And for a small application, it works perfectly fine.

const [users, setUsers] = useState([]);

useEffect(() => {
fetch("/api/users")
.then(res => res.json())
.then(data => setUsers(data));
}, []);

The problem starts when the application grows.

Suddenly, you need to handle:

Loading states
Errors
Refetching
Caching
Stale data
Retries
Multiple components using the same data
Keeping server data in sync

At that point, the question isn't really, "Can I use useState?"
Of course I can.

The better question is:

Who owns this state?
Client State vs Server State :

Client state is state owned by the UI.

Things like:

Is a modal open?
Which tab is selected?
What's currently typed in an input?
Which item is selected?

For example:

const [isModalOpen, setIsModalOpen] = useState(false);
const [selectedTab, setSelectedTab] = useState("profile");

useState is a great fit here because the component is responsible for that state.

Server state is different.

Think about:

Users
Products
Orders
Notifications
Comments
Dashboard data

This data comes from an external source.

Your React application is essentially consuming a snapshot of data that the server owns.
That means the data can change outside your component.
It can become stale, need to be refetched, or be shared across multiple components. You may also want caching and background updates.
That's where something like TanStack Query becomes useful.

Instead of manually managing:

const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);

you can let a server-state library handle much of that lifecycle:

const {
data: users = [],
isLoading,
error
} = useQuery({
queryKey: ["users"],
queryFn: getUsers
});

Now the responsibility isn't just "store this API response in state."
You're working with data that has a lifecycle:

fetch → cache → become stale → refetch → update

That's the part that useState doesn't try to solve.

So, should we stop using useState?
Definitely not.

I still use useState for UI state all the time.
The lesson is simply that not all state is the same.

A useful mental model is:

UI owns it → useState
Server owns it → consider a server-state solution

Once I started thinking about state this way, React state management became much easier to reason about.
It's not about replacing useState.
It's about using the right abstraction for the problem you're actually solving.

Have you had a similar experience where a simple useState + useEffect solution eventually became difficult to maintain?

Top comments (0)