Next.js × TypeScriptの同期・非同期処理をHooksを使って書く ~同期的処理編~

# 概要
こんにちはよしデブです。
[前回(Next.js × TypeScriptの同期・非同期処理をHooksを使って書く ~Reduxの準備編~)](https://qiita.com/yoshidev523/items/fdd73543e0d3762890b1)の続きです。

1. [Reduxを始めるの準備](https://qiita.com/yoshidev523/items/fdd73543e0d3762890b1)
2. **同期処理でTodo追加・完了機能を作る(今日はここ)**
3. [非同期処理でログイン機能を作る（メイン）](https://qiita.com/yoshidev523/items/0b5f1131d04f3e310ba3)
4. (おまけ)その他のライブラリ紹介

前回はReduxの準備をしました。今回はこれを使ってTodoリストを実装していこうと思います！
本アプリを実装するにあたり、[styled-components](https://styled-components.com/)でコンポーネントのスタイリングをして、Todoの入力フォームに[Formik](https://jaredpalmer.com/formik/)を使用したので、その書き方についても参考になればと思います。

# Todoを入力するFormを作る！
では早速Todoリスト追加・完了機能を実装していきましょう！
## styled-componentsで部品を作る。
まずはフォームの部品になるものを作っていきます。[Atomic Design](https://bradfrost.com/blog/post/atomic-web-design/)の考えに則ってAtom→Molecule→Organismの順に作ります。
[styled-components](https://styled-components.com/)はコンポーネントのスタイルをcssファイルではなく、JavaScript(TypeScript)内で書けるライブラリです。cssの書き方のままコードに書けちゃいます。CSS in JSの書き方の一つです。

### Atom
入力フォーム(TextInput)とラベル(Label)とバリデーションエラーの時に表示するエラーメッセージ(ErrorText)を作りました。
COLOR変数は`Color.js`で予め定義した色コードが入っています。TypeScriptでcssが書けるので、cssでは面倒な値の変数化はとても簡単です(scssを使えば変数化もできますね)

```typescript:src/components/atoms/forms/TextInput.tsx
import COLOR from '@common/Color'
import styled from 'styled-components'

const TextInput = styled.input`
  padding: 10px;
  background: ${COLOR.WHITE};
  border: 1px solid ${COLOR.BLACK};
`

export default TextInput;
```

```typescript:src/components/atoms/label/Label.tsx
import styled from 'styled-components'

const Label = styled.div`
  padding-top: 5px;
  padding-bottom: 5px;
`

export default Label
```

```typescript:src/components/atoms/forms/ErrorText.tsx
import COLOR from '@common/Color'
import styled from 'styled-components'

const ErrorText = styled.div`
  padding: 5px;
  color: ${COLOR.RED};
`
export default ErrorText;
```

```typescript:src/common/Color.ts
const COLOR = {
  PRIMARY_MAIN: '#388E3C',
  PRIMARY_LIGHT: '#60AD5E',
  PRIMARY_DARK: '#124F03',
  SECONDARY_MAIN: '#6E4C40',
  SECONDARY_LIGHT: '#9D786B',
  SECONDARY_DARK: '#40231B',
  WHITE: '#FFFFFF',
  BLACK: '#333333',
  RED: '#F41F20',
}

export default COLOR;
```

### Molecule
TextInputとLabelを組み合わせてラベル付きの入力フォーム(TextField)を作成します。

```typescript:src/components/molecules/TextField/TextFiels.tsx
import TextInput from '@components/atoms/forms/TextInput'
import Label from '@components/atoms/label/Label'
import { FieldProps } from 'formik'
import React, {FC} from 'react'

interface Props {
  type: string
  label: string
  fieldProps: FieldProps
}

const TextField: FC<Props> = ({fieldProps, label, type }) => {
  const {field} = fieldProps
  return(
    <div>
      <Label>{label}</Label>
      <TextInput type={type} {...field}/>
    </div>
  )
}

export default TextField;
```

### Organism
**OrganismでやっとReduxが登場します。**

***「テキストに入力してボタンを押せばTodoが追加される」*** という処理を記述していきます。

#### 1. Formikでフォームを書く
ここで余談ですが、フォームに関しては [*Formik*](https://jaredpalmer.com/formik/) というフォームライブラリを使用します。よくReduxFormと比較されるようですが、Formik公式はこう言ってます([Why not Redux-form?](https://jaredpalmer.com/formik/docs/overview#why-not-redux-form))

> Why not Redux-Form?

> By now, you might be thinking, "Why didn't you just use Redux-Form?" Good question.

> 1. According to our prophet Dan Abramov, form state is inherently ephemeral and local, so tracking it in Redux (or any kind of Flux library) is unnecessary
> 2. Redux-Form calls your entire top-level Redux reducer multiple times ON EVERY SINGLE KEYSTROKE. This is fine for small apps, but as your Redux app grows, input latency will continue to increase if you use Redux-Form.
> 3. Redux-Form is 22.5 kB minified gzipped (Formik is 12.7 kB)

> My goal with Formik was to create a scalable, performant, form helper with a minimal API that does the really really annoying stuff, and leaves the rest up to you.

要は、 **ReduxFormより手軽で動作が軽い** らしい。

Formikでのバリデーションチェックはyupというライブラリを使用します。 *Schema* という形でバリデーションを定義します。 **直感的な記述ができて個人的にはとても気に入っています！！**
詳しいFormikでのフォームの書き方の説明は今回は割愛します。

#### 2. 追加ボタンが押された時にActionCreatorをdispatchし、Reducerに接続する。
HooksでActionCreatorをdispatchするには **useDispatch** を使ってReducerと接続した時に呼び出す。たったこれだけ。
Hooksが登場するまでは、Reduxを使いたい時はクラスコンポーネントでconnect関数を使ったりしてましたが、めちゃくちゃスッキリした書き方ができるようになっててびっくりしました！

```typescript:src/components/organisms/TodoForm/TodoForm.tsx
import ErrorText from '@components/atoms/forms/ErrorText'
import TextField from '@components/molecules/TextField/TextField'
import Button from '@material-ui/core/Button'
import { addTodo } from '@store/todos/actions'
import {
  Field,
  FieldProps,
  Form,
  Formik,
} from 'formik'
import React, {FC} from 'react'
import { useDispatch } from 'react-redux'
import styled from 'styled-components'
import * as Yup from 'yup';


const FieldWrapper = styled.div`
  margin-top: 30px;
  margin-left: 40px;
`

const AddButtonWrapper = styled.div`
  margin-top: 40px;
  margin-left: 40px;
`

// validation schema。yupは便利！
const TodoSchema = Yup.object().shape({
  task: Yup.string()
    .required('入力してください'),
});

export interface TodoFormValues {
  task: string;
}

const TodoForm: FC = ({}) => {
  // hooksの醍醐味。これだけでstoreに接続できる
  const dispatch = useDispatch();
  // 初期値は空文字をセット
  const initialValues: TodoFormValues = { task: '' };

  // ボタンが呼び出されたらaddTodoをdispatch
  // これでreducerにtaskが渡される
  const handleSubmit = (values: TodoFormValues) => {
    dispatch(addTodo(values.task));
  }

  return(
    <Formik
      initialValues={initialValues}
      validationSchema={TodoSchema}
      // type="submit"の要素が押された時に呼び出す関数を設定
      onSubmit={handleSubmit}
      render={({errors, touched}) => (
        <Form>
          <Field
            name="task"
            // renderでinput要素を与える
            render={(props: FieldProps) => {
              return (
                <FieldWrapper>
                  <TextField
                    label={'タスク'}
                    type={'text'}
                    fieldProps={props}
                  />
                  {errors.task && touched.task &&
                  <ErrorText>
                    {errors.task}
                  </ErrorText>}
                </FieldWrapper>
              )
            }}
          />
          <AddButtonWrapper>
            <Button type="submit" variant="contained" color="primary">追加</Button>
          </AddButtonWrapper>
        </Form>
      )}
    />)
};

export default TodoForm;
```

# ページを作成する！
最後にページを作成します。Next.jsではpages配下にページを書くと勝手にルーティングをしてくれます。
index.tsxでは現在のtodoリストとTodo入力フォームを表示します。
ReduxのStateを取得するには `useSelector` を使用します。
今回は現在のtodoリスト(todos)を `useSelector` を使用して取得します。

また、ここでは「各todoに対して完了ボタンを押すと完了状態にする」処理を実現します。完了ボタンが押された時に `doneTodo`アクションをdispatchするように記述してあげます。

```typescript:pages/index.tsx
import LoginForm from '@components/organisms/LoginForm/LoginForm'
import TodoForm from '@components/organisms/TodoForm/TodoForm'
import { Button } from '@material-ui/core'
import { StoreState } from '@store/index'
import { doneTodo } from '@store/todos/actions'
import React from 'react'
import { useDispatch, useSelector } from 'react-redux'
import Container from '@material-ui/core/Container'

/**
 * TopPage
 */
const TopPage = () => {
  const dispatch = useDispatch()
  // 現在のtodoリストを取得
  const [todos] = useSelector((state: StoreState) => [
    state.todos.todos,
  ])

  return (
    <main>
      <Container maxWidth="xs">
        <h1>Hello, World</h1>
        <h2>Todos</h2>
        // 現在のTodoリストを表示
        <ul>
          {todos.map((todo, idx) => (
            <li key={idx}>
              // doneだったら打ち消し線
              <span
                style={{ textDecoration: todo.done ? 'line-through' : 'none' }}
              >
                {todo.task}
              </span>
              <Button
                variant="contained"
                color="primary"
                // doneだったらボタンを押せなくする
                disabled={todo.done}
                // 完了ボタンを押したらdoneTodoアクションをdispatchする
                onClick={() => dispatch(doneTodo(todo.id))}
                style={{ marginLeft: 10 }}
              >
                DONE
              </Button>
            </li>
          ))}
        </ul>
        <TodoForm />
      </Container>
    </main>
  )
}

export default TopPage
```

# 完成！！
ここまでの進捗でこんな感じの簡単なTodoリスト管理ができました。
![demo2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/183745/c0790d28-da19-cb58-bfbe-5de4af50e02c.gif)


# 終わりに
Redux Hooksを使ったTodoリストの追加・完了機能を実装しました。
ここまでで実装してきたものは同期的処理になります。処理が順番になされるので比較的直感的な記述ができたと思います。

**次回は本シリーズのメイン！ログイン機能で非同期処理をする方法をRedux Hooksを使って実現したいと思います。**

前回はこちら[Next.js × TypeScriptの同期・非同期処理をHooksを使って書く ~Reduxの準備編~](https://qiita.com/yoshidev523/items/fdd73543e0d3762890b1)
次回はこちら[Next.js × TypeScriptの同期・非同期処理をHooksを使って書く ~非同期処理編~](https://qiita.com/yoshidev523/items/0b5f1131d04f3e310ba3)