@andy-defer/react-client-kit
v1.0.4
Published
React client kit for Authentication Kit, Nemesis, and Laravel Actions
Maintainers
Readme
React Client Kit
Client HTTP et authentification pour React avec TypeScript
📋 Table des matières
- Introduction
- Installation
- Philosophie
- Démarrage rapide
- Hooks
- Core
- Types
- Exemples
- Performance
- Compatibilité
- Contribuer
- Licence
Introduction
React Client Kit est une bibliothèque qui fournit des hooks React typés pour la communication avec des APIs REST, avec un focus sur l'authentification et la gestion des tokens.
Philosophie
- TypeScript-first : Typage complet pour une expérience de développement fluide
- Headless : Pas de composants UI, seulement des hooks et des utilitaires
- Flexible : Support de multiples méthodes d'authentification (email, OTP, etc.)
- Configurable : Endpoints personnalisables pour s'adapter à votre API
- Testable : Injection de dépendances pour faciliter les tests
Cas d'usage
- 🔐 Authentification email/password
- 📡 Appels API typés
- 🎯 Gestion automatique des tokens
- 🔄 Réinitialisation de mot de passe
- ✅ Vérification d'email
Installation
# Avec npm
npm install @andy-defer/react-client-kit
# Avec yarn
yarn add @andy-defer/react-client-kit
# Avec pnpm
pnpm add @andy-defer/react-client-kitDépendances
Le package utilise les bibliothèques suivantes en peerDependencies :
{
"react": "^18.2.0",
"react-dom": "^18.2.0",
"axios": "^1.6.0"
}Philosophie
Pourquoi ce package ?
| Problème | Solution | |----------|----------| | Réécrire les mêmes appels API dans chaque projet | ✅ Hooks React réutilisables | | Gestion manuelle du token en localStorage | ✅ Gestion automatique | | Duplication des types TypeScript | ✅ Types partagés et générés | | Configuration répétée d'Axios | ✅ Client préconfiguré | | Pas de typage pour les erreurs API | ✅ Erreurs typées | | Chaque projet gère les tokens différemment | ✅ Standardisation |
Démarrage rapide
Exemple minimal
import React from 'react';
import { useClientKit } from '@andy-defer/react-client-kit';
function App() {
const { auth, actions } = useClientKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
});
const handleLogin = async () => {
const result = await auth.login('[email protected]', 'password123');
if (result.success) {
console.log('Connecté !', result.data);
}
};
const fetchUsers = async () => {
const users = await actions.get('/users');
console.log(users);
};
return (
<div>
{auth.isAuthenticated ? (
<>
<p>Bonjour {auth.user?.name}</p>
<button onClick={auth.logout}>Déconnexion</button>
<button onClick={fetchUsers}>Charger les utilisateurs</button>
</>
) : (
<button onClick={handleLogin}>Connexion</button>
)}
</div>
);
}
export default App;Hooks
useClientKit
Hook combiné qui fournit à la fois l'authentification et le client HTTP.
import { useClientKit } from '@andy-defer/react-client-kit';
const { auth, actions } = useClientKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
tokenStorage: 'localStorage',
tokenKey: 'my_app_token',
onTokenChange: (token) => {
console.log('Token changé:', token ? 'présent' : 'absent');
},
});Configuration
| Prop | Type | Requis | Défaut | Description |
|------|------|--------|--------|-------------|
| baseURL | string | ✅ | - | URL de base de l'API |
| modelType | string | ❌ | - | Type du modèle pour l'authentification |
| tokenStorage | 'localStorage' \| 'sessionStorage' | ❌ | 'localStorage' | Type de stockage du token |
| tokenKey | string | ❌ | 'client_kit_token' | Clé de stockage du token |
| onTokenChange | (token: string \| null) => void | ❌ | - | Callback lors du changement de token |
Retour
| Propriété | Type | Description |
|-----------|------|-------------|
| auth | UseMailAuthenticationKitReturn<T> | État et méthodes d'authentification |
| actions | UseActionClientReturn | Méthodes HTTP |
useMailAuthenticationKit
Hook d'authentification par email.
import { useMailAuthenticationKit } from '@andy-defer/react-client-kit';
const auth = useMailAuthenticationKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
});Méthodes
register(data: RegisterRequest): Promise<ApiResult<AuthRegisteredData & { auth: T }>>
Inscription d'un nouvel utilisateur.
const result = await auth.register({
model_type: 'App\\Models\\User',
name: 'John Doe',
email: '[email protected]',
password: 'Password123!',
password_confirmation: 'Password123!',
with_token: true,
});login(email: string, password: string): Promise<ApiResult<AuthLoginData & { auth: T }>>
Connexion d'un utilisateur existant.
const result = await auth.login('[email protected]', 'password123');logout(): Promise<ApiResult<void>>
Déconnexion de l'utilisateur courant.
await auth.logout();forgotPassword(email: string): Promise<ApiResult<PasswordResetLinkSentData>>
Demande de réinitialisation de mot de passe.
const result = await auth.forgotPassword('[email protected]');resetPassword(data: ResetPasswordRequest): Promise<ApiResult<PasswordResetSuccessData>>
Réinitialisation du mot de passe avec OTP.
const result = await auth.resetPassword({
model_type: 'App\\Models\\User',
email: '[email protected]',
token: '123456',
password: 'NewPassword123!',
password_confirmation: 'NewPassword123!',
});verifyEmail(data: VerifyEmailRequest): Promise<ApiResult<EmailVerifiedData>>
Vérification d'email avec OTP.
const result = await auth.verifyEmail({
model_type: 'App\\Models\\User',
email: '[email protected]',
token: '123456',
});sendVerificationOTP(authId: number): Promise<ApiResult<EmailVerificationSentData>>
Envoi d'un OTP de vérification.
const result = await auth.sendVerificationOTP(1);resendVerificationOTP(authId: number): Promise<ApiResult<EmailVerificationResentData>>
Renvoi d'un OTP de vérification.
const result = await auth.resendVerificationOTP(1);États
| Propriété | Type | Description |
|-----------|------|-------------|
| token | string \| null | Token d'authentification actuel |
| user | T \| null | Données de l'utilisateur authentifié |
| isAuthenticated | boolean | État d'authentification |
| isLoading | boolean | Indique si une requête est en cours |
| error | ErrorResponseData \| null | Dernière erreur |
useActionClient
Hook pour les appels HTTP typés avec gestion automatique du token.
import { useActionClient } from '@andy-defer/react-client-kit';
const actions = useActionClient({
baseURL: 'https://api.example.com',
token: 'my-token',
});Méthodes
get<T>(path: string, config?: ApiClientRequestConfig): Promise<T>
Requête GET typée.
const user = await actions.get<User>('/users/1');post<T, R>(path: string, data?: T, config?: ApiClientRequestConfig): Promise<R>
Requête POST typée.
// post<RequestType, ResponseType>
const user = await actions.post<CreateUserRequest, UserResponse>(
'/users',
{ name: 'John', email: '[email protected]' }
);put<T, R>(path: string, data?: T, config?: ApiClientRequestConfig): Promise<R>
Requête PUT typée.
const user = await actions.put<UpdateUserRequest, UserResponse>(
'/users/1',
{ name: 'John Updated' }
);patch<T, R>(path: string, data?: T, config?: ApiClientRequestConfig): Promise<R>
Requête PATCH typée.
const user = await actions.patch<Partial<UpdateUserRequest>, UserResponse>(
'/users/1',
{ email: '[email protected]' }
);delete<T>(path: string, config?: ApiClientRequestConfig): Promise<T>
Requête DELETE typée.
await actions.delete('/users/1');request<T>(config: ApiClientRequestConfig): Promise<T>
Requête personnalisée avec contrôle total.
const data = await actions.request<{ id: number }>({
method: 'POST',
url: '/custom',
data: { foo: 'bar' },
});Core
ApiClient
Client HTTP avec gestion automatique des tokens.
import { ApiClient } from '@andy-defer/react-client-kit';
const client = new ApiClient('https://api.example.com', 'auth_token', 'localStorage');
client.setToken('my-jwt-token');
const user = await client.get<User>('/users/1');Méthodes
| Méthode | Description |
|---------|-------------|
| getToken(): string \| null | Récupère le token actuel |
| setToken(token: string \| null): void | Stocke ou supprime le token |
| get<T>(path, config): Promise<T> | Requête GET |
| post<T>(path, data, config): Promise<T> | Requête POST |
| put<T>(path, data, config): Promise<T> | Requête PUT |
| patch<T>(path, data, config): Promise<T> | Requête PATCH |
| delete<T>(path, config): Promise<T> | Requête DELETE |
| request<T>(config): Promise<T> | Requête personnalisée |
| getAxiosInstance(): AxiosInstance | Instance Axios sous-jacente |
TokenStorage
Gestionnaire de stockage des tokens.
import { TokenStorage } from '@andy-defer/react-client-kit';
const storage = new TokenStorage('localStorage');
storage.set('auth_token', 'my-token');
const token = storage.get('auth_token');Méthodes
| Méthode | Description |
|---------|-------------|
| get(key: string): string \| null | Récupère une valeur |
| set(key: string, value: string): void | Stocke une valeur |
| remove(key: string): void | Supprime une valeur |
Types
Request Types
interface RegisterRequest {
model_type: string;
with_token?: boolean;
name: string;
email: string;
password: string;
password_confirmation: string;
}
interface LoginRequest {
model_type: string;
email: string;
password: string;
}
interface ResetPasswordRequest {
model_type: string;
email: string;
token: string;
password: string;
password_confirmation: string;
}
interface VerifyEmailRequest {
model_type: string;
email: string;
token: string;
}Response Types
interface ErrorResponseData {
message: string;
status: number;
errorCode?: string;
errors?: Record<string, string[]>;
}
interface AuthLoginData {
message: string;
auth: Record<string, unknown>;
token: string;
}
interface AuthRegisteredData {
message: string;
auth: Record<string, unknown>;
token?: string;
}Api Result
interface ApiResult<T> {
success: boolean;
data?: T;
error?: ErrorResponseData;
}Endpoints
interface MailAuthEndpoints {
register?: string;
login?: string;
forgotPassword?: string;
resetPassword?: string;
verifyEmail?: string;
sendVerification?: string;
resendVerification?: string;
logout?: string;
}
// Endpoints par défaut
const DEFAULT_ENDPOINTS = {
register: '/register',
login: '/login',
forgotPassword: '/forgot-password',
resetPassword: '/reset-password',
verifyEmail: '/email/verify',
sendVerification: '/email/verification',
resendVerification: '/email/resend',
logout: '/logout',
};Exemples
Exemple 1 : Formulaire de connexion
import React, { useState } from 'react';
import { useClientKit } from '@andy-defer/react-client-kit';
function LoginForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState<string | null>(null);
const { auth } = useClientKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
const result = await auth.login(email, password);
if (result.success) {
console.log('Connecté !');
} else {
setError(result.error?.message || 'Erreur de connexion');
}
};
if (auth.isLoading) {
return <div>Chargement...</div>;
}
if (auth.isAuthenticated) {
return <div>Bienvenue {auth.user?.name}</div>;
}
return (
<form onSubmit={handleSubmit}>
{error && <div style={{ color: 'red' }}>{error}</div>}
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
/>
<input
type="password"
placeholder="Mot de passe"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
/>
<button type="submit">Se connecter</button>
</form>
);
}Exemple 2 : Formulaire d'inscription
import React, { useState } from 'react';
import { useClientKit } from '@andy-defer/react-client-kit';
function RegisterForm() {
const [formData, setFormData] = useState({
name: '',
email: '',
password: '',
password_confirmation: '',
});
const { auth } = useClientKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
});
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
const result = await auth.register({
model_type: 'App\\Models\\User',
...formData,
with_token: true,
});
if (result.success) {
console.log('Inscription réussie !');
}
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="Nom"
value={formData.name}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
required
/>
<input
type="email"
placeholder="Email"
value={formData.email}
onChange={(e) => setFormData({ ...formData, email: e.target.value })}
required
/>
<input
type="password"
placeholder="Mot de passe"
value={formData.password}
onChange={(e) => setFormData({ ...formData, password: e.target.value })}
required
/>
<input
type="password"
placeholder="Confirmation"
value={formData.password_confirmation}
onChange={(e) => setFormData({ ...formData, password_confirmation: e.target.value })}
required
/>
<button type="submit" disabled={auth.isLoading}>
{auth.isLoading ? 'Inscription...' : "S'inscrire"}
</button>
</form>
);
}Exemple 3 : Réinitialisation de mot de passe
import React, { useState } from 'react';
import { useClientKit } from '@andy-defer/react-client-kit';
function ResetPassword() {
const [email, setEmail] = useState('');
const [otp, setOtp] = useState('');
const [password, setPassword] = useState('');
const [step, setStep] = useState<'request' | 'reset'>('request');
const { auth } = useClientKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
});
const handleRequestReset = async () => {
const result = await auth.forgotPassword(email);
if (result.success) {
setStep('reset');
}
};
const handleReset = async () => {
const result = await auth.resetPassword({
model_type: 'App\\Models\\User',
email,
token: otp,
password,
password_confirmation: password,
});
if (result.success) {
alert('Mot de passe réinitialisé !');
}
};
return (
<div>
{step === 'request' ? (
<div>
<input
type="email"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<button onClick={handleRequestReset} disabled={auth.isLoading}>
{auth.isLoading ? 'Envoi...' : 'Envoyer l\'OTP'}
</button>
</div>
) : (
<div>
<input
type="text"
placeholder="Code OTP"
value={otp}
onChange={(e) => setOtp(e.target.value)}
/>
<input
type="password"
placeholder="Nouveau mot de passe"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button onClick={handleReset} disabled={auth.isLoading}>
{auth.isLoading ? 'Réinitialisation...' : 'Réinitialiser'}
</button>
</div>
)}
</div>
);
}Exemple 4 : Appel API protégé
import React, { useEffect, useState } from 'react';
import { useClientKit } from '@andy-defer/react-client-kit';
interface User {
id: number;
name: string;
email: string;
}
function ProtectedData() {
const [users, setUsers] = useState<User[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const { auth, actions } = useClientKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
});
useEffect(() => {
if (auth.isAuthenticated) {
fetchUsers();
}
}, [auth.isAuthenticated]);
const fetchUsers = async () => {
setLoading(true);
setError(null);
try {
const data = await actions.get<User[]>('/users');
setUsers(data);
} catch (err: any) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (!auth.isAuthenticated) {
return <div>Veuillez vous connecter</div>;
}
if (loading) return <div>Chargement...</div>;
if (error) return <div style={{ color: 'red' }}>{error}</div>;
return (
<ul>
{users.map((user) => (
<li key={user.id}>
{user.name} ({user.email})
</li>
))}
</ul>
);
}Exemple 5 : Endpoints personnalisés
import { useClientKit } from '@andy-defer/react-client-kit';
function App() {
const { auth } = useClientKit({
baseURL: 'https://api.example.com',
modelType: 'App\\Models\\User',
endpoints: {
register: '/auth/register',
login: '/auth/login',
logout: '/auth/logout',
forgotPassword: '/auth/password/email',
resetPassword: '/auth/password/reset',
verifyEmail: '/auth/email/verify',
sendVerification: '/auth/email/verification',
resendVerification: '/auth/email/resend',
},
});
// Les méthodes utilisent maintenant les endpoints personnalisés
const handleLogin = async () => {
await auth.login('[email protected]', 'password123');
};
return <button onClick={handleLogin}>Connexion</button>;
}Performance
Optimisation
- Mémoïsation : Les clients sont mémoïsés pour éviter les re-créations
- Lazy initialization : Le token est chargé uniquement au besoin
- Pas de re-rendus inutiles : Les hooks sont optimisés pour minimiser les re-rendus
Recommandations
| Cas d'usage | Recommandation |
|-------------|----------------|
| Appel API simple | useActionClient directement |
| App complète | useClientKit pour tout avoir |
| Tests unitaires | Utiliser factory pour injecter des mocks |
Compatibilité
| Environnement | Support | |---------------|---------| | React | 18.0+ | | TypeScript | 5.0+ | | Chrome | 60+ | | Firefox | 55+ | | Safari | 12+ | | Edge | 79+ | | Node.js | 18+ (pour les builds) |
Contribuer
Les contributions sont les bienvenues !
- Forkez le projet
- Créez votre branche (
git checkout -b feature/amazing-feature) - Committez vos changements (
git commit -m 'Add some amazing feature') - Poussez vers la branche (
git push origin feature/amazing-feature) - Ouvrez une Pull Request
Licence
Ce projet est sous licence MIT. Voir le fichier LICENSE pour plus d'informations.
