[React] Refine + Hasura で作る簡単タスク管理アプリ

# 初めに

工場の現場の見える化などを普段行っているものです。
本業はITエンジニアではないので色々と甘い部分がありますが悪しからず。

自分でDBを触ってマスター管理するのが面倒なのでWebで管理画面を作った時に使いました。

面倒なイベント処理やバックエンドとの通信処理をほぼ書くことなく作ることができます。

私の工場では外部にネットワークが繋がらないのでオンプレミスで動く構成となっております。

なお説明のためHasuraはCloud使ってますが、Dockerで立てて簡単に使うことができます。

大体30分ぐらいで作ることが出来ます。

# 出来上がるもの
- テーブルView
- タスク追加機能
- タスク編集機能
- タスク削除機能
データはHasuraを通してDBに保存されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390404/98865af5-939a-c9b8-7fd7-71d207be4a5b.png)

# 中途半端な完成コード
https://github.com/tansudorobou/refine-hasura-test-task-app

# 前提知識
- React 必須
- postgres ほぼ知らなくてよい
- GraphQL ほぼ知らなくてよい


# 導入

## Refineセットアップ
https://github.com/refinedev/refine
RefineというReactフレームワークを使います。
日本語の文献がほぼないですが、コードをあまり書くことなく簡単なアプリを作ることができます。
認証やら多言語化やらいいようにしてくれます。

まずはインストール
```
npm create refine-app@latest
or
yarn create refine-app
```

コマンドを打つとテンプレート選ぶ画面が出ます。
ここではViteで開発します。
プロジェクト名は必須だった気がします。
バックエンドはHasuraを選択
UI Frameworkも好きなのを選べます。
Ant Designがおすすめ(できることが多いので)
Example pageは最初はあったほうがいいと思います。
authはお好みで。
i18nもお好みで
packerge managerもお好みで
メールは書かなくてオッケー
```
>refine(Vite)
What would you like to name your project? 好きな名前
Choose your backend service to connect Hasura
Do you want to use a UI Framework? Ant Design
Do you want to add example pages?: Yes
Do you want to add example pages?: inferencer
Do you need any Authentication logic?: auth-provider-custom
Do you need i18n (Internationalization) support?: i18n-antd
Choose a package manager: yarn
```
すべて選択するとインストールが始まります。

とりあえず動かしてみます。
```
yarn dev 
```

Example Pageを追加してるのであればここで既にそれっぽいページができてます。


## Hasuraセットアップ
詳細割愛
データソースはpostgresを使ってます。

立ち上がったらこんな感じのページを開けると思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390404/54f309a7-4025-6939-e7c5-c0c96460a5ca.png)

Create Tableでテーブルを作ります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390404/c75a0278-db0e-d96c-a426-5170e384c1f0.png)

フレームワークの関係上idをPrimary Keyにする必要があります。

テーブルができたらInsert Rowで適当にデータを入れときます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390404/1bdf48ad-8b17-86e6-378e-998821d4ad44.png)

# コーディング
## ページ部分
pages直下にtasksフォルダを作成し
crate, edit, index, listファイルを以下のように作成します。

```
src\pages\tasks
create.tsx
edit.tsx
index.ts
list.tsx
```

コードはblog-postsをそのままコピーして関数名だけ変更します。
``` react
// list.tsx
import { IResourceComponentsProps } from "@refinedev/core";
import { AntdListInferencer } from "@refinedev/inferencer/antd";

import { inferencerPredefinedMeta } from "../../inferencerPredefinedMeta";

export const TasksList: React.FC<IResourceComponentsProps> = () => {
    return <AntdListInferencer
        meta={inferencerPredefinedMeta}
    />
};
```

``` typescript
// index.ts
export { TasksList } from "./list";
export { TasksCreate } from "./create";
export { TasksEdit } from "./edit"
```

## データ連携
App.tsx部分を記述していきます。

API_URLを先ほど作成したhasuraのGraphQL EndpointのURLに変更します。

この画像のページのGraphQL EndpointにあるURL
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390404/0d3b08f2-c60d-5c35-e058-9cd43771ede3.png)

headers部分をHasuraのRequestHeaders部分のkey valueにします。

``` react
const client = new GraphQLClient(API_URL, {
    headers: {
        "Hasuraに書いてあるKEY":"Hasuraに書いてあるValue" 
    },
});
```

HasuraのdataProviderはデフォルトだとuuidを使おうとするのでidTypeをIntに変更します。
``` react
return (
    <BrowserRouter>
        <GitHubBanner />
        <RefineKbarProvider>
            <ColorModeContextProvider>
                <Refine dataProvider={dataProvider(client, {
                    idType: "Int"
                })}
```

resources部分を修正してtasksを追加します。
``` react
resources={[
{
    name: "tasks",
    list: "/tasks",
    create: "/tasks/create",
    edit: "/tasks/edit/:id",
    meta: {
        canDelete: true,
    }
}
]}
```

Routeも修正します。
``` react
<Route index element={
    <NavigateToResource resource="tasks" />
} />
<Route path="/tasks">
    <Route index element={<TasksList />} />
    <Route path="create" element={<TasksCreate />} />
    <Route path="edit/:id" element={<TasksEdit />} />
</Route>
```

次にinferencerPredefinedMeta.tsに追記します。
``` typescript
    tasks: {
        getList: {
            fields: ["id", "task", "status"]
        },
        getOne: {
            fields: ["id", "task", "status"]
        },
    }
```

ここまで出来たら再度アプリを開きます。
すると勝手に生成したコードを表示してくれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390404/142c8c6d-ffab-35e8-5b83-8562b5e3b713.png)

Show the auto-generated code を押しコード表示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/390404/15a6103e-70cc-2067-244d-63ff93cafe87.png)

こちらをコピーして実際のコードに反映させます。
table部分はlist.tsxです。

``` react
// list.tsx
import React from "react";
import {
    IResourceComponentsProps,
    BaseRecord,
    useTranslate,
} from "@refinedev/core";
import { useTable, List, EditButton, DeleteButton } from "@refinedev/antd";
import { Table, Space } from "antd";

export const TasksList: React.FC<IResourceComponentsProps> = () => {
    const translate = useTranslate();
    const { tableProps } = useTable({
        syncWithLocation: true,

        meta: { fields: ["id", "task", "status"] },
    });

    return (
        <List>
            <Table {...tableProps} rowKey="id">
                <Table.Column
                    dataIndex="id"
                    title={translate("tasks.fields.id")}
                />
                <Table.Column
                    dataIndex={["task"]}
                    title={translate("tasks.fields.task")}
                />
                <Table.Column
                    dataIndex="status"
                    title={translate("tasks.fields.status")}
                />
                <Table.Column
                    title={translate("table.actions")}
                    dataIndex="actions"
                    render={(_, record: BaseRecord) => (
                        <Space>
                            <EditButton
                                hideText
                                size="small"
                                recordItemId={record.id}
                            />

                            <DeleteButton
                                hideText
                                size="small"
                                recordItemId={record.id}
                            />
                        </Space>
                    )}
                />
            </Table>
        </List>
    );
};

```

これだけでページが完成します。
createやedit部分も同様の作業をします。
全て貼り付けて不要部分を修正したら大枠は完成です。

これだけでタスク管理アプリができます。(細かい部分はいい感じに修正が必要)
詳しくはドキュメントを読んでください。
https://refine.dev/docs/#what-is-refine


社内で運用するのであればdockerでnginxを立ち上げてbuildしたデータを配信するだけです。

慣れたら10分程度で作れそうです。