use-http 使ってみた

2020-07-31

最近ブログを書くモチベーションがめちゃくちゃ高いです、たまーにそういう時期が来るんですよね。

記事に書いたとおり、さっそくこのサイトのコンタクトフォームを axios から use-http に替えてみました。

axios のときはこんな感じで実装していました。

async/await 使えよってツッコミは聞きません。

const [isSubmitting, setIsSubmitting] =
  useState<ButtonProps["disabled"]>(false);
const callback = useCallback<Parameters<typeof handleSubmitUseForm>[0]>(
  ({ email, message: text, name, subject }) => {
    setIsSubmitting(true);

    axios
      .post(`${process.env.GATSBY_BASE_URL}/sendMail`, {
        email,
        name,
        subject,
        text,
      })
      .then(() => {
        toast.success("Send Success Email!");
      })
      .catch(() => {
        toast.error("An Unknown Network Error Has Occurred");
      })
      .finally(() => {
        setIsSubmitting(false);
      });
  },
  [setIsSubmitting],
);

対して、use-http はこんな感じで実装しました。

こっちはきっちり実装しました。

const { error, loading, post, response } = useFetch(
  process.env.GATSBY_BASE_URL,
);
const callback = useCallback<Parameters<typeof handleSubmitUseForm>[0]>(
  async ({ email, message: text, name, subject }) => {
    await post("/sendMail", {
      email,
      name,
      subject,
      text,
    });

    const { ok } = response;

    if (!ok) {
      return;
    }

    toast.success("Send Success Email!");
  },
  [post, response],
);

useEffect(() => {
  if (!error) {
    return;
  }

  toast.error("An Unknown Network Error Has Occurred");
}, [error]);

ぱっと見、response のハンドリングを行っている場所が気持ち悪く見えると思いますが、use-http の仕様になります。

ちゃんとドキュメントを読めば引っかからないのですが、以下のように書いてしまうと正常に動作しません。

const {
  error,
  loading,
  post,
  response: { ok },
} = useFetch(process.env.GATSBY_BASE_URL);
const callback = useCallback<Parameters<typeof handleSubmitUseForm>[0]>(
  async ({ email, message: text, name, subject }) => {
    await post("/sendMail", {
      email,
      name,
      subject,
      text,
    });
  },
  [post, response],
);

useEffect(() => {
  if (!ok) {
    return;
  }

  toast.success("Send Success Email!");
}, [ok]);

useEffect(() => {
  if (!error) {
    return;
  }

  toast.error("An Unknown Network Error Has Occurred");
}, [error]);

結構みんな書きがちだと思います、自分も最初この形で書いていました。

タチが悪いことに、この書き方だと build 時に 「response が undefined だよ!」というエラーで初めて引っかかるという、おそらく static 周りでうまくいかないっぽいです。

で、公式サイトを読み直してみると、ちゃーんと注意書きがありました。

⚠️ Do not destructure the response object! Details in this video. Technically you can do it, but if you need to access the response.ok from, for example, within a component's onClick handler, it will be a stale value for ok where it will be correct for response.ok. ️️⚠️

⚠️ 応答オブジェクトを分解しないでください! このビデオの詳細。 技術的には可能ですが、たとえばコンポーネントの onClick ハンドラー内から response.ok にアクセスする必要がある場合、それは ok の古い値になり、response.ok に対して正しい値になります。 ️️⚠️

ref で実装してあるのかな?ともかく、fetch を await した直後に取得してやれば正常に動くみたいです。

公式のコードもそうなってますね。

import useFetch from 'use-http'

function Todos() {
  const [todos, setTodos] = useState([])
  const { get, post, response, loading, error } = useFetch('https://example.com')

  useEffect(() => { loadInitialTodos() }, []) // componentDidMount

  async function loadInitialTodos() {
    const initialTodos = await get('/todos')
    if (response.ok) setTodos(initialTodos)
  }

  async function addTodo() {
    const newTodo = await post('/todos', { title: 'my new todo' })
    if (response.ok) setTodos([...todos, newTodo])
  }

  return (
    <>
      <button onClick={addTodo}>Add Todo</button>
      {error && 'Error!'}
      {loading && 'Loading...'}
      {todos.map(todo => (
        <div key={todo.id}>{todo.title}</div>
      )}
    </>
  )
}

ということで、ちょっとクセがあるものの、やっぱり hooks だと素直に書けますね。

これからもサクッとアプリを作るケースや、GraphQL を使用するケースで使っていきたいなーと思います。