Skip to main content

State & Data

This document covers client-side data fetching, cache management, global state, and internationalization patterns used in the application.

TanStack Query (Data Fetching)​

Why TanStack Query​

  • Declarative data fetching and caching
  • Background revalidation and stale-while-revalidate semantics
  • Built-in support for pagination, infinite queries, and optimistic updates

Installation​

pnpm add @tanstack/react-query

Provider setup​

'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

export default function Providers({ children }: { children: React.ReactNode }) {
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
}

Example usage​

import { useQuery } from '@tanstack/react-query';

function useEmployees(page = 1) {
return useQuery(['employees', page], () =>
fetch(`/api/v1/employees?page=${page}`).then((r) => r.json()),
);
}

function EmployeesList() {
const { data, isLoading } = useEmployees(1);
if (isLoading) return <div>Loading...</div>;
return <EmployeesTable data={data.data} />;
}

Best practices​

  • Use query keys that reflect parameters
  • Use select to transform server responses
  • Keep cache times reasonable (staleTime, cacheTime)
  • Use prefetchQuery for route transitions

Zustand (Lightweight Global State)​

Why Zustand​

  • Minimal API and small bundle size
  • Works well for transient UI state and small global stores
  • Simple to use with React and server components via client boundaries

Installation​

pnpm add zustand

Example store​

import create from 'zustand';

type UIState = {
sidebarOpen: boolean;
toggleSidebar: () => void;
};

export const useUIStore = create<UIState>((set) => ({
sidebarOpen: false,
toggleSidebar: () => set((s) => ({ sidebarOpen: !s.sidebarOpen })),
}));

Usage​

'use client';
import { useUIStore } from '../stores/ui';

function SidebarToggle() {
const toggle = useUIStore((s) => s.toggleSidebar);
return <button onClick={toggle}>Toggle</button>;
}

Best practices​

  • Use Zustand for UI state (modals, sidebars, feature flags)
  • Keep domain state (entities) in TanStack Query cache or server
  • Don't overuse global stores for deeply nested component state

Internationalization with next-intl​

Why next-intl​

  • Designed to work with Next.js App Router
  • Supports static & dynamic messages, locale routing, and server usage

Installation​

pnpm add next-intl

Basic setup​

  • Create i18n messages under src/i18n/{locale}.json
  • Wrap root layout with NextIntlProvider or use route-based loading
// app/layout.tsx (server component)
import { NextIntlProvider } from 'next-intl';

export default async function RootLayout({ children, params }) {
const messages = (await import(`../i18n/${params.locale}.json`)).default;
return (
<html lang={params.locale}>
<body>
<NextIntlProvider messages={messages}>{children}</NextIntlProvider>
</body>
</html>
);
}

Usage in components​

'use client';
import { useTranslations } from 'next-intl';

export default function Greeting() {
const t = useTranslations('Home');
return <h1>{t('welcome')}</h1>;
}

Locale routing​

  • Use segment routes like app/[locale]/page.tsx for locale-aware routing
  • Provide a default locale and locale fallback strategy

Testing data & state​

  • Use mocked responses for TanStack Query in unit tests
  • Use isolated Zustand stores for component tests

Resources​