React Integration
Build React applications with type-safe APSO backend integration.
Setup
Install Dependencies
npm install @apso/sdkCreate Client
// src/lib/apso.ts
import { createClient } from '@apso/sdk';
export const apso = createClient({
baseUrl: import.meta.env.VITE_API_URL,
});Custom Hooks
useQuery Hook
// src/hooks/useQuery.ts
import { useState, useEffect } from 'react';
export function useQuery<T>(
queryFn: () => Promise<T>,
deps: unknown[] = []
) {
const [data, setData] = useState<T | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
setLoading(true);
queryFn()
.then(setData)
.catch(setError)
.finally(() => setLoading(false));
}, deps);
return { data, loading, error };
}useMutation Hook
// src/hooks/useMutation.ts
import { useState } from 'react';
export function useMutation<T, V>(
mutationFn: (variables: V) => Promise<T>
) {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<Error | null>(null);
const mutate = async (variables: V) => {
setLoading(true);
setError(null);
try {
const result = await mutationFn(variables);
return result;
} catch (e) {
setError(e as Error);
throw e;
} finally {
setLoading(false);
}
};
return { mutate, loading, error };
}Component Examples
List Component
// src/components/ProjectList.tsx
import { apso } from '../lib/apso';
import { useQuery } from '../hooks/useQuery';
export function ProjectList() {
const { data: projects, loading, error } = useQuery(
() => apso.projects.findMany({
orderBy: { createdAt: 'desc' }
}),
[]
);
if (loading) return <div>Loading projects...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div className="space-y-4">
{projects?.map(project => (
<ProjectCard key={project.id} project={project} />
))}
</div>
);
}Create Form
// src/components/CreateProjectForm.tsx
import { useState } from 'react';
import { apso } from '../lib/apso';
import { useMutation } from '../hooks/useMutation';
export function CreateProjectForm({ onSuccess }: { onSuccess: () => void }) {
const [name, setName] = useState('');
const [description, setDescription] = useState('');
const { mutate, loading, error } = useMutation(
(data: { name: string; description?: string }) =>
apso.projects.create(data)
);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
await mutate({ name, description: description || undefined });
setName('');
setDescription('');
onSuccess();
};
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="name">Name</label>
<input
id="name"
value={name}
onChange={e => setName(e.target.value)}
required
/>
</div>
<div>
<label htmlFor="description">Description</label>
<textarea
id="description"
value={description}
onChange={e => setDescription(e.target.value)}
/>
</div>
{error && <div className="text-red-500">{error.message}</div>}
<button type="submit" disabled={loading}>
{loading ? 'Creating...' : 'Create Project'}
</button>
</form>
);
}Authentication Context
// src/contexts/AuthContext.tsx
import { createContext, useContext, useState, useEffect, ReactNode } from 'react';
import { apso } from '../lib/apso';
interface User {
id: string;
email: string;
name: string;
}
interface AuthContextType {
user: User | null;
login: (email: string, password: string) => Promise<void>;
logout: () => void;
loading: boolean;
}
const AuthContext = createContext<AuthContextType | null>(null);
export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
apso.setToken(token);
apso.auth.me()
.then(setUser)
.catch(() => localStorage.removeItem('token'))
.finally(() => setLoading(false));
} else {
setLoading(false);
}
}, []);
const login = async (email: string, password: string) => {
const { token, user } = await apso.auth.login({ email, password });
localStorage.setItem('token', token);
apso.setToken(token);
setUser(user);
};
const logout = () => {
localStorage.removeItem('token');
apso.setToken(null);
setUser(null);
};
return (
<AuthContext.Provider value={{ user, login, logout, loading }}>
{children}
</AuthContext.Provider>
);
}
export const useAuth = () => {
const context = useContext(AuthContext);
if (!context) throw new Error('useAuth must be used within AuthProvider');
return context;
};Protected Routes
// src/components/ProtectedRoute.tsx
import { Navigate } from 'react-router-dom';
import { useAuth } from '../contexts/AuthContext';
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { user, loading } = useAuth();
if (loading) return <div>Loading...</div>;
if (!user) return <Navigate to="/login" />;
return <>{children}</>;
}With React Query
For production apps, consider using React Query:
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { apso } from '../lib/apso';
export function useProjects() {
return useQuery({
queryKey: ['projects'],
queryFn: () => apso.projects.findMany(),
});
}
export function useCreateProject() {
const queryClient = useQueryClient();
return useMutation({
mutationFn: apso.projects.create,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['projects'] });
},
});
}Related
Last updated on