---
# System prepended metadata

title: React + TypeScript TodoList

---

# React + TypeScript TodoList

npx create-react-app todo-typescript --template typescript


### App.css書き換え
```
*, *:active, *:hover, *:before, *:after {
    box-sizing: border-box;
}
 
.inner {
    max-width: 700px;
    margin: 0 auto;
}
 
.task-list {
    list-style: none;
    margin: 0;
    padding: 0;
    border-top: 1px solid #d7d2cd;
}
.task-list li {
    padding: 20px 10px;
    border-bottom: 1px solid #d7d2cd;
    display: flex;
    align-items: center;
    position: relative;
}
 
.task-list li.done span {
    text-decoration: line-through;
}
.task-list li label {
    width: calc(100% - 100px);
    display: block;
    cursor: pointer;
}
.task-list li span {
display: block;
}
.task-list li .btn {
    display: none;
    width: 80px;
    position: absolute;
    right: 10px; 
    padding: 0.4em 1em;
    font-size: 13px;
    z-index: 10;
}
.task-list li:hover .btn {
    display: block;
}
 
.inputForm {
    margin-bottom: 40px;
    background: #f9f3ee;
    padding: 40px 0;
    border-bottom: 1px solid #d7d2cd;
}
.inputForm .inner  {
    display: flex;
}
.inputForm .input {
    width: 80%;
    font-size: 15px;
    outline: none;
    border: solid 3px #d7d2cd;
    padding: 10px;
    border-radius: 7px;
    margin-right: 10px;
}
.inputForm .input:focus {
    background: #f9f9f0;
}
.inputForm .btn {
    width: 20%;
}
.btn {
    font-size: 15px;
    cursor: pointer ;
    font-weight: bold;
    display: inline-block;
    padding: 10px 15px;
    text-decoration: none;
    background: #668ad8;
    color: #FFF;
    border-bottom: solid 4px #627295;
    border-radius: 7px;
    outline: none;
}
.btn:active {
    transform: translateY(4px);
    border-bottom: none;
    margin-bottom: 4px;
}
.btn.is-delete {
    background-color: #d86681;
    border-bottom-color: #956270;
}
 
.checkbox-input {
    display: none;
}
.checkbox-label {
    padding-left: 30px;
    position: relative;
    margin-right: 20px;
}
.checkbox-label::before {
    content: "";
    display: block;
    position: absolute;
    top: 2px;
    left: 0;
    width: 18px;
    height: 18px;
    border: 1px solid #d7d2cd;
    border-radius: 4px;
}
.checkbox-input:checked + .checkbox-label {
    color: #999;
}
.checkbox-input:checked + .checkbox-label::after {
    content: "";
    display: block;
    position: absolute;
    top: 0;
    left: 5px;
    width: 7px;
    height: 14px;
    transform: rotate(40deg);
    border-bottom: 3px solid #d01137;
    border-right: 3px solid #d01137;
}
```

mkdir src/components

### 型の設定
src/components/Types.ts

```
export type Task = {
  id: number;
  title: string;
  done: boolean;
};

```

### TaskItem作成
src/components/TaskItem.tsx

```
import React from 'react'
import { Task } from './Types'
 
type Props = {
    task: Task
    handleDone: (task: Task) => void
    handleDelete: (task: Task) => void
}
 
const TaskItem: React.FC<Props> = ({ task, handleDone, handleDelete }) => {
 
    return (
        <li className={task.done ? 'done' : ''}>
            <label>
                <input
                    type="checkbox"
                    className="checkbox-input"
                    onClick={() => handleDone(task)}
                    defaultChecked={task.done}
                />
                <span className="checkbox-label">{ task.title }</span>
            </label>
            <button
                onClick={() => handleDelete(task)}
                className="btn is-delete"
            >削除</button>
        </li>
    )
}
 
export default TaskItem
```

### TaskList.tsx
src/components/TaskList.tsx

```
import React from 'react'
import TaskItem from './TaskItem'
import { Task } from './Types'
 
type Props = {
    tasks: Task[]
    setTasks: React.Dispatch<React.SetStateAction<Task[]>>
}
 
const TaskList: React.FC<Props> = ({ tasks, setTasks }) => {
 
    const handleDone = (task: Task) => {
        setTasks(prev => prev.map(t =>
            t.id === task.id
                ? { ...task, done: !task.done }
                : t
        ))
    }
 
    const handleDelete = (task: Task) => {
        setTasks(prev => prev.filter(t =>
            t.id !== task.id
        ))
    }
 
    return (
        <div className="inner">
        {
            tasks.length <= 0 ? '登録されたTODOはありません。' :
            <ul className="task-list">
            { tasks.map( task => (
                <TaskItem
                    key={task.id}
                    task={task}
                    handleDelete={handleDelete}
                    handleDone={handleDone}
                />
            )) }
            </ul>
        }
        </div>
    )
}
 
export default TaskList

```

### TaskInput.tsx
src/components/TaskInput.tsx
```
import React, { useState } from 'react';
import { Task } from './Types';

type Props = {
  setTasks: React.Dispatch<React.SetStateAction<Task[]>>;
  tasks: Task[];
};

const TaskInput: React.FC<Props> = ({ setTasks, tasks }) => {
  const [inputTitle, setInputTitle] = useState<string>('');
  const [count, setCount] = useState<number>(tasks.length + 1);

  const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInputTitle(e.target.value);
  };

  const handleSubmit = () => {
    setCount(count + 1);

    const newTask: Task = {
      id: count,
      title: inputTitle,
      done: false,
    };

    setTasks([newTask, ...tasks]);
    setInputTitle('');
  };

  return (
    <div>
      <div className='inputForm'>
        <div className='inner'>
          <input
            type='text'
            className='input'
            value={inputTitle}
            onChange={handleInputChange}
          />
          <button onClick={handleSubmit} className='btn is-primary'>
            追加
          </button>
        </div>
      </div>
    </div>
  );
};

export default TaskInput;

```

### App.tsx書き換え
```
import React, { useState } from 'react';
import TaskList from './components/TaskList';
import TaskInput from './components/TaskInput';
import { Task } from './components/Types';
import './App.css';

const initialState: Task[] = [
  {
    id: 2,
    title: '次にやるやつ',
    done: false,
  },
  {
    id: 1,
    title: 'はじめにやるやつ',
    done: true,
  },
];

const App: React.FC = () => {
  const [tasks, setTasks] = useState(initialState);

  return (
    <div>
      <TaskInput setTasks={setTasks} tasks={tasks} />
      <TaskList setTasks={setTasks} tasks={tasks} />
    </div>
  );
};

export default App;
```

<!-- ## styled-componentsに一意のclassを張れるようにする。
yarn add styled-components @types/styled-components -->

<!-- あとは授業内でやればいいかな。 -->