Comment j'ai intégré react-flow avec la manipulation de données complexes et profondément imbriquées
Cet article fait suite à l'article "Comment créer un SaaS Next.js + MongoDB entièrement auto-hébergé".
Nous allons principalement nous concentrer sur la magie qui se passe au sein de l'application Next.js.
Les besoins :
Ce que nous voulons construire est une interface graphique nodale pour gérer les données de Docker Compose.
De ce besoin produit, nous pouvons extraire certaines réponses techniques :
- Une gestion efficace des données Docker Compose.
- Un outil pour nous aider avec l'interface graphique nodale.
- Un système de gestion d'état permettant des mutations complexes et profondément imbriquées, de manière efficace.
Choisir une bibliothèque de données pour gérer Docker Compose efficacement
Docker Compose a une structure de données très complexe : elle est profondément imbriquée, et certains objets en bas de la hiérarchie sont liés à ceux du haut. J'ai cherché une bibliothèque Docker Compose en TypeScript, mais n'en ai trouvé aucune, donc j'ai construit la mienne à partir de zéro.
Elle n'est actuellement pas open-source, mais nous avons un formulaire pour demander un accès.
Cette bibliothèque centralise tous les états dans une grande classe Compose
. En gérant l'état d'une seule instance de Compose
, il devient simple de rester à jour avec toutes les données liées aux tâches.
Gestion de l'état avec React
Dès le début, nous savions que nous devions gérer l'état de Compose
côté client, car de nombreuses mutations d'état ont lieu sur la page du playground.
Mais quel outil choisir entre React UseState, Zustand, Recoil, Redux... ?
Nous avons choisi Zustand pour la gestion d'état grâce à sa bonne intégration avec Next.js et sa capacité à modifier l'état en passant une fonction plutôt qu'un nouvel objet.
Pourquoi cela importe-t-il ?
Avec une approche traditionnelle, en utilisant setData(newData)
, un nouvel objet Compose serait recréé à chaque mise à jour de l'état, comme ceci :
const [compose, setCompose] = useComposeState()
setCompose(new Compose(whatever you change))
Cela entraînerait une nouvelle instance de Compose
à chaque fois, ce qui pourrait causer des inefficacités ou des re-rendus inutiles.
Avec Zustand, nous pouvons passer une fonction de modification, permettant d'appliquer directement les changements à l'objet existant sans en créer un nouveau :
const [compose, setCompose] = useComposeState()
setCompose((oldCompose) => oldCompose.whateverChange(anyParamsNeeded))
Cette approche permet des mises à jour ciblées tout en gardant l'instance intacte. Pour des changements plus précis, vous pouvez modifier directement les propriétés au sein de l'état :
const [compose, setCompose] = useComposeState()
setCompose((oldCompose) => {
oldCompose.service1.name = "nouveau nom"
})
En utilisant cette méthode, nous maintenons la simplicité et l'efficacité des mises à jour d'état sans compromettre l'intégrité de l'instance Compose
.
Mais vous vous demandez peut-être, à quoi ressemble le store Zustand ?
"use client";
import { create } from "zustand";
import { Compose } from "@entrecompetents/composecraftLib";
import { generateRandomName } from "@/lib/utils";
interface ComposeState {
compose: Compose;
setCompose: (updater: (currentCompose: Compose) => void) => void;
}
export const useComposeStore = create<ComposeState>((set) => ({
compose: new Compose({ name: generateRandomName() }),
setCompose: (updater: (currentCompose: Compose) => void) => {
set((state) => {
updater(state.compose);
return { compose: state.compose };
});
},
}));
La bibliothèque GUI nodale
Les options étaient : JointJS, react flow, mermaidJS (peut-être d'autres, mais nous n'avons pas exploré plus loin).
Voici un petit tableau récapitulatif de notre choix :
JointJS | React Flow | mermaidJS | |
---|---|---|---|
Interactivité | ✅ | ✅ | ❌ |
Facilité de développement | ⭐️ | ? | ⭐️⭐️⭐️ |
Open source | ✅ | ❌ | ✅ |
Nous avons finalement choisi React Flow pour son interactivité et le fait qu'il soit open source.
Cependant, gardez à l'esprit que ce n'est pas la voie la plus simple, car React Flow nécessite beaucoup de développement personnalisé.
Le schéma du playground :
Conclusion :
Créer une interface graphique nodale pour gérer les données Docker Compose dans une application Next.js est un processus à plusieurs niveaux combinant gestion d'état efficace, une bibliothèque GUI robuste, et des outils développés sur mesure. À travers cette exploration, nous avons :
- Créé une bibliothèque TypeScript adaptée à la complexité et à la nature imbriquée de la structure Docker Compose.
- Optimisé la gestion de l'état client avec Zustand, permettant des mises à jour efficaces sans recréer d'objets ou d'instances entiers.
- Choisi React Flow comme bibliothèque GUI nodale, en exploitant son interactivité et sa personnalisation, tout en surmontant une courbe d'apprentissage importante avec des développements ciblés.
Cette intégration, représentée dans le schéma final, montre un flux de travail rationalisé où les modifications d'état profondément imbriquées, les interactions GUI et les communications avec le backend fonctionnent harmonieusement ensemble. Les outils choisis répondent non seulement aux exigences fonctionnelles mais aussi aux objectifs de performance, d'ergonomie et d'évolutivité.
Ces efforts aboutissent à une page de playground offrant une expérience utilisateur interactive et cohérente, tout en respectant les meilleures pratiques techniques.