Hox est une bibliothèque légère dédiée au partage d'état entre composants React, conçue pour offrir une alternative simple à des solutions plus complexes comme Redux. Son API, basée sur les Hooks React, réduit la courbe d'apprentissage et minimise le code boilerplate. Cet article couvre l'intégration complète de Hox dans un projet.
Installation
Prérequis : Node.js 14+, React 16.8+, TypeScript 4.1+ (recommandé). La bibliothèque s'installe via un gestionnaire de paquets.
# Pour npm
npm install hox
# Pour yarn
yarn add hox
# Pour pnpm
pnpm add hox
⚠️ Note : La version 2 de Hox introduit des changements d'API par rapport à la version 1. Pour une nouvelle utilisation, il est conseillé de commencer directement avec la v2.
Configuration initiale
Pour une intégration TypeScript optimale, ajustez les options du compilateur dans tsconfig.json :
{
"compilerOptions": {
"jsx": "react-jsx",
"module": "ESNext",
"target": "ES5",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
L'état global partagé nécessite que le composant racine de l'application soit enveloppé par un fournisseur de contexte spécifique.
import { HoxRoot } from 'hox';
import { createRoot } from 'react-dom/client';
import App from './App';
const container = document.getElementById('root');
const root = createContainer(container);
root.render(
<HoxRoot>
<App />
</HoxRoot>
);
Ce fournisseur (HoxRoot) est indispensable pour accéder à tout store global depuis l'application.
Définition des stores
Store global
Un store global est accessible depuis n'importe quel composant. Il convient aux données partagées à l'échelle de l'application.
// stores/authentification.ts
import { createGlobalStore } from 'hox';
import { useState } from 'react';
export const useAuthStore = createGlobalStore(() => {
const [session, setSession] = useState({ nom: '', connecte: false });
const ouvrirSession = (nomUtilisateur: string) => {
setSession({ nom: nomUtilisateur, connecte: true });
};
const fermerSession = () => {
setSession({ nom: '', connecte: false });
};
return { session, ouvrirSession, fermerSession };
});
Store local
Un store local est partagé uniquement entre les composants qui l'utilisent. Il permet un cloisonnement d'état.
// stores/panier.ts
import { createStore } from 'hox';
import { useState, useEffect } from 'react';
interface ArticlePanier {
id: string;
libelle: string;
prixUnitaire: number;
quantite: number;
}
export const usePanierStore = createStore(
() => {
const [contenu, setContenu] = useState<ArticlePanier[]>([]);
const [montantTotal, setMontantTotal] = useState(0);
useEffect(() => {
const total = contenu.reduce(
(accumulateur, article) => accumulateur + article.prixUnitaire * article.quantite,
0
);
setMontantTotal(total);
}, [contenu]);
const ajouterArticle = (article: Omit<ArticlePanier, 'quantite'>) => {
setContenu(ancienContenu => {
const articleExistant = ancienContenu.find(a => a.id === article.id);
if (articleExistant) {
return ancienContenu.map(a =>
a.id === article.id ? { ...a, quantite: a.quantite + 1 } : a
);
}
return [...ancienContenu, { ...article, quantite: 1 }];
});
};
return { contenu, montantTotal, ajouterArticle };
},
{
persistence: {
cle: 'donnees-panier',
stockage: localStorage
}
}
);
Options avancées de configuration
La fonction createStore accepte un objet de configuraton optionnel pour contrôler son comportement.
interface OptionsStore {
debounce?: number; // Délai d'anti-rebond en ms pour optimiser les mises à jour fréquentes
persistence?: {
cle: string;
stockage?: Storage;
expiration?: number; // Durée de vie en ms
serialiser?: (valeur: any) => string;
deserialiser?: (chaine: string) => any;
};
devtools?: {
nom?: string;
actif?: boolean;
};
}
Exemple d'utilisation pour un état mis à jour fréquemment :
export const useDonneesTempsReel = createStore(
() => {
// Logique de traitement des données en temps réel
},
{
debounce: 250, // Limite les re-rendus
devtools: { actif: process.env.NODE_ENV !== 'production' }
}
);
Dépannage courant
Conflit de versions : En cas d'avertissement sur les dépendances pairs, forcez la résolution dans package.json :
{
"resolutions": {
"@types/react": "^18.0.0"
}
}
Inférence de type inexacte : Spécifiez explicitement le type de retour du store.
interface StoreProfil {
donnees: { prenom: string; age: number };
mettreAJourAge: (nouvelAge: number) => void;
}
export const useProfilStore = createGlobalStore<StoreProfil>(() => {
// Implémentation...
});
Persistence défaillante : Vérifiez que la clé de stockage est définie, que le volume des données ne dépasse pas la limite du navigateur et que HoxRoot est bien placé à la racine de l'arborescence des composants.
Exemple intégral : Compteur partagé
// stores/compteur.ts
import { createGlobalStore } from 'hox';
import { useState } from 'react';
export const useCompteurStore = createGlobalStore(() => {
const [valeur, setValeur] = useState(0);
return {
valeur,
incrementer: () => setValeur(v => v + 1),
decrementer: () => setValeur(v => v - 1),
reinitialiser: () => setValeur(0)
};
});
// Composant Affichage.tsx
import { useCompteurStore } from '../stores/compteur';
function Affichage() {
const { valeur } = useCompteurStore();
return <span>{valeur}</span>;
}
// Composant Commandes.tsx
import { useCompteurStore } from '../stores/compteur';
function Commandes() {
const { incrementer, decrementer, reinitialiser } = useCompteurStore();
return (
<div>
<button onClick={decrementer}>-</button>
<button onClick={reinitialiser}>Réinit.</button>
<button onClick={incrementer}>+</button>
</div>
);
}
La philoosphie de conception de Hox repose sur le principe de moindre surprise, en s'alignant étroitement sur les Hooks React. Son mécanisme de collecte de dépendances automatique permet des mises à jour ciblées et performantes. Le code source, compact, offre une opportunité d'étudier une implémentation propre du partage d'état dans un écosystème React moderne.