Sidebar Manager Pattern
Pattern d'architecture pour gérer les sidebars dans une application React avec un gestionnaire centralisé.
Problème
Dans une application complexe, gérer plusieurs sidebars peut devenir chaotique :
- État dispersé dans différents composants
- Logique de fermeture/ouverture dupliquée
- Difficile de maintenir une seule sidebar ouverte à la fois
Solution
Un Sidebar Manager centralisé avec des providers dédiés pour chaque sidebar.
Architecture
src/
├── features/
│ └── sidebar/
│ ├── SidebarManager.tsx
│ ├── SidebarProvider.tsx
│ ├── hooks/
│ │ └── useSidebar.ts
│ └── sidebars/
│ ├── CommentSidebar/
│ │ ├── CommentSidebarProvider.tsx
│ │ ├── useCommentSidebar.ts
│ │ └── CommentSidebar.tsx
│ └── SettingsSidebar/
│ ├── SettingsSidebarProvider.tsx
│ ├── useSettingsSidebar.ts
│ └── SettingsSidebar.tsx
Implémentation
1. SidebarManager
Le gestionnaire central qui track quelle sidebar est ouverte :
import { createContext, useContext, useState, ReactNode } from 'react';
type SidebarType = 'comments' | 'settings' | null;
interface SidebarManagerContextValue {
activeSidebar: SidebarType;
openSidebar: (sidebar: SidebarType) => void;
closeSidebar: () => void;
}
const SidebarManagerContext = createContext<SidebarManagerContextValue | undefined>(undefined);
export function SidebarManagerProvider({ children }: { children: ReactNode }) {
const [activeSidebar, setActiveSidebar] = useState<SidebarType>(null);
const openSidebar = (sidebar: SidebarType) => {
setActiveSidebar(sidebar);
};
const closeSidebar = () => {
setActiveSidebar(null);
};
return (
<SidebarManagerContext.Provider value={{ activeSidebar, openSidebar, closeSidebar }}>
{children}
</SidebarManagerContext.Provider>
);
}
export function useSidebarManager() {
const context = useContext(SidebarManagerContext);
if (!context) {
throw new Error('useSidebarManager must be used within SidebarManagerProvider');
}
return context;
}
2. Provider spécifique (exemple: Comments)
import { createContext, useContext, ReactNode } from 'react';
import { useSidebarManager } from '../SidebarManager';
interface CommentSidebarContextValue {
isOpen: boolean;
open: () => void;
close: () => void;
// Logique métier spécifique
selectedCommentId: string | null;
selectComment: (id: string) => void;
}
const CommentSidebarContext = createContext<CommentSidebarContextValue | undefined>(undefined);
export function CommentSidebarProvider({ children }: { children: ReactNode }) {
const { activeSidebar, openSidebar, closeSidebar } = useSidebarManager();
const [selectedCommentId, setSelectedCommentId] = useState<string | null>(null);
const isOpen = activeSidebar === 'comments';
const open = () => {
openSidebar('comments');
};
const close = () => {
closeSidebar();
setSelectedCommentId(null); // Cleanup
};
const selectComment = (id: string) => {
setSelectedCommentId(id);
open();
};
return (
<CommentSidebarContext.Provider
value={{ isOpen, open, close, selectedCommentId, selectComment }}
>
{children}
</CommentSidebarContext.Provider>
);
}
export function useCommentSidebar() {
const context = useContext(CommentSidebarContext);
if (!context) {
throw new Error('useCommentSidebar must be used within CommentSidebarProvider');
}
return context;
}
3. Composant Sidebar
import { useCommentSidebar } from './CommentSidebarProvider';
export function CommentSidebar() {
const { isOpen, close, selectedCommentId } = useCommentSidebar();
if (!isOpen) return null;
return (
<div className="sidebar">
<button onClick={close}>Close</button>
<div>
{selectedCommentId ? (
<CommentDetail id={selectedCommentId} />
) : (
<CommentList />
)}
</div>
</div>
);
}
4. Utilisation dans l'app
function App() {
return (
<SidebarManagerProvider>
<CommentSidebarProvider>
<SettingsSidebarProvider>
<MainContent />
<CommentSidebar />
<SettingsSidebar />
</SettingsSidebarProvider>
</CommentSidebarProvider>
</SidebarManagerProvider>
);
}
function MainContent() {
const { selectComment } = useCommentSidebar();
const { open: openSettings } = useSettingsSidebar();
return (
<div>
<button onClick={() => selectComment('comment-123')}>
View Comment
</button>
<button onClick={openSettings}>
Settings
</button>
</div>
);
}
Avantages
✅ Un seul point de vérité : Le manager sait quelle sidebar est ouverte
✅ Isolation : Chaque sidebar a sa propre logique métier
✅ Fermeture automatique : Ouvrir une sidebar ferme automatiquement l'autre
✅ Type-safe : TypeScript garantit l'utilisation correcte
✅ Testable : Chaque provider est testable indépendamment
Extensions possibles
- Ajouter des animations de transition
- Supporter plusieurs sidebars ouvertes simultanément (avec un array)
- Historique de navigation entre sidebars
- Persister l'état dans localStorage