Design Pattern
SOLID

Single Responsibility Principle

Une classe ou fonction ne doit avoir qu'une seule raison de changer

Principe

Une classe (ou fonction, ou module) ne doit avoir qu'une seule raison de changer. Elle n'est responsable que d'une seule chose.

"Raison de changer" est le signal d'alarme : si modifier la logique de fetch oblige à toucher au même fichier que la logique d'affichage, il y a un problème.

Violation classique

// Ce composant a trois raisons de changer
function UserProfile({ userId }: { userId: string }) {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  // Raison 1 : la stratégie de fetching change
  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        // Raison 2 : les règles de transformation changent
        const formatted = {
          ...data,
          fullName: `${data.firstName} ${data.lastName}`,
          age: new Date().getFullYear() - new Date(data.birthDate).getFullYear(),
        };
        setUser(formatted);
        setLoading(false);
      });
  }, [userId]);

  // Raison 3 : le rendu visuel change
  if (loading) return <div>Loading...</div>;
  return (
    <div>
      <h1>{user?.fullName}</h1>
      <p>{user?.age} ans</p>
    </div>
  );
}

Application du SRP

// Responsabilité 1 : transformation des données (pure function)
function formatUser(data: RawUser): FormattedUser {
  return {
    ...data,
    fullName: `${data.firstName} ${data.lastName}`,
    age: new Date().getFullYear() - new Date(data.birthDate).getFullYear(),
  };
}

// Responsabilité 2 : fetching et état local (custom hook)
function useUser(userId: string) {
  const [user, setUser] = useState<FormattedUser | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch(`/api/users/${userId}`)
      .then(res => res.json())
      .then(data => {
        setUser(formatUser(data));
        setLoading(false);
      });
  }, [userId]);

  return { user, loading };
}

// Responsabilité 3 : rendu
function UserProfile({ userId }: { userId: string }) {
  const { user, loading } = useUser(userId);

  if (loading) return <div>Loading...</div>;
  return (
    <div>
      <h1>{user?.fullName}</h1>
      <p>{user?.age} ans</p>
    </div>
  );
}

Chaque unité a maintenant une seule raison de changer. formatUser change si les règles de formatage changent. useUser change si la stratégie de fetch change. UserProfile change si le rendu change.

Ce que SRP ne signifie pas

SRP ne signifie pas "une fonction = une ligne". Il s'agit de cohésion : est-ce que les responsabilités regroupées changent pour les mêmes raisons ?

Un hook useForm qui gère la validation, l'état des champs, et la soumission respecte le SRP si ces trois choses font partie de la même préoccupation "gestion de formulaire".

Le signal d'alarme, c'est le "et" : "cette fonction fetch les données et les transforme et les affiche".