If you're coming from the classic Redux + thunks + connected components era (2017–2021), the ecosystem has shifted quite a bit. The biggest change is that state is no longer treated as one giant global store. Instead, teams try to keep state as close as possible to where it's needed.
For a large admin/dashboard app in 2026, a common architecture looks something like this:
Server/API State
├── TanStack Query (or framework equivalent)
│
Global Client State
├── Zustand / Redux Toolkit (small amount)
│
Feature State
├── React Context (where appropriate)
│
Local UI State
└── useState / useReducer
1. Separate server state from client state
This is probably the biggest mindset shift.
Older Redux apps often looked like:
API
↓
Redux store
↓
Components
Now it's more like:
API
↓
TanStack Query
↓
Components
Things like:
- users
- projects
- reports
- permissions
- notifications
- dashboard data
don't usually belong in Redux anymore. They belong in a server-state library that handles:
- caching
- background refetching
- optimistic updates
- deduplication
- loading/error states
- invalidation
Example:
const { data: users } = useQuery({
queryKey: ["users"],
queryFn: getUsers,
})
No reducers.
No actions.
No loading flags.
2. Keep global state small
Global state is now mostly things like:
{
currentOrganization,
currentWorkspace,
auth,
theme,
sidebarCollapsed,
featureFlags,
userPreferences
}
Notice these are UI/application concerns, not API resources.
Many teams use Zustand because it's much simpler than Redux.
const useAppStore = create((set) => ({
sidebarOpen: true,
toggleSidebar: () =>
set((s) => ({
sidebarOpen: !s.sidebarOpen
}))
}))
If your team already knows Redux, Redux Toolkit is still an excellent choice and remains widely used. The old boilerplate-heavy Redux patterns are largely gone.
3. Organize by features, not by file type
Older apps:
components/
reducers/
actions/
selectors/
pages/
Modern apps:
features/
users/
api.ts
hooks.ts
components/
routes.tsx
types.ts
billing/
...
reports/
...
Each feature owns:
- components
- API
- validation
- hooks
- types
- tests
This scales much better.
4. Components should mostly compose
Instead of giant smart/container components:
Dashboard
loads everything
manages everything
renders everything
Prefer:
DashboardPage
UserStatsCard
RevenueCard
ActivityFeed
RecentOrders
TeamMembers
Each card fetches its own data when reasonable.
For example:
DashboardPage
├── RevenueCard
├── UsersCard
├── AlertsCard
└── UsageChart
Each card:
const { data } = useRevenue()
instead of one page making 12 API calls and passing props down five levels.
5. Put business logic into hooks
Instead of:
function Dashboard() {
...
lots of logic
}
Prefer:
function Dashboard() {
const revenue = useRevenue()
const alerts = useAlerts()
const users = useUsers()
...
}
Even better:
const {
revenue,
loading,
refresh
} = useRevenueSummary()
Components become mostly JSX.
6. Build reusable primitives
Large admin apps usually develop layers like:
ui/
Button
Dialog
Table
Input
components/
UserTable
InvoiceCard
PermissionEditor
features/
billing/
users/
reports/
Avoid creating hundreds of one-off UI components.
7. Tables deserve their own architecture
Most dashboards are table-heavy.
A reusable table system often supports:
DataTable
columns
sorting
filtering
pagination
selection
bulk actions
virtualization
Rather than rewriting tables for every page.
8. Forms are their own feature
Most teams use:
instead of storing form state globally.
const form = useForm({
resolver: zodResolver(schema)
})
Form state almost never belongs in Redux.
9. Avoid prop drilling
Rather than:
Dashboard
↓
Layout
↓
Section
↓
Card
↓
Widget
passing
user
permissions
theme
organization
through every layer,
use:
- Context (for feature-specific shared state)
- Zustand/Redux (for app-wide shared state)
- TanStack Query (for server data)
10. Think in feature boundaries
A large dashboard is easier to evolve when each feature owns its own pieces:
billing/
API
hooks
components
routes
permissions
tests
users/
reports/
analytics/
A developer should be able to work almost entirely within one feature directory.
What I'd choose today
For a greenfield React admin application:
- Framework: Next.js (App Router) or React Router depending on your needs
- Server state: TanStack Query
- Global client state: Zustand (or Redux Toolkit if your team already uses Redux extensively)
- Forms: React Hook Form + Zod
- Tables: TanStack Table
- Data fetching: Feature-specific hooks (
useUsers, useInvoices, etc.)
- Styling: Tailwind CSS with a component library such as shadcn/ui, Mantine, or Chakra UI, depending on your design goals
When Redux is still the right choice
Redux hasn't disappeared. It's still a strong fit if you need:
- complex workflows spanning many unrelated parts of the app
- extensive time-travel debugging or action logging
- sophisticated middleware
- offline synchronization
- large teams that value explicit, predictable state transitions
- an existing mature Redux codebase
If you're starting fresh, though, many teams find that TanStack Query + a small Zustand (or Redux Toolkit) store covers most dashboard use cases with significantly less code than the classic "everything in Redux" architecture.
The overarching best practice today is to choose the right home for each kind of state: server data stays in a server-state library, ephemeral UI stays local, feature-specific shared state stays within the feature, and only truly application-wide concerns live in a global store. That separation tends to produce codebases that are easier to understand, test, and scale.