1
votes

Logique de soumission de formulaire de réaction avec état levé et dépendance contrôlée

Je me suis plongé dans un trou de lapin profond avec ce composant pour tenter d'utiliser les hooks React.

Le composant Parent gère un état dictionnaire qui est finalement distribué à plusieurs composants.

/ p>

Mon composant enfant problématique WordInput a un formulaire avec une seule entrée. Lors de la soumission du formulaire, le composant récupère la définition du mot à partir d'une API et transmet à la fois le mot et la définition au parent qui définit ensuite l'état sous la forme de dictionnaire . Jusqu'ici, tout va bien SI c'est le premier mot du dictionnaire . La partie avec laquelle j'ai des problèmes est de soumettre tous les mots / définitions suivants.

Lorsque l'utilisateur soumet un mot suivant, je veux que le composant vérifie si le mot existe déjà dans le dictionnaire qui est passé à l'enfant. S'il n'existe pas, ajoutez-le au dictionnaire via la fonction d'envoi.

Je pense que le problème est que j'essaie d'en faire trop avec useEffect J' useEffect pour: - définir le chargement - vérifier et traiter le dictionnaire pour les mots existants - vérifiez que la définition et le mot ne sont pas vides et soumettez les deux au parent / dictionnaire - chercher une définition à partir d'une API

Dans le code non traité, j'ai plusieurs console.groups pour m'aider à garder une trace de ce qui se passe. Plus j'ajoute au composant, plus les sous-groupes et sous-groupes de sous-groupes s'accumulent. De toute évidence, l'approche que je prends n'est pas très sèche et provoque trop de ré-rendus des fonctions component / useEffect. Par souci de concision, j'ai retiré les entrées console.log .

La fetchWordDefinition importée traite simplement les données extraites et les arrange correctement dans un tableau.

Je ne sais pas comment garder cela sec et efficace, et toute aide est appréciée avec cette tâche plutôt simple. Mon intuition est de garder toute la logique pour soumettre le mot / définition dans le gestionnaire d'envoi, et d'utiliser uniquement useEffect pour valider les données antérieures.

import React, { useState, useEffect } from "react";
import fetchWordDefinition from "./lib/utils";

const WordInput = ({ onSubmit, dictionary }) => {
    const [definition, setDefinition] = useState([]);
    const [cause, setCause] = useState({ function: "" });
    const [error, setError] = useState({});
    const [loading, setLoading] = useState(false);
    const [word, setWord] = useState("");
    const [wordExistsInDB, setWordExistsInDB] = useState(false);

    useEffect(() => {
        const dictionaryEmpty = dictionary.length === 0 ? true : false;

        if (dictionaryEmpty) {
            return;
        } else {
            for (let i = 0; i < dictionary.length; i += 1) {
                if (dictionary[i].word === word) {
                    setWordExistsInDB(true);
                    setError({ bool: true, msg: "Word already exists in DB" });
                    break;
                } else {
                    setWordExistsInDB(false);
                    setError({ bool: false, msg: "" });
                }
            }
        }
    }, [dictionary, word]);

    useEffect(() => {
        const definitionNotEmpty = definition.length !== 0 ? true : false;
        const wordNotEmpty = word !== "" ? true : false;

        if (wordNotEmpty && definitionNotEmpty && !wordExistsInDB) {
            onSubmit(word, definition);
            setWord("");
            setDefinition([]);
        }
    }, [definition, word, onSubmit, wordExistsInDB]);

    useEffect(() => {
        if (cause.function === "fetch") {
            async function fetchFunction() {
                const fetch = await fetchWordDefinition(word);
                return fetch;
            }

            fetchFunction().then(definitionArray => {
                setDefinition(definitionArray);
                setCause({ function: "" });
            });
        }
    }, [cause, word]);

    const handleSubmit = async e => {
        e.preventDefault();

        setLoading(true);
        setCause({ function: "fetch" });
    };

    return (
        <form onSubmit={handleSubmit}>
            {error.bool ? <span>{error.msg}</span> : null}
            <input
                name='word'
                placeholder='Enter Word'
                type='text'
                value={word}
                onChange={({ target: { value } }) => setWord(value)}
            />
            <input type='submit' />
        </form>
    );
};

export default WordInput;


0 commentaires

3 Réponses :


0
votes

Il y a en effet plus de useEffect que nécessaire, ainsi que la plupart de l'état. Tout ce dont vous avez besoin est le handleSubmit pour effectuer la récupération.

const WordInput = ({ onSubmit, dictionary }) => {
  const [word, setWord] = React.useState("");
  const handleChange = React.useCallback(e => {
    setWord(e.currentTarget.value)
  }, [])
  const handleSubmit = React.useCallback(() => {
    //check if word is in dictionary
    const wordIsAlreadyThere = dictionary.map(entry => entry.word).includes(word)
    //fetch the definition, wait for it, and call submit
    if(!wordIsAlreadyThere && word.length > 0){
      fetchWordDefinition(word)
        .then(definition => {
          onSubmit(word, definition)
          setWord('')
        }).catch(err => console.log(err))
    }
  }, [])
  return (
    <form onSubmit={handleSubmit}>
      <input
        value={word}
        onChange={handleChange}/>
      <input type='submit' />
     </form>
  );
}


0 commentaires

0
votes

Je pense que vous manquez une certaine clarté et à quoi sert useEffect

Un composant fonctionnel est réexécuté à chaque fois qu'un accessoire ou un état change. useEffect s'exécute lorsque le composant est créé, et nous l'utilisons pour des choses telles que la première extraction ou l'abonnement à un gestionnaire d'événements. Le deuxième argument (tableau de variables) est utilisé pour que, si nous avons par exemple un article de blog avec des commentaires, etc., nous ne récupérons pas tout sauf si l'ID change (ce qui signifie que c'est un nouveau billet de blog)

En regardant votre code, nous avons ce flux:

  1. L'utilisateur entre quelque chose et clique sur Soumettre

  2. Vérifier si le mot existe dans un dictionnaire

    a. S'il existe, affichez un message d'erreur

    b. S'il n'existe pas, récupérez une API et appelez onSubmit

Donc, vraiment, le seul état que nous ayons ici est le mot. Vous pouvez simplement calculer une erreur en fonction de si le mot est dans le dictionnaire, et l'appel d'API est effectué dans un rappel ( useCallback ). Vous avez beaucoup d’états supplémentaires qui n’ont pas vraiment d’importance dans un état.

Une version simplifiée ressemblerait à ceci

const WordInput = ({ onSubmit, dictionary }) => {
  const [word, setWord] = useState("")
  const [loading, setLoading] = useState(false)

  // `find` will find the first entry in array that matches
  const wordExists = !!dictionary.find(entry => entry.word === word)

  // Ternary operator, 
  const error = (wordExists) ? "Word already exists in DB" : null

  // When user hits submit
  const handleSubmit = useCallback(() => {
    if (wordExists || !word.length) return;

    setLoading(true)
    fetchFunction()
      .then(definitionArray => {
        onSubmit(word, definitionArray)
      })
  }, [])

  return (
      <form onSubmit={handleSubmit}>
          {error && <span>{error}</span>}

          <input
              name='word'
              placeholder='Enter Word'
              type='text'
              value={word}
              onChange={({ target: { value } }) => setWord(value)}
          />
          <input type='submit' onclick={handleSubmit} disabled={wordExists}/>
      </form>
  );
};


2 commentaires

Merci pour votre réponse, j'aimerais ajouter ce qui suit. useEffect s'exécute non seulement lorsque le composant est monté, mais également lorsque l'une des variables d'état change. Il semble que setLoading ne soit jamais remis à false dans votre extrait de code.


Oups désolé, je viens de le taper de la mémoire très rapidement. Vous feriez setLoading (false) dans le .then () lorsque l'API a résolu, avant onSubmit () . Vous pouvez également définir Word sur une chaîne vide



0
votes

Votre composant n'a besoin que de garder une trace du mot et de l'indicateur de chargement .

  • Lorsque l'utilisateur modifie l'entrée de mot, il met à jour l'état du mot.
  • Lorsque l'utilisateur soumet le formulaire, l'état de chargement change. Cela déclenche un useEffect qui vérifie d'abord si le mot existe déjà. Sinon, il procède à sa récupération et ajoute à la fois le mot et sa définition au dictionnaire.

const WordInput = ({ onSubmit, dictionary }) => {
    const [loading, setLoading] = useState(false);
    const [word, setWord] = useState("");

    useEffect(() => {
        if (!loading) return;

        const existing_word = dictionary.find(item => item.word === word);

        if (existing_word) return;

        const fetchFunction = async () => {
            const definition = await fetchWordDefinition(word);

            // Update the dictionary
            onSubmit(word, definition);

            // Reset the component state
            setWord("");
            setLoading(false);
        };

        fetchFunction();
    }, [loading]);

    return (
        <form
            onSubmit={e => {
                e.preventDefault();

                if (word.length) {
                    setLoading(true);
                }
            }}
        >
            <input
                name="word"
                placeholder="Enter Word"
                type="text"
                value={word}
                onChange={({ target: { value } }) => setWord(value)}
            />
            <input type="submit" />
        </form>
    );
};

S'il vous plaît laissez-moi savoir si quelque chose n'est pas clair ou si j'ai manqué quelque chose.


0 commentaires