【问题标题】:React Hooks to Implement CounterReact Hooks 实现计数器
【发布时间】:2020-04-11 15:59:08
【问题描述】:

我正在尝试在组件中使用 React 挂钩(useState 和 useEffect)来显示投票计数。用户单击上/下后,“投票”状态应该增加或减少,然后应用程序调用 API 来修补数据库中的“投票”计数。原始投票计数来自父组件传递的道具。但由于某种原因,投票的初始状态始终为 0——即使当我 console.log 显示道具时它显示正确的票数?

PATCH 请求似乎工作正常,但我认为带有钩子的东西是错误的。在此先感谢您提供的任何帮助!

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>
    )
}

【问题讨论】:

  • 将雕像的初始值分配给道具的初始值被认为是反模式。
  • 如果 questionApiService 正在调用远程服务或 API 端点,它可能需要是带有 awaitasync 或使用承诺模式。
  • 所以问题只与组件的初始状态有关?
  • 问题似乎只与初始状态有关(PATCH 有效,单击向上/向下按预期重新呈现屏幕上的计数)。问题是当我刷新页面时,屏幕上显示的投票计数会变回零,即使这不是从道具传递的 voteCount。
  • 我是 React 新手,所以不知道将 props 作为初始状态传递是不好的。我选择这样做是因为这个“投票”组件在多个地方被重用。父组件总是获取一些数据(其中一部分是当前投票计数,这是我想要的初始状态)。我不确定是否有更好的方法来做到这一点...除了将“投票”功能作为自己的组件之外,我可以将其写入每个父级,以便它可以访问那里的数据?

标签: javascript reactjs react-hooks


【解决方案1】:

您需要将itemType 添加到useEffect 的依赖项中,因为您不能期望该道具在第一次渲染时可用,或者在整个组件生命周期中保持静态。在您当前的示例中,引用的 itemType 将始终在此函数第一次运行时引用其值。

正如其他人所提到的,从 props 设置状态初始值是禁止的。一旦你的组件收到它的道具,你可以通过使用单独的效果来设置状态来解决这个问题:

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

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

【讨论】:

  • 这正是我需要做的!谢谢!一旦我有足够的声望投票,我会回来投票这个答案!
【解决方案2】:

您可以像@tobiasfreid 所说的那样使用useState() 来更改计数器状态,或者如果您想创建一个自定义挂钩来实现相同的功能。

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]; 
}

通过这种方式,您可以创建自定义挂钩。

【讨论】:

    【解决方案3】:

    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);
    <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>

    【讨论】:

      【解决方案4】:

      tobiasfried 下面的回答真的帮助了我。我在这里发布完整的解决方案代码,以防将来可以帮助其他人。当然,如果有人有改进此代码的建议,我很乐意听到。

      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>
          )
      }

      【讨论】:

        猜你喜欢
        • 2019-11-29
        • 2021-12-12
        • 2019-06-30
        • 2020-09-29
        • 2019-07-28
        • 2021-03-24
        • 2019-09-11
        • 1970-01-01
        • 2020-01-18
        相关资源
        最近更新 更多