React, Material-UI, Unstated, RechartsでTODOを作った

(2019-03-28)

動作

コード

create-react-app

create-react-appでアプリを作成した。 TypeScriptを有効にしている

$ npx create-react-app react-todo-unstated --typescript
$ cd react-todo-unstated
$ tree src/
src/
├── App.css
├── App.test.tsx
├── App.tsx
├── index.css
├── index.tsx
├── logo.svg
├── react-app-env.d.ts
└── serviceWorker.ts

$ npm start

Material-UI

UIはMaterial-UIでUIで作った。

$ npm install --save @material-ui/core @material-ui/icons

public/index.htmlRobotoフォントを入れた。

<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">

Unstated

UnstatedはReact v16からのContext APIを使ったStateを管理するための薄いライブラリ。

$ npm install --save unstated

Stateを持つContainerを作る。

class TodoContainer extends Container<TodoState> {
  state: TodoState = {
    newTodo: "",
    todos: [],
    isCreating: false
  };

  changeNewTodo(newTodo: string) {
    this.setState({ newTodo: newTodo });
  }

  async createTodo() {
    if (!this.canCreateTodo()) {
      return
    }
    this.setState({ isCreating: true });
    const newTodo = { title: this.state.newTodo, isDone: false }
    await axios.post<Todo[]>("http://localhost:3001/todo", newTodo)
    await this.loadTodo()
    this.setState({ newTodo: "", isCreating: false })
  }

  canCreateTodo() {
    return this.state.newTodo.length !== 0 && !this.state.isCreating
  }

  async setIsDone(id: number, status: boolean) {
    const target = this.state.todos.find((t) => t.id === id)
    if (target) {
      target.isDone = status
      await axios.put<Todo[]>(`http://localhost:3001/todo/${id}`, target)
      await this.loadTodo()
    }
  }

  async deleteTodo(id: number) {
    await axios.delete(`http://localhost:3001/todo/${id}`)
    await this.loadTodo()
  }

  async loadTodo() {
    const resp = await axios.get<Todo[]>("http://localhost:3001/todo")
    this.setState({ todos: resp.data })
  }
}

<Provider>の中で<Subscript to={[Container]}>するとContainerが渡ってくるので、 このstateにアクセスしたりメソッドを呼んでstateを更新でき、変更があったら再レンダリングされる。

<Provider>
    ...
    <div className="App">
        <div className="Form"><TodoForm></TodoForm></div>
        <div className="Chart"><DoneChart></DoneChart></div>
        <TodoList></TodoList>
    </div>
</Provider>
# <DoneChart>
<Subscribe to={[TodoContainer]}>
    {(container: TodoContainer) =>
        <PieChart width={200} height={200}>
            <Pie data={this.data(container).data} dataKey="value" innerRadius={60} outerRadius={80}>
                {
                    this.data(container).data.map(d => <Cell fill={d.color} />)
                }
            </Pie>
            <text x={100} y={105} textAnchor="middle">{this.data(container).ratio}</text>
        </PieChart>
    }
</Subscribe>

Recharts

Rechartsでグラフを描いた。

$ npm install --save recharts @types/recharts
<PieChart width={200} height={200}>
    <Pie data={this.data(container).data} dataKey="value" innerRadius={60} outerRadius={80}>
        {
            this.data(container).data.map(d => <Cell fill={d.color} />)
        }
    </Pie>
    <text x={100} y={105} textAnchor="middle">{this.data(container).ratio}</text>
</PieChart>