Sitemap

The Difference Between UI State, Server State, and Application State

10 min readSep 30, 2026

--

One of the easiest ways to make a React application difficult to maintain is to treat every piece of data as the same kind of state.

A dashboard might have:

  • A sidebar that is open or closed
  • A selected date range
  • A logged-in user
  • A list of orders fetched from an API
  • A loading indicator
  • A notification count
  • A selected dashboard filter
  • A modal that is currently open
  • User permissions
  • Cached API responses

All of these are “state.” But they are not the same kind of state — and that distinction matters.

When developers don’t separate these categories, they often end up putting everything into Redux, duplicating API data inside global state, passing props through multiple components, or building complicated synchronization logic to keep copies in sync with each other.

A better approach is to first ask: what kind of state am I dealing with?

For most React applications, state can be broadly divided into three categories:

UI State
↓
State related to the current interface
Server State
↓
State owned by an API/backend
Application State
↓
State shared across the application

Understanding these three categories makes it much easier to decide where state should live and which tool should manage it.

1. A Real-World Example: React Dashboard

Imagine we’re building an admin dashboard:

+------------------------------------------------+
| Sidebar | Dashboard |
| | |
| Users | Total Users: 12,450 |
| Orders | Total Orders: 8,231 |
| Reports | |
| Settings | [This Month ▼] |
| | |
| | Sales Chart |
| | |
| | Recent Orders |
+------------------------------------------------+

The dashboard gets its data from an API — GET /api/dashboard — which might return:

{
"totalUsers": 12450,
"totalOrders": 8231,
"revenue": 125000,
"recentOrders": []
}

At the same time, the frontend needs to manage things like sidebarOpen, selectedDateRange, isFilterModalOpen, and selectedOrder. And perhaps the entire application needs currentUser, permissions, theme, and notificationCount.

All of this is state. But putting all of it into one state-management solution is usually a mistake. Let’s separate it.

2. UI State

UI state represents the temporary state of the interface. It answers questions such as:

  • Is the sidebar open?
  • Is the modal visible?
  • Which tab is selected?
  • Is a dropdown open?
  • Which row is currently selected?
  • What value has the user typed into a form?
const [sidebarOpen, setSidebarOpen] = useState(false);
const [selectedTab, setSelectedTab] = useState("overview");
const [isModalOpen, setIsModalOpen] = useState(false);

The important characteristic is that this state is owned by the frontend. The backend doesn’t care whether the sidebar is open, which tab the user selected, or whether a modal is currently visible. This state belongs to the UI.

3. UI State Usually Belongs Close to the Component

One of the common mistakes in React applications is putting simple UI state into Redux:

dispatch(setSidebarOpen(true));

…for something that only one component cares about. This introduces unnecessary complexity. Instead:

const [sidebarOpen, setSidebarOpen] = useState(true);

is usually enough. A useful rule: if only one component needs the state, keep it local.

function Dashboard() {
const [isFilterOpen, setIsFilterOpen] = useState(false);
return (
<>
<button onClick={() => setIsFilterOpen(true)}>
Filters
</button>
{isFilterOpen && <FilterModal />}
</>
);
}

There’s no reason to introduce Redux here.

4. Server State Is Different

Now consider the dashboard API. The returned data isn’t really owned by React — it belongs to the backend. That’s server state, and the distinction matters because server state has characteristics UI state doesn’t. It can be:

  • fetched
  • cached
  • stale
  • refetched
  • invalidated
  • updated by another user
  • changed by another application
  • unavailable
  • loading
  • failed

For example, another administrator might create a new order. Your React application doesn’t know about that change automatically — the server does. Server state has its own lifecycle, independent of your component tree.

5. Why Server State Shouldn’t Automatically Go Into Redux

Suppose we use Redux for everything. We might create a dashboardSlice and store:

{
totalUsers: 12450,
totalOrders: 8231,
revenue: 125000,
loading: false,
error: null
}

At first, this looks reasonable. But then things get complicated — we need to handle the full request → loading → success/error → store → invalidate → refetch → stale-data → cache lifecycle ourselves. Then another page requests the same data. Should it use the same Redux state? What happens when the data becomes stale? When should it refetch? What happens after an update — do we manually clear the old data?

At that point, the Redux store is slowly becoming a hand-rolled API cache. That’s not really what Redux was designed to be.

6. Server State Needs Server-State Tools

For server state, a library purpose-built for it — TanStack Query (formerly React Query) is the standard choice in 2026 — is usually a better fit:

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

Now the library handles fetching, caching, loading states, errors, refetching, stale data, invalidation, and retries — instead of you manually rebuilding all of that inside Redux.

Get Silversky Technology’s stories in your inbox

Join Medium for free to get updates from this writer.

This creates a much cleaner separation:

React Query
|
+---- API data
+---- Cache
+---- Loading
+---- Errors
+---- Refetching
Redux / Zustand
|
+---- Application state
useState
|
+---- UI state

This separation is one of the most useful mental models when designing a React application.

7. Application State

Now consider a third category. Suppose the application has currentUser, permissions, selectedWorkspace, theme, and notificationPreferences. These aren't necessarily UI-only values — they may be needed by many unrelated components:

Header    → currentUser
Sidebar → permissions
Dashboard → permissions
Settings → currentUser

Passing these values through every component can quickly become difficult. This is where application state becomes useful — it represents information that multiple, otherwise-unrelated parts of the application need to access or update.

8. This Is Where a Global Store Makes Sense

For application-wide state, a global state manager is a reasonable choice. A quick note on which one: in 2026, Zustand has become the more common default for this kind of shared client state in small-to-medium apps, thanks to its minimal boilerplate — while Redux Toolkit remains the stronger fit for larger teams or apps with genuinely complex, interconnected client-side state that benefits from strict conventions and time-travel debugging. Either way, the architecture looks the same:

Global Store (Zustand / Redux Toolkit)
|
+----------+----------+
| | |
Header Sidebar Dashboard
| | |
User info Permissions Workspace

The important point isn’t “Redux is good” or “Zustand is good.” The important point is: a global store is useful when the state actually needs to be shared and centrally managed — not just because it exists in the project. Using a global store for every useState variable doesn't make an application more scalable. It usually makes it more complicated.

9. The Three Types of State in One Dashboard

Bringing it together, our dashboard might contain:

UI State — sidebarOpen, isFilterModalOpen, selectedTab, expandedRow Best fit: useState / useReducer

Server State — dashboard statistics, recent orders, user list, sales report, any API response Best fit: TanStack Query

Application State — currentUser, permissions, selectedWorkspace, global notification count, application preferences Best fit: Zustand, Redux Toolkit, or another global state manager

React Application
|
+-----------------+-----------------+
| | |
v v v
UI State Server State Application State
| | |
useState TanStack Query Zustand / Redux
useReducer | |
| API / Backend |
| |
+----------------+------------------+
|
Components

This is a much cleaner model than putting everything into one global store.

10. A Common Mistake: Copying Server State Into a Global Store

Consider this:

const { data } = useQuery({
queryKey: ["users"],
queryFn: fetchUsers
});
useEffect(() => {
dispatch(setUsers(data));
}, [data]);

Now we’ve duplicated the same server state: API → TanStack Query → Redux → Component. If TanStack Query already owns the API data, why copy it into a global store? Now there are two sources of truth:

TanStack Query: users = [A, B, C]
Redux/Zustand: users = [A, B]

Which one is correct? This is exactly the kind of problem good state architecture tries to prevent. A better approach is to let each tool own the type of state it’s designed to manage.

11. But What If I Need API Data in a Global Store?

There are cases where an application intentionally stores server-derived data in a global store — the data might be heavily transformed into application-specific state, or there may be architectural constraints requiring centralization.

The point isn’t “never put API data into Redux or Zustand.” The better rule is:

Don’t duplicate server state without a clear reason. If your global store owns it, know why. If TanStack Query owns it, let TanStack Query own it.

The problem is not the library. The problem is unclear ownership.

12. State Ownership Is More Important Than State Management

This is probably the most important idea in this entire topic. Before asking “should I use Redux or Zustand?” ask “who owns this state?”

Is the sidebar open?              → UI owns it
What users exist on the server? → Backend owns it
Who is the logged-in user? → Application needs to share it
What permissions does this user have? → Application-wide state
What did the user type into this input? → UI owns it

Once ownership is clear, choosing the tool becomes much easier.

13. A Practical Decision Tree

When you encounter a new piece of state, ask these questions:

Does the backend own this data? → Server state. Consider TanStack Query. Examples: users, orders, products, dashboard statistics, reports.

Is this state only about the current UI? → UI state. Consider useState / useReducer. Examples: modal visibility, selected tab, dropdown state, form input, sidebar visibility.

Do many unrelated components need this state? → Application state. Consider Zustand, Redux Toolkit, Context, or another global state manager. Examples: current user, permissions, workspace, global preferences, app-wide notifications.

14. Don’t Reach for a Global Store Just Because You Have One

This is a common pattern in large React applications. A developer sees a new requirement — “we need to store this value” — and the immediate instinct becomes: create a slice, create an action, create a reducer, dispatch, select.

But the actual requirement might have been:

const [isOpen, setIsOpen] = useState(false);

That’s it. The size of the project doesn’t mean every state variable should be global. A large application can still have lots of local state. In fact, good architecture often means keeping state as local as possible until there’s a real reason to share it.

15. Avoid the “Everything Is Global” Architecture

Imagine a store with user, dashboard, sidebar, modal, filters, forms, orders, products, notifications, API responses, loading states, and temporary UI values all mixed together. At some point, the global store becomes a dumping ground. The application technically works, but understanding state ownership becomes difficult — a developer changing a modal might need to inspect actions, reducers, selectors, middleware, components, and effects for something that could have been a local variable.

That’s a sign state boundaries aren’t clear.

16. A Better Dashboard Architecture

For our dashboard, a cleaner structure looks like:

Dashboard
│
├── UI State
│ ├── selectedTab
│ ├── filterModalOpen
│ └── sidebarOpen
│
├── Server State
│ ├── dashboardStats
│ ├── recentOrders
│ └── salesReport
│
└── Application State
├── currentUser
├── permissions
└── selectedWorkspace

Matched to tools: UI state → useState / useReducer. Server state → TanStack Query. Application state → Zustand or Redux Toolkit. Clear ownership reduces complexity.

17. State Management Is Not About Choosing One Library

A common question is “should I use Redux, Context, Zustand, or TanStack Query?” That’s the wrong starting point — there’s no requirement that one library manage everything. A real application can use several tools at once: useState for small local UI state, a global store for application state, TanStack Query for server state, and a form library for complex form state.

The goal isn’t to minimize the number of libraries. The goal is to give each type of state the right owner.

18. A Simple Rule to Use

Who owns the data?
|
+-------------+-------------+
| | |
v v v
Frontend Backend Shared App
| | |
v v v
UI State Server State Application State
| | |
v v v
useState TanStack Query Zustand / Redux Toolkit

It’s not a strict rule for every architecture, but it’s a useful starting point.

19. The Real Problem Is Usually Not the Library

When state management becomes complicated, developers often blame the library. But many state-management problems are actually state-classification problems.

  • Problem: Dashboard API data is stale. Wrong reaction: add another Redux action. Actual problem: server state is being treated as application state.
  • Problem: Modal state requires multiple global-store actions. Wrong reaction: create more abstractions. Actual problem: local UI state was moved into global state unnecessarily.
  • Problem: Two components have different copies of user data. Actual problem: there are multiple sources of truth.

The library isn’t always the problem. Sometimes the architecture is.

20. A Practical Checklist

Before adding a new state variable, ask:

Ownership — Who owns this data? Frontend, backend, or application? Scope — Does one component need it? A parent and its children? The entire application? Lifetime — Does it exist only while a component is mounted? Does it survive navigation? Does it need to be persisted? Source — Did this data come from an API? Can another user change it? Can it become stale? Tool — Can useState handle it? Should TanStack Query manage it? Does it actually need a global store? Would Context be enough?

These questions usually lead to a better decision than starting with the library.

Conclusion

UI state, server state, and application state may all be called “state,” but they behave very differently — and the important difference is ownership.

  • UI State → the frontend controls it.
  • Server State → the backend owns it.
  • Application State → multiple parts of the application need it.

Because these states have different characteristics, they shouldn’t automatically be managed the same way. For a typical React dashboard: UI state → useState / useReducer. Server state → TanStack Query. Application state → Zustand, Redux Toolkit, or a similar global state manager.

The biggest mistake isn’t choosing the “wrong” library. The bigger mistake is not knowing what kind of state you’re managing in the first place.

Before adding a reducer, creating a slice, or installing another state-management library, ask one simple question: who owns this state? Once you know the answer, choosing the right tool becomes much easier. Good React architecture isn’t about putting everything in one place — it’s about putting the right state in the right place.

Where has this bitten you — Redux turning into an API cache, duplicated sources of truth, or something else? Comment below — we’d love to hear how you’ve drawn these lines in your own projects.

Brought to you by Jayendra Jadeja from the Silversky Technology crew.
Curious what else we’re building? Explore more at silverskytechnology.com.

--

--

Silversky Technology
Silversky Technology

Written by Silversky Technology

Transforming Visionary Ideas into Cutting-Edge Solutions