1
votes

Réagir les crochets pour implémenter le compteur

J'essaye d'utiliser les hooks React (useState & useEffect) dans un composant pour afficher un décompte de votes. Une fois que l'utilisateur clique vers le haut / vers le bas, l'état 'vote' doit être incrémenté ou décrémenté, puis l'application fait un appel API à PATCH le décompte des «votes» dans la base de données. Le décompte des votes d'origine provient des accessoires transmis par le composant parent. Mais pour une raison quelconque, l'état initial des votes est toujours 0 - même si lorsque je console.log les accessoires, il montre le nombre correct de votes?

La requête PATCH semble fonctionner correctement, mais je pense que quelque chose avec des crochets ne va pas. Merci d'avance pour toute aide que je peux obtenir à ce sujet!

export default function Votes(props) {
    const { item, voteCount, itemType } = props
    const [votes, setVotes] = useState(voteCount || 0)

    useEffect(() => {
        if (itemType == 'question') {
                questionApiService.updateQuestionFields({
                    questionId: item.id, 
                    questionFields : { votes: `${votes}` }
                })
            } else if (itemType === 'answer') {
                console.log('answer')
                // questionApiService.updateAnswerFields({
                //     answerId: item.id,
                //     answerFields: { votes: votes }
                //})
            }
        }, [votes])

    const handleClick = e => {
        e.currentTarget.id === "increment"
            ? setVotes(prevCount => prevCount + 1)
            : setVotes(prevCount => prevCount - 1)
    }

    return (
        <div className="QuestionPage__votes-count">
            <button id="increment" onClick={handleClick}>
                <FontAwesomeIcon icon={faCaretUp} size="2x" />
            </button>
            {votes}
            <button id="decrement" onClick={handleClick}>
                <FontAwesomeIcon icon={faCaretDown} size="2x" />
            </button>
        </div>
    )
}


6 commentaires

Attribuer la valeur initiale de la statue à celle des accessoires est considéré comme un anti-motif.


Si questionApiService appelle un service distant ou un point de terminaison d'API, il peut être nécessaire d'être un async avec wait ou d'utiliser un modèle de promesse.


Le problème est donc uniquement lié à l'état initial du composant?


Le problème semble être uniquement lié à l'état initial (PATCH fonctionne et un clic haut / bas redonne le décompte à l'écran comme prévu). Le problème est que lorsque j'actualise la page, le nombre de votes affiché à l'écran revient à zéro, même si ce n'est pas le voteCount transmis par les accessoires.


Je suis nouveau sur React, donc je ne savais pas que passer des accessoires en tant qu'état initial n'était pas bon. J'ai choisi de le faire car ce composant 'Votes' est réutilisé à plusieurs endroits. Les composants parents récupèrent toujours des données (une partie de cela étant le nombre de votes actuel, ce que je veux que l'état initial soit). Je ne suis pas sûr d'une meilleure façon de faire cela ... à part au lieu de faire de la fonction 'vote' son propre composant, je pourrais l'écrire dans chaque parent afin qu'il puisse accéder aux données là-bas?


vous ne devez pas définir l'état à l'aide d'accessoires au départ


4 Réponses :


2
votes

Vous devez ajouter itemType aux dépendances de useEffect , car vous ne pouvez pas vous attendre à ce que l'accessoire soit disponible sur le tout premier rendu, ou qu'il reste statique tout au long le cycle de vie des composants. Avec votre exemple actuel, le itemType référencé fera toujours référence à sa valeur lors de la toute première exécution de cette fonction.

De même que d'autres l'ont mentionné, définir la valeur initiale de l'état à partir des accessoires est un non- non. Vous pouvez résoudre ce problème en utilisant un effet distinct pour définir l'état une fois que votre composant reçoit ses accessoires:

...
const [votes, setVotes] = useState(0);
...

useEffect(() => {
  setVotes(voteCount);
}, [voteCount]);


1 commentaires

C'est exactement ce que j'avais à faire! Merci! Une fois que j'aurai assez de réputation pour voter, je reviendrai et voterai cette réponse!



1
votes

Vous pouvez utiliser useState () comme @tobiasfreid l'a dit pour changer l'état du compteur ou si vous souhaitez créer un hook personnalisé pour implémenter la même fonctionnalité.

function ChangeCounter() {
  const [counter,onChangeValue] = useCounter(0);
  return (
    <div className="Form">
      <button type="button" onClick={onChangeValue(counter + 1)}> Increase Counter </button>
    </div>
  );
}
function useCounter(initialValue){
  const [value, setValue] = useState(initialValue || 0);

  const onChange= (data) => {
    setValue(data)
  };

  return [value, onChange]; 
}

De cette façon, vous pouvez créer vos crochets personnalisés.


0 commentaires

1
votes

<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
import React from 'react';
import ReactDOM from 'react-dom';

function useCounter(initialCount = 0) {
  const [count, setCount] = React.useState(initialCount);
  const increment = React.useCallback(() => setCount(c => c + 1), []);
  const decrement = React.useCallback(() => setCount(c => c - 1), []);
  return { count, increment, decrement };
}

const delay = duration => new Promise(resolve => setTimeout(resolve, duration));

function App() {
  const { count: votes, increment, decrement } = useCounter(0);
  React.useEffect(() => {
    (async () => {
      console.log('Call your API');
      await delay(2000);
      console.log('API Called with votes :', votes);
    })();
  }, [votes]);

  return (
    <div>
      <h1>Votes {votes}</h1>
      <button onClick={decrement}>Decrement</button>
      <button onClick={increment}>Increment</button>
    </div>
  );
}

const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);


0 commentaires

0
votes

La réponse de tobiasfried ci-dessous m'a vraiment aidé. Je poste le code complet de la solution ici au cas où cela pourrait aider quelqu'un d'autre à l'avenir. Et bien sûr, si quelqu'un a des suggestions pour améliorer ce code, je serais ravi de les entendre.

export default function Votes(props) {
    const { item, itemType } = props
    const [votes, setVotes] = useState()

    useEffect(() => {
        setVotes(item.votes);
    }, [item.votes])

    useEffect(() => {
        if (itemType == 'question') {
                questionApiService.updateQuestionFields({
                    questionId: item.id, 
                    questionFields : { votes: votes }
                })
            } else if (itemType === 'answer') {
                // questionApiService.updateAnswerFields({
                //     answerId: item.id,
                //     answerFields: { votes: votes }
                //})
            }
        }, [votes, itemType])

    return (
        <div className="QuestionPage__votes-count">
            <button 
                onClick={() => setVotes(prevCount => prevCount + 1)}>
                <FontAwesomeIcon icon={faCaretUp} size="2x" />
            </button>
            {votes}
            <button 
                onClick={() => setVotes(prevCount => prevCount - 1)}>
                <FontAwesomeIcon icon={faCaretDown} size="2x" />
            </button>
        </div>
    )
}


0 commentaires