React hooksこんな風に使ってみたよ

バイトでReactを使うことになり、hooksの利用を~~強行~~提案して書いて見たところ、楽しすぎたのでどんな感じで使っているかを紹介したい。

あくまでこんな感じで使ってみたよって話なので、良いプラクティスかどうかは自己判断でお願いします

## そもそもなんでhooksを利用するのか
[公式ドキュメント](https://ja.reactjs.org/docs/hooks-intro.html#motivation)から引用すれば

> ステートフルなロジックをコンポーネント間で再利用する

ためです。今まではHoCとかで管理してたものをhooksでやりやすくした感じですね。

## 例1) 初期化するときの非同期通信の共通化

まずは単純にuseEffect(あるいはcomponentDidMount)内のfetchしてjsonにしてみたいなロジックを一箇所にまとめてみる

```javascript:useInitFetch.js
import { useEffect } from 'react';

export function useInitFetch(path, callback) {
  useEffect(() => {
    fetch(`http://path/to/api/${path}`)
      .then(res => res.json())
      .then(res => ({data: res}),
            e => ({error: e}))
      .then(res => callback(res));
  }, []);
}

```

使い方は

```jsx:MyComponent.jsx
import React, { useState } from 'react';
import { useInitFetch } from './useInitFetch'

export const MyComponent = () => {
  const [value, setValue] = useState({});
  useInitFetch('path/to/value', (res) => setValue(res));
  return <div>{value}</div>
}
```

## 例2) socket.ioの通信をどのコンポーネントからも呼び出せるようにする

useSocketはこんなことができるようにしました

* いろんなところからsocket.emitを呼び出したいが、socket自体はコンポーネントの表現には関わらないので、バケツリレーを避けたい -> context apiを利用
* 間違ってio.connectをなんども呼び出さないようにしたい -> 同じくcontext apiで解決可能
* あるコンポーネントがマウントするまでイベントを登録したくない・アンマウント後はイベントを削除したい -> useEffectのクリーンアップを利用

```jsx:useSocket.jsx

import React, { useContext, useEffect, useState, createContext } from 'react';
import io from 'socket.io-client';

export const socketContext = createContext();

export const SocketProvider = ({children}) => {

  const [socket, _] = useState(() => io.connect('http://path/to/socketapi'));

  useEffect(() => {
    return function cleanup() {
      socket.close();
    }
  }, []);

  return (
    <socketContext.Provider value={socket}>
      {children}
    </socketContext.Provider>
  )
}

export const useSocket = (setEvent, removeEvent) => {
  
  const socket = useContext(socketContext);

  useEffect(() => {
    setEvent(socket);
    return () => {
      removeEvent(socket);
    }
  }, []);

  return socket;
}

```

使い方：

```jsx:MyComponent.jsx
import React, { useState } from 'react';
import { SocketProvider, useSocket } from './useSocket';

export const MyComponent = () => {
  const [value, setValue] = useState({});
  const socket = useSocket((socket) => {
    socket.on('myevent', (res) => {
      setValue(res);
    });
  }, (socket) => {
    socket.off('myevent');
  });

  return (
    <>
      {value}
      <button onClick={() => socket.emit('myevent2', 'hoge')>Click!</button>
    </>
  )

  export const App = () => {
    return (
      <SocketProvider>
         <MyComponent />
      </SocketProvider>
}
```



これに限ったことではないですが、hooks + context APIは特にグローバルに唯一存在してほしい変数がある時にとても強力であると感じました

## 例3) 別Windowで開く&別Windowは最大1つまで

同じくcontext api + hooksを利用できます。

```jsx:useNewWindow.jsx
import { useState, createContext } from 'react';


export const windowContext = createContext();

export const WindowProvider = ({children}) => {
  const [win, setWin] = useState(null);

  return (
    <windowContext.Provider value={[win, setWin]}>
      {children}
    </windowContext.Provider>
  )
}

export const useNewWindow = (title, features) => {
  const [win, setWin] = useContext(windowContext);

  return (url) => {
    if(win === null || win.isClosed) {
      setWin(window.open(url, title, features));
    }
  }
}
```

使い方：


```jsx:MyComponent.jsx
import React, { useState } from 'react';
import { WindowProvider, useNewWindow } from './useNewWindow';

export const MyComponent = () => {
  const openWindow = useNewWindow('HogeTitle', 'resizable=yes,scrollbars=yes');

  return (
    <button onClick={() => openWindow('path/to/newWindow')}>Open Window</button>
  )

  export const App = () => {
    return (
      <WindowProvider>
         <MyComponent />
      </WindowProvider>
    )
  };
}
```

## まとめ

なんかhooksがいいっていうよりcontext apiがいいみたいな記事になってしもうた

useRefとかuseMemoとかまだまだ面白そうな機能がいっぱいなので、使う場面があったら更新していきます