Skip to main content

Client Components

Overview​

Client Components are React components that run in the browser. They're marked with "use client" and can use React hooks, event handlers, and browser APIs.

When to Use Client Components​

  • ✅ Interactive UI (buttons, forms, modals)
  • ✅ Browser APIs (localStorage, window)
  • ✅ React hooks (useState, useEffect)
  • ✅ Event handlers (onClick, onChange)
  • ✅ Real-time updates
  • ✅ Client-side data fetching

Implementation Pattern​

'use client';
import { useState, useEffect } from 'react';
import { createClient } from '@/lib/supabase/client';

export function InteractiveComponent() {
const [data, setData] = useState([]);

useEffect(() => {
// Client-side data fetching
fetchData();
}, []);

return <div>{/* Interactive UI */}</div>;
}

Client-Side Data Fetching Patterns​

1. Table Data Fetching Hook​

The application uses a custom hook useFetchTableData for client-side table data:

// components/tables/common/useFetchTableData.tsx
'use client';
import { createClient } from "@/lib/supabase/client";

export function useFetchTableData({ config, campaignId, tableState }) {
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);

const fetchData = useCallback(async () => {
setIsLoading(true);
const supabase = createClient(); // Browser client

let query = supabase
.from(config.tableName)
.select(config.selectQuery, { count: "exact" });

// Apply filters, sorting, pagination
query = applyFilters(query, tableState);

const { data, error, count } = await query;
setData(data);
setIsLoading(false);
}, [tableState]);

useEffect(() => {
fetchData();
}, [fetchData]);

return { data, isLoading, refetch: fetchData };
}

Features:

  • ✅ Client-side caching with useRef
  • ✅ Automatic refetch on filter/sort/pagination changes
  • ✅ Loading and error states
  • ✅ Pagination support

2. Voter Search Hook​

// hooks/add-signatures/useESValidation.tsx
'use client';
import { useState } from 'react';

export function useESValidation() {
const [results, setResults] = useState([]);
const [isSearching, setIsSearching] = useState(false);

const searchVoter = async (searchParams) => {
setIsSearching(true);
try {
const response = await fetch('/api/essearch', {
method: 'POST',
body: JSON.stringify({ params: searchParams }),
});
const data = await response.json();
setResults(data.persons);
} finally {
setIsSearching(false);
}
};

return { results, isSearching, searchVoter };
}

Hybrid Pattern: Server + Client​

A common pattern is Server Component fetching initial data, then Client Component handling interactions:

// Server Component (page.tsx)
export default async function Page({ params }) {
const initialData = await getPetitionById(params.petitionId);

return (
<PetitionContextProvider initialValues={initialData.data}>
{/* Client Component receives server-fetched data */}
<SignatureFlow result={initialData} />
</PetitionContextProvider>
);
}

// Client Component (signatureFlow.tsx)
'use client';
export function SignatureFlow({ result }) {
const [step, setStep] = useState(1);
// Uses server-fetched data + client-side state
return <div>{/* Interactive workflow */}</div>;
}