Help us understand the problem. What is going on with this article?

react-hooksのuseStateでfunctionを管理させたい場合のTips

More than 1 year has passed since last update.

react-hooksのuseStateはこれまでのstateのように値を保持してくれる重要な関数だ。

例えば単純なカウンターならこんな具合になるだろう

const useCounter = () => {
  const [count, setCounter] = useState(0)
  return { count, setCounter }
}
export const MyApp = () => {
  const { count, setCounter } = useCounter()
  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCounter(count + 1)}>+</button>
    </div>
  )
}

このuseStateに関数を保持させたい場合、ちょっと注意が必要になる。

例えばこんな風に書くと、意図しない挙動になるだろう。

const initialHelloFn = () => {
  console.log("initial")
}
const useCounter = () => {
  const [count, setCounter] = useState(0)
  const [helloFn, setHelloFn] = useState(initialHelloFn)
  // このnewFnが何度も呼び出される
  const newFn = useCallback(() => {
    console.log("hello!", count)
  }, [count])
  useEffect(() => {
    setHelloFn(newFn)
  }, [newFn, setHelloFn])
  console.log(helloFn)
  return {
    count,
    setCounter,
    helloFn
  }
}

export const MyApp = () => {
  const { count, setCounter, helloFn } = useCounter()
  return (
    <div>
      <div>{count}</div>
      <button onClick={() => setCounter(count + 1)}>+</button>
      <button onClick={() => helloFn()}>hello</button>
    </div>
  )
}

おそらくこのようにすると、hello! 0のような出力が大量に出てしまうだろう

正しく動かす場合はどうするか?

{fn: 管理したい関数} のようにobjectで管理する。

const useCounter = () => {
  const [count, setCounter] = useState(0)
  // {fn: Function}などobjectの形にラップする
  const [helloFn, setHelloFn] = useState({ fn: initialHelloFn })
  const newFn = useCallback(() => {
    console.log("hello!", count)
  }, [count])

  useEffect(() => {
    setHelloFn({ fn: newFn })
  }, [newFn, setHelloFn])

  return {
    count,
    setCounter,
    helloFn: helloFn.fn
  }
}

なぜこうする必要があるか?

結論から言えばuseStateから返ってくるsetFooのようなハンドラーはFunctional Updateに対応しているため、単純に関数を渡してしまうとFunctional Updateとして処理されてしまうからになる。

https://reactjs.org/docs/hooks-reference.html#functional-updates

Functional Update自体は便利で、下記のように、現在の値を引き継がずに関数だけで利用することができる

<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>

これはhooksで新しく入ったわけではなく、React.Component.setStateにも同様の機能が存在していたものだ
https://reactjs.org/docs/react-component.html#setstate

ただComponentの場合はstate自体がobjectなのでほとんどこのような引っかかり方をすることは無かった。useStateが単純な値を格納するものとして利用できてる分、この点は気をつけるべき部分だろう

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした