Client-Side Hooks
Client-side hooks manage state and user interactions in Next.js applications, optimized for hydration and server-side rendering scenarios.
Overview
Intrig's client-side hooks provide reactive state management in the browser environment with:
- Hydration optimization for smooth server-to-client transitions
- Multiple hook variants for different use cases
- Type-safe state management with loading, error, and data states
- Real-time capabilities through Server-Sent Events
- Performance optimization with automatic caching and deduplication
Hook Types
Stateful Hooks
Stateful hooks provide comprehensive state management with loading, error, and data states, ideal for most interactive use cases.
// app/components/UserProfile.tsx
'use client';
import { useGetUser } from '@intrig/next/userApi/users/getUser/client';
import { useEffect } from 'react';
export function UserProfile({ userId }: { userId: string }) {
const [userState, getUser] = useGetUser();
useEffect(() => {
getUser({ id: userId });
}, [userId, getUser]);
// State includes: loading, error, data, lastFetched
const { loading, error, data, lastFetched } = userState;
if (loading && !data) {
return <UserProfileSkeleton />;
}
if (error) {
return (
<div className="error-state">
<p>Error loading user: {error.message}</p>
<button onClick={() => getUser({ id: userId })}>
Try Again
</button>
</div>
);
}
if (!data) {
return <div>No user found</div>;
}
return (
<div className="user-profile">
<img src={data.avatar} alt={`${data.name}'s avatar`} />
<h2>{data.name}</h2>
<p>{data.email}</p>
<small>Last updated: {lastFetched?.toLocaleString()}</small>
<button onClick={() => getUser({ id: userId })}>
Refresh
</button>
</div>
);
}
Stateless Hooks
Stateless hooks provide function-only access without built-in state management, giving you complete control over state handling.
// app/components/UserActions.tsx
'use client';
import { useGetUserAsync } from '@intrig/next/userApi/users/getUser/client';
import { useUpdateUserAsync } from '@intrig/next/userApi/users/updateUser/client';
import { useState } from 'react';
export function UserActions({ userId }: { userId: string }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const [getUser] = useGetUserAsync();
const [updateUser] = useUpdateUserAsync();
const handleFetchUser = async () => {
setLoading(true);
setError(null);
try {
const userData = await getUser({ id: userId });
setUser(userData);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
const handleUpdateUser = async (updates: Partial<User>) => {
if (!user) return;
setLoading(true);
setError(null);
try {
const updatedUser = await updateUser({ id: user.id, ...updates });
setUser(updatedUser);
} catch (err) {
setError(err as Error);
} finally {
setLoading(false);
}
};
return (
<div>
<button onClick={handleFetchUser} disabled={loading}>
{loading ? 'Loading...' : 'Load User'}
</button>
{user && (
<div>
<p>{user.name} - {user.email}</p>
<button
onClick={() => handleUpdateUser({ name: 'Updated Name' })}
disabled={loading}
>
Update Name
</button>
</div>
)}
{error && <p className="error">Error: {error.message}</p>}
</div>
);
}
Server-Sent Events (SSE) Hooks
SSE hooks enable real-time data streaming from the server for live updates.
// app/components/LiveUserData.tsx
'use client';
import { useGetUserSSE } from '@intrig/next/userApi/users/getUser/client';
import { useEffect } from 'react';
export function LiveUserData({ userId }: { userId: string }) {
const [state, startStream, stopStream] = useGetUserSSE();
useEffect(() => {
// Start streaming when component mounts
startStream({ id: userId });
// Cleanup on unmount
return () => stopStream();
}, [userId, startStream, stopStream]);
return (
<div className="live-user-data">
<div className="connection-status">
Status: {state.connected ? 'Connected' : 'Disconnected'}
{state.connecting && ' (Connecting...)'}
</div>
{state.error && (
<div className="error">
Connection error: {state.error.message}
<button onClick={() => startStream({ id: userId })}>
Reconnect
</button>
</div>
)}
{state.data && (
<div className="user-data">
<h3>Live User Data</h3>
<p>Name: {state.data.name}</p>
<p>Status: {state.data.status}</p>
<p>Last Activity: {state.data.lastActivity}</p>
</div>
)}
<div className="controls">
<button onClick={() => startStream({ id: userId })}>
Start Stream
</button>
<button onClick={stopStream}>
Stop Stream
</button>
</div>
</div>
);
}
Generated Hook Structure
Client hooks are generated in the following structure:
src/
├── [source]/ # e.g., userApi/
│ ├── [controller]/ # e.g., users/
│ │ └── [operationId]/ # e.g., getUser/
│ │ ├── client.ts # exports: useGetUser, useGetUserAsync
│ │ ├── useGetUser.ts # stateful hook
│ │ ├── useGetUserAsync.ts # stateless hook
│ │ └── GetUser.params.ts # shared parameter types
│ └── components/schemas/ # shared response/request types
Hydration Optimization
Client hooks are specifically optimized for Next.js hydration patterns to prevent mismatches and ensure smooth transitions.
Preventing Hydration Mismatches
// app/components/HydratedUserProfile.tsx
'use client';
import { useGetUser } from '@intrig/next/userApi/users/getUser/client';
import { useState, useEffect } from 'react';
export function HydratedUserProfile({
initialUser,
userId
}: {
initialUser?: User;
userId: string;
}) {
const [isClient, setIsClient] = useState(false);
const [userState, getUser] = useGetUser();
useEffect(() => {
setIsClient(true);
// Only fetch if no initial data provided
if (!initialUser) {
getUser({ id: userId });
}
}, [userId, getUser, initialUser]);
// Prevent hydration mismatch by using consistent rendering
if (!isClient) {
return initialUser ? (
<UserDisplay user={initialUser} />
) : (
<UserProfileSkeleton />
);
}
// After hydration, use client state
if (userState.loading && !userState.data && !initialUser) {
return <UserProfileSkeleton />;
}
if (userState.error) {
return <ErrorDisplay error={userState.error} />;
}
// Use client data if available, fallback to initial data
const user = userState.data || initialUser;
return user ? <UserDisplay user={user} /> : <div>No user found</div>;
}
Basic Hook Imports
// From consolidated client export
import { useGetUser, useGetUserAsync } from '@intrig/next/userApi/users/getUser/client';
// Or directly from specific hook files
import { useGetUser } from '@intrig/next/userApi/users/getUser/useGetUser';
import { useGetUserAsync } from '@intrig/next/userApi/users/getUser/useGetUserAsync';
Type Imports
// Parameter types (shared with server)
import type { GetUserParams } from '@intrig/next/userApi/users/getUser/GetUser.params';
// Response types
import type { User } from '@intrig/next/userApi/components/schemas/User';
import type { UpdateUserRequest } from '@intrig/next/userApi/components/schemas/UpdateUserRequest';
Multiple Hook Imports
// Multiple operations from different controllers
import { useGetUser } from '@intrig/next/userApi/users/getUser/client';
import { useCreateUser } from '@intrig/next/userApi/users/createUser/client';
import { useUpdateUser } from '@intrig/next/userApi/users/updateUser/client';
Client-side hooks provide powerful, flexible state management capabilities that work seamlessly with Next.js's server-side rendering and hydration patterns, enabling rich interactive user experiences.