Skip to Content
🚀 APSO is now in public beta. Get started →

React Integration

Build React applications with type-safe APSO backend integration.

Setup

Install Dependencies

npm install @apso/sdk

Create 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'] }); }, }); }
Last updated on