redux-api-structをnpmに公開しました

# redux-api-struct

ライブラリを公開したのでqiitaに残しておきます。

https://github.com/konojunya/redux-api-struct

僕は普段Reactを業務で書く場合に、

- typescript
- styled-component
- redux
- react-router
- axios

あたりを使って書くのですが、このAPIに対してstoreの値をどのように持っておくかを簡単に定義することができるものをOSSにしました。

公開したものは [俺の考えたRedux + Atomic DesignでTypeSafeにコンポーネントを扱う方法](https://qiita.com/konojunya/items/5042da2257bb1a85fcac#reducer-typets%E3%81%AE%E4%B8%AD%E8%BA%AB%E3%81%AF%E3%81%AA%E3%82%93%E3%81%AA%E3%81%AE%E3%81%8B)という記事にでてくるもの何ですが、普段からよく書くのでライブラリにしておくと楽なので公開したといった具合です。

## 使い方

使い方は [README.md](https://github.com/konojunya/redux-api-struct/blob/master/README.md)にも書いていますが、ざっくり書きます。

例えば、ブログサービスで記事詳細画面を作る場合を想定します。

modelは

```ts
interface Article {
  title: string
  body: string
}
```

とします。フロントエンドでやる流れとしては

1. 画面を描画する（ローディング画面）
2. APIにアクセスする
3. 200 or 4xxが帰ってくる
4. その状況に応じてUIを適応する

とざっくり仮定します。

この詳細の管理を `article`というstateで管理するとします。

```ts
interface InitialState {
 article: Article
}
const initialState: InitialState = {
  article: null
}
```

さてこれを表示する画面を作成します。

```tsx
import * as React from "react"

interface Props {
  article: Article
}
export const Article: React.FunctionComponent = ({ article }) => {
  return (
    <>
      <h1>{article.title}</h1>
      <div>{article.body}</div>
    </>
  )
}
```

ここまで書いておけば

```tsx
<Article article={store.sampleReducer.article} />
```

これで、記事を表示するコンポーネントを定義することができました！

しかし、これだとAPIをfetchしている間、真っ白な画面がでてしまいます。ローディング画面を追加してみましょう。

## ローディング画面を追加する

```tsx
import * as React from "react"

export const Loading: React.FunctionComponent = () => (
  <p>now loading...</p>
)
```

ローディングコンポーネントを定義できたので、これを使って画面をつくりたいですね！

と、なった時に今の状況ではapiのfetchを行っている時の状態をstateで管理していないのでローディング画面をだすことができませんね。stateで管理しましょう。

```ts
interface InitialState {
  isFetching: boolean
  article: Article
}
```

これで、

```tsx
import * as React from "react"
import { Loading } from "./Loading"

interface Props {
  article: Article
  isFetching: boolean
}
export const Article: React.FunctionComponent = ({ article, isFetching }) => {
  if(isFetching) {
    return <Loading />
  }

  return (
    <>
      <h1>{article.title}</h1>
      <div>{article.body}</div>
    </>
  )
}
```

isFetchingがtrueの間は、ローディング画面ができてfetchが終わりfalseになった時はちゃんと記事の表示ができたと思います。

しかし、例えば存在しない記事にきた場合、APIは404を返しますね。isFetchingはfalseになりますが、 `article.title` は `undefined` になります。

と、いった具合にAPIと連携することを想定すると気にする点が多数出てきます。
これがあるたびに `initialState` にその状態管理を行うstateをはやしていくのはとてもめんどくさい作業になると思います。

そこで、redux-api-structを使います。

# redux-api-structを使い、簡単に記述する

redux-api-structは、状態。実データ。エラーを一つのstateで管理するようにするためのライブラリです。

initialStateの書き方を以下のようにしてみます。

```ts
import { ReduxAPIStruct } from "redux-api-struct"

interface InitialState {
  article: ReduxAPIStruct<Article>
}
```

こうすると、この `article` stateは以下のようなtreeを持つようになります。

```
article/
  - status
  - data
  - error
```

このstatusは `INITIAL`, `FETCHING`, `SUCCESS`, `FAILURE`の4つの状態をもつenumです。
dataに実際のresponseが入ります。
errorには、そのresponseのエラー内容がはいります。

これをコンポーネントで使う時は

```tsx
import * as React from "react"
import { ReduxAPIState, ReduxAPIStruct } from "redux-api-struct"

interface Props {
  article: ReduxAPIStruct<Article>
}
export const Article = ({ article }) => {
  switch(article.status) {
    case ReduxAPIState.INITIAL:
    case ReduxAPIState.FETCHING:
      return <Loading />
    case ReduxAPIState.SUCCESS:
      return <ArticleTemplate article={article.data} />
    case ReduxAPIState.FAILURE:
      if(article.error.statusCode === 404) {
        return <p>page not found!</p>
      } else {
        return <p>error view</p>
      }
  }
}
```

これでAPIの状態別でviewを出すことができて、成功した場合にちゃんと記事詳細を表示することができ、エラーの場合のハンドリングまで行うことができます。

簡単にtypesafeにコンポーネントとデータを扱うことができるので、typescriptで書いているプロジェクトはぜひ。

その他、質問などがありましたら [こちら](https://github.com/konojunya/redux-api-struct/issues)にお願いします。