React Hooksのルールをよく理解しないとハマるエラー対処法

# 概要
React Hooksは関数型コンポーネント内でクラスコンポーネントと同じような処理を簡潔に書くことができますが、
ルールをよく理解しないで使うと予期せぬエラーに見舞われます。
自分もフック使い始めの頃はなぜエラーが出ているのかわからずに時間を費やしていました。
この記事では、自分がハマったエラーをどう解決したのかを紹介します。

# ルール① フックはトップレベルでしか呼び出せない

フックはメソッド内の一番外側でしか、呼び出すことができません。
下の例を見てもらうと`useState`をprintメソッド内部で呼び出すことはできません。
なぜなら、printメソッドを実行することで、使用されるフックの数が変わってしまうからです。
フックを使用する場合は、コンポーネントを読み込んだ時点でどのフックが使われるかを明示しなければいけないので、printメソッドを実行して途中でフックの数が変わるといった挙動をしてしまうとエラーになってしまいます。

```react
import React, { useState } from "react";

export default function App() {
  const print = () => {
    const [value, setValue] = useState(0)
    console.log('print')
  }
  return (
    <div>
      <h1 onClick={() => print()}>Hello Qiita</h1>
    </div>
  );
}
```

実際にHello Qiitaをクリックすると以下のようなエラーが発生します。

```
Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.
```

これを解決するには、Appコンポーネントのルートでフックを呼び出す必要があります。

```react:解決方法
import React, { useState } from "react";

export default function App() {
  const [value, setValue] = useState(0)
  const print = () => {
    setValue(100)
    console.log('print')
  }
  return (
    <div>
      <h1 onClick={() => print()}>Hello Qiita</h1>
    </div>
  );
}
```

このように書くと、Appコンポーネントが読み込まれた時に使用するフックの数が変わることなく呼び出されるので正常に動作します。


# ルール② カスタムフックを作る時のルール

関数内でフックを定義して他の関数にそのフックを渡したい場合があります。
その時にもルールを守っていないとエラーになります。
フックを関数の内部で使用する場合は、メソッド名を`use`から始めないといけません。
これは一目でフックを使っているメソッドかどうかを分かりやすくするためです。
例えば、`getValue`メソッド内部でフックを使用しています。

```react
import React, { useState } from "react";

const getValue = () => {
  const [value, setValue] = useState(100)
  return value
}

export default function App() {
  const value = getValue()
  return (
    <div>
      <h1>Hello Qiita{value}</h1>
    </div>
  );
}
```

Appコンポーネントを呼び出すと、以下のようなwarningが発生します。

```
React Hook "useState" is called in function "getValue" which is neither a React function component or a custom React Hook function. (react-hooks/rules-of-hooks)
```

これは、`getValue`内でフックを使っているのにメソッド名が`use`で始まっていないことによるエラーです。
`getValue`を`useValue`に変更することでwarningがなくなります。
フックを使うメソッド名は、`use`で始めるようにしましょう。

```react:解決方法
import React, { useState } from "react";

const useValue = () => {
  const [value, setValue] = useState(100)
  return value
}

export default function App() {
  const value = useValue()
  return (
    <div>
      <h1>Hello Qiita{value}</h1>
    </div>
  );
}

```

# ルール③ フックを使う時はループ処理に気をつける

フックを使う時はリープ処理に陥っていないか気をつけましょう。
以下のように書くと、
`setValue(200) → render → setValue(200) → render`のループに陥ってしまいエラーが発生します。

```react
import React, { useState } from "react";

export default function App() {
  const [value, setValue] = useState(100)
  setValue(200)
  return (
    <div>
      <h1>Hello Qiita{value}</h1>
    </div>
  );
}
```

```
Too many re-renders. React limits the number of renders to prevent an infinite loop.
```

この場合は、`setValue(200)`が呼び出される回数を制限します。
例として、初期表示時しか`setValue(200)`を呼び出さないようにするなどで対応します。

```react:解決方法
import React, { useState, useEffect } from "react";

export default function App() {
  const [value, setValue] = useState(100)
  useEffect(() => {
    setValue(200)
  }, [])
  return (
    <div>
      <h1>Hello Qiita{value}</h1>
    </div>
  );
}
```