4.5 KiB
Executable File
4.5 KiB
Executable File
React & Frontend — Patterns Expert
React Hooks Patterns
Custom Hook: useApi
function useApi(url, options = {}) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const controller = new AbortController();
async function fetchData() {
try {
setLoading(true);
const res = await fetch(url, { ...options, signal: controller.signal });
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const json = await res.json();
setData(json);
} catch (err) {
if (err.name !== 'AbortError') setError(err);
} finally {
setLoading(false);
}
}
fetchData();
return () => controller.abort();
}, [url]);
return { data, loading, error };
}
Custom Hook: useDebounce
function useDebounce(value, delay = 500) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const timer = setTimeout(() => setDebouncedValue(value), delay);
return () => clearTimeout(timer);
}, [value, delay]);
return debouncedValue;
}
// Usage: recherche en temps réel
function SearchInput() {
const [query, setQuery] = useState('');
const debouncedQuery = useDebounce(query, 300);
const { data } = useApi(`/api/search?q=${debouncedQuery}`);
return <input value={query} onChange={e => setQuery(e.target.value)} />;
}
Custom Hook: useLocalStorage
function useLocalStorage(key, initialValue) {
const [storedValue, setStoredValue] = useState(() => {
try {
const item = window.localStorage.getItem(key);
return item ? JSON.parse(item) : initialValue;
} catch { return initialValue; }
});
const setValue = value => {
setStoredValue(value);
window.localStorage.setItem(key, JSON.stringify(value));
};
return [storedValue, setValue];
}
Performance Patterns
Memoization
// Composant lourd qui ne re-render que si props changent
const HeavyComponent = React.memo(({ data }) => {
// ... rendu coûteux
});
// Callback stable (pas recréé à chaque render)
const handleClick = useCallback((id) => {
setSelected(id);
}, []);
// Valeur calculée mise en cache
const sortedData = useMemo(() => {
return [...data].sort((a, b) => a.name.localeCompare(b.name));
}, [data]);
Lazy Loading
const Dashboard = React.lazy(() => import('./Dashboard'));
const Analytics = React.lazy(() => import('./Analytics'));
function App() {
return (
<Suspense fallback={<LoadingSpinner />}>
<Routes>
<Route path="/dashboard" element={<Dashboard />} />
<Route path="/analytics" element={<Analytics />} />
</Routes>
</Suspense>
);
}
CSS Architecture
Tailwind Utility-First (préféré pour WEVIA)
// Composant bouton avec variants
function Button({ variant = 'primary', size = 'md', children, ...props }) {
const base = 'inline-flex items-center justify-center font-medium rounded-lg transition-colors focus:outline-none focus:ring-2';
const variants = {
primary: 'bg-blue-600 text-white hover:bg-blue-700 focus:ring-blue-500',
secondary: 'bg-gray-200 text-gray-800 hover:bg-gray-300 focus:ring-gray-500',
danger: 'bg-red-600 text-white hover:bg-red-700 focus:ring-red-500',
ghost: 'bg-transparent text-gray-600 hover:bg-gray-100'
};
const sizes = {
sm: 'px-3 py-1.5 text-sm',
md: 'px-4 py-2 text-base',
lg: 'px-6 py-3 text-lg'
};
return (
<button className={`${base} ${variants[variant]} ${sizes[size]}`} {...props}>
{children}
</button>
);
}
State Management Patterns
Context + useReducer (alternative légère à Redux)
const AppContext = createContext();
function appReducer(state, action) {
switch (action.type) {
case 'SET_USER': return { ...state, user: action.payload };
case 'SET_THEME': return { ...state, theme: action.payload };
case 'ADD_NOTIFICATION': return { ...state, notifications: [...state.notifications, action.payload] };
default: return state;
}
}
function AppProvider({ children }) {
const [state, dispatch] = useReducer(appReducer, {
user: null, theme: 'light', notifications: []
});
return (
<AppContext.Provider value={{ state, dispatch }}>
{children}
</AppContext.Provider>
);
}
// Usage dans n'importe quel composant
function UserProfile() {
const { state, dispatch } = useContext(AppContext);
return <h1>{state.user?.name}</h1>;
}