perf(media): memoize toast context and improve loading state UX
- wrap ToastContext value in useMemo to prevent unnecessary re-renders - show skeleton only on initial load, use opacity transition for subsequent fetches - destructure toastError from toast context to stabilize fetchItems dependency
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { createContext, useContext, useState, useCallback } from 'react';
|
import React, { createContext, useContext, useState, useCallback, useMemo } from 'react';
|
||||||
|
|
||||||
const ToastContext = createContext();
|
const ToastContext = createContext();
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ export const ToastProvider = ({ children }) => {
|
|||||||
});
|
});
|
||||||
}, [addToast]);
|
}, [addToast]);
|
||||||
|
|
||||||
const value = {
|
const value = useMemo(() => ({
|
||||||
toasts,
|
toasts,
|
||||||
addToast,
|
addToast,
|
||||||
removeToast,
|
removeToast,
|
||||||
@@ -97,7 +97,7 @@ export const ToastProvider = ({ children }) => {
|
|||||||
error,
|
error,
|
||||||
warning,
|
warning,
|
||||||
info,
|
info,
|
||||||
};
|
}), [toasts, addToast, removeToast, clearAllToasts, success, error, warning, info]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ToastContext.Provider value={value}>
|
<ToastContext.Provider value={value}>
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function MediaGrid({
|
|||||||
selectedIds = new Set(),
|
selectedIds = new Set(),
|
||||||
emptyMessage = 'Aucun média',
|
emptyMessage = 'Aucun média',
|
||||||
}) {
|
}) {
|
||||||
if (loading) {
|
if (loading && items.length === 0) {
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
|
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
|
||||||
{Array.from({ length: 12 }).map((_, i) => (
|
{Array.from({ length: 12 }).map((_, i) => (
|
||||||
@@ -59,7 +59,7 @@ export default function MediaGrid({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3">
|
<div className={`grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 lg:grid-cols-6 gap-3 transition-opacity ${loading ? 'opacity-60' : ''}`}>
|
||||||
{items.map(media => {
|
{items.map(media => {
|
||||||
const Icon = iconForMime(media.mime_type);
|
const Icon = iconForMime(media.mime_type);
|
||||||
const isSelected = selectedIds.has(media.id);
|
const isSelected = selectedIds.has(media.id);
|
||||||
|
|||||||
@@ -204,6 +204,7 @@ const MediaPage = ({ user }) => {
|
|||||||
const [filters, setFilters] = useState({ search: '', kind: '', visibility: '' });
|
const [filters, setFilters] = useState({ search: '', kind: '', visibility: '' });
|
||||||
const [selected, setSelected] = useState(null);
|
const [selected, setSelected] = useState(null);
|
||||||
|
|
||||||
|
const { error: toastError } = toast;
|
||||||
const fetchItems = useCallback(async () => {
|
const fetchItems = useCallback(async () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
@@ -217,11 +218,11 @@ const MediaPage = ({ user }) => {
|
|||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
setItems(data.media || []);
|
setItems(data.media || []);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error(err.message || 'Échec du chargement');
|
toastError(err.message || 'Échec du chargement');
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
}, [filters, toast]);
|
}, [filters, toastError]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchItems();
|
fetchItems();
|
||||||
|
|||||||
Reference in New Issue
Block a user