スポンサーリンク

【React】Todoアプリ作成してみた②-②

【React】Todoアプリ作成してみた②-②JavaScript
【React】Todoアプリ作成してみた②-②
スポンサーリンク

「ReactでTodoアプリを作成してみた②-①」の続きです。

ReactでTodoアプリ作成してみた②-①

ステータスごとのTodoリストとチェックボタンを押下するとTodoが移動する処理を作成していきます。

ワイヤーフレーム

React Todo App

Todoアプリの構成

TodoListOrganisms.jsx、TodoFormOrganisms.jsxを追加しました。

変更した内容についはコンポーネントの説明にて行います。

src
│  index.jsx
│
├─components
│      TodoForm.jsx
│      TodoList.jsx
│
├─context
│      TodoProvider.jsx
│
├─organisms
│      TodoFormOrganisms.jsx
│      TodoListOrganisms.jsx
│
└─pages
        TodoPage.jsx

コンポーネントの説明

TodoProvider.jsx

3種類のTodoListをオブジェクトで保存するようにuseStateの初期値を変更しました。

statusNameを条件としてtaskListの値を画面に一覧表示していきます。

import { useState, createContext } from 'react';
export const TodoContext = createContext();
export const TodoProvider = ({ children }) => {
    const [todo, setTodo] = useState('');
    const [todoList, setTodoList] = useState([
        { statusName: 'NOT STARTED', taskList: [] },
        { statusName: 'STARTED', taskList: [] },
        { statusName: 'COMPLETION', taskList: [] },
    ]);
    return (
        <TodoContext.Provider value={{ todo, setTodo, todoList, setTodoList }}>
            {children}
        </TodoContext.Provider>
    );
};

TodoPage.jsx

TodoFormOrganismsコンポーネントに値入力用Formを、TodoListOrganismsコンポーネントに3種類のTodoリストを表示するように変更しています。

import { TodoFormOrganisms } from '../organisms/TodoFormOrganisms';
import { TodoListOrganisms } from '../organisms/TodoListOrganisms';

export const TodoPage = () => {
    return (
        <>
            <TodoFormOrganisms />
            <TodoListOrganisms />
        </>
    );
};

TodoFormOrganisms.jsx

入力フォームを表示するコンポーネントとなります。

useContextで定義したstateを呼び出してpropsとして子コンポーネントに渡していきます。

import { useContext } from 'react';
import { TodoContext } from '../context/TodoProvider';
import { Typography, Grid } from '@mui/material';
import { TodoForm } from '../components/TodoForm';

export const TodoFormOrganisms = () => {
    const { todo, setTodo, todoList, setTodoList } = useContext(TodoContext);
    return (
        <Grid container>
            <Grid item lg={4}>
                <Typography variant='h5' component='h1'>
                    {'TODO APP'}
                </Typography>
                <TodoForm
                    todo={todo}
                    setTodo={setTodo}
                    todoList={todoList}
                    setTodoList={setTodoList}
                />
            </Grid>
        </Grid>
    );
};

TodoForm.jsx

オブジェクトのtaskList配列に値を追加するようmap関数の処理を変更しています。

todoList.map((value) => { return value.statusName === 'NOT STARTED' ? { statusName: 'NOT STARTED', taskList: [...value.taskList, todo] } : value;
})

ステータスがNOT STARTEDの時に画面で入力された値をtaskListに追加したオブジェクトを返却します。NOT STARTEDでなければ現在のオブジェクトを返却します。

import { Box, TextField, IconButton } from '@mui/material';
import AddIcon from '@mui/icons-material/Add';
export const TodoForm = (props) => {
    const { todo, setTodo, todoList, setTodoList } = props;
    return (
        <Box
            sx={{
                width: 250,
            }}
        >
            <TextField
                id='inputTodo'
                label=''
                type='text'
                name='inputTodo'
                size='small'
                sx={{ maxWidth: 160 }}
                value={todo}
                onChange={(e) => {
                    console.log('inputTodo:onChange');
                    setTodo(e.target.value);
                }}
                variant='outlined'
            />
            <IconButton
                aria-label='add'
                size='large'
                type='submit'
                onClick={() => {
                    console.log('addButton:onClick');
                    setTodoList(
                        todoList.map((value) => {
                            return value.statusName === 'NOT STARTED'
                                ? { statusName: 'NOT STARTED', taskList: [...value.taskList, todo] }
                                : value;
                        })
                    );
                }}
            >
                <AddIcon sx={{ fontSize: 18 }} />
            </IconButton>
        </Box>
    );
};

TodoListOrganisms.jsx

3種類のTodoリストを表示するコンポーネントとなります。

map関数を使用してMaterial-UIのGridで3分割したTodoListコンポーネントを表示させます。

propsとしてTodoProvider.jsxで定義したstatusNameとindex,3種類のオブジェクトを定義したtodoListと状態更新関数を渡します。TodoListコンポーネントを共通コンポーネントとして利用しているため、statusNameとindexはTodoListコンポーネント内でステータスによってTodoリストの移動、削除、更新を行うかの条件分岐に利用します。

import { useContext } from 'react';
import { TodoContext } from '../context/TodoProvider';
import { Typography, Grid } from '@mui/material';
import { TodoList } from '../components/TodoList';

export const TodoListOrganisms = () => {
    const { todoList, setTodoList } = useContext(TodoContext);
    return (
        <Grid container>
            {todoList.map((value, index) => {
                return (
                    <div key={index}>
                        <Grid item lg={4}>
                            <Typography variant='h6' component='h1'>
                                {value.statusName}
                            </Typography>
                            <TodoList
                                statusName={value.statusName}
                                statusIndex={index}
                                todoList={todoList}
                                setTodoList={setTodoList}
                            />
                        </Grid>
                    </div>
                );
            })}
        </Grid>
    );
};

TodoList.jsx

ステータスごとにTodoを移動する処理を追加しました。削除、編集の処理を3種類のTodoリストに対して行えるよう変更しています。

mapやfilter関数を利用すると複雑な処理となるため可読性を重視してObject.assign関数を使用しました。Object.assign関数はオブジェクトをコピーすることができます。

let copyTodoList = Object.assign([],JSON.parse(JSON.stringify(todoList)));

JSON.parse(JSON.stringify(todoList)でオブジェクトをディープコピーしています。JSON.parseなしだとシャロ―コピーとなるためコピー先のオブジェクト更新時に元のオブジェクトが更新されてしまうので取り扱いに注意が必要となります。

今回追加したチェックボタンを押下するとTodoが次のステータスに移動する処理です。

if (statusName === 'NOT STARTED') {
   copyTodoList[statusIndex].taskList.splice(index, 1);
   copyTodoList[1].taskList.unshift(todoList[statusIndex].taskList[index]);
}

ステータスがNON STARTEDのチェックボタンを押下した場合、チェックされたTodoを削除後、STARTEDに移動する。spliceでNON STARTEDの要素を1件削除、unshiftでSTARTに要素を1件追加しています。これらの処理を3パターン分用意しました。

削除と編集の処理も同じようにObject.assign関数でディープコピーを行い、ディープコピーしたオブジェクトに対して削除、編集処理を行っています。処理終了後にuseStateでオブジェクトを保存します。

import { Box, TextField, IconButton } from '@mui/material';
import DeleteIcon from '@mui/icons-material/Delete';
import DoneIcon from '@mui/icons-material/Done';

export const TodoList = (props) => {
    const { statusName, statusIndex, todoList, setTodoList } = props;
    return (
        <Box
            sx={{
                width: 250,
            }}
        >
            {todoList[statusIndex].taskList.map((todo, index) => {
                return (
                    <div key={index}>
                        <IconButton
                            aria-label='move'
                            size='large'
                            type='submit'
                            onClick={(e) => {
                                console.log('moveButton:onClick');

                                let copyTodoList = Object.assign(
                                    [],
                                    JSON.parse(JSON.stringify(todoList))
                                );

                                if (statusName === 'NOT STARTED') {
                                    copyTodoList[statusIndex].taskList.splice(index, 1);
                                    copyTodoList[1].taskList.unshift(
                                        todoList[statusIndex].taskList[index]
                                    );
                                }

                                if (statusName === 'STARTED') {
                                    copyTodoList[statusIndex].taskList.splice(index, 1);
                                    copyTodoList[2].taskList.unshift(
                                        todoList[statusIndex].taskList[index]
                                    );
                                }

                                if (statusName === 'COMPLETION') {
                                    copyTodoList[statusIndex].taskList.splice(index, 1);
                                    copyTodoList[0].taskList.unshift(
                                        todoList[statusIndex].taskList[index]
                                    );
                                }

                                setTodoList(copyTodoList);
                            }}
                        >
                            <DoneIcon sx={{ fontSize: 18 }} />
                        </IconButton>
                        <TextField
                            id='outputTodo'
                            label=''
                            name='outputTodo'
                            size='small'
                            sx={{ maxWidth: 160 }}
                            value={todo}
                            onChange={(e) => {
                                console.log('TodoList:onChange');

                                let copyTodoList = Object.assign(
                                    [],
                                    JSON.parse(JSON.stringify(todoList))
                                );

                                if (statusName === 'NOT STARTED') {
                                    copyTodoList[statusIndex].taskList[index] = e.target.value;
                                }

                                if (statusName === 'STARTED') {
                                    copyTodoList[statusIndex].taskList[index] = e.target.value;
                                }

                                if (statusName === 'COMPLETION') {
                                    copyTodoList[statusIndex].taskList[index] = e.target.value;
                                }

                                setTodoList(copyTodoList);
                            }}
                            variant='standard'
                        />
                        <IconButton
                            aria-label='delete'
                            size='large'
                            type='submit'
                            onClick={() => {
                                console.log('deleteButton:onClick');

                                let copyTodoList = Object.assign(
                                    [],
                                    JSON.parse(JSON.stringify(todoList))
                                );

                                if (statusName === 'NOT STARTED') {
                                    copyTodoList[statusIndex].taskList.splice(index, 1);
                                }

                                if (statusName === 'STARTED') {
                                    copyTodoList[statusIndex].taskList.splice(index, 1);
                                }

                                if (statusName === 'COMPLETION') {
                                    copyTodoList[statusIndex].taskList.splice(index, 1);
                                }

                                setTodoList(copyTodoList);
                            }}
                        >
                            <DeleteIcon sx={{ fontSize: 20 }} />
                        </IconButton>
                    </div>
                );
            })}
        </Box>
    );
};

完成したTodoアプリ

フォームからTodoを追加後にチェックボタンを押下すると次のステータスに移動します。

各ステータスごとのTodoリストは編集、削除も可能となっています。

React_TodoApp
GitHub - TokutyTok/react-todoApp at feature/react_todoApp_v1.3.0
react_todoApp. Contribute to TokutyTok/react-todoApp development by creating an account on GitHub.

今回学んだこと

React hook useState オブジェクトの状態管理 オブジェクトのCRUD

ディープコピーとシャロ―コピー

map,filter関数以外での移動、削除、編集の方法

Material-UI Gridを使ったレイアウト

おすすめの書籍

JavaScriptのおすすめの書籍です。

基本的に構文が載っているため、教科書代わりに使える内容となっています。

Reactの前にとてもお世話になりました。JavaScriptをこれから始める方におすすめです。

おすすめのUdemy講座

ReactでおすすめのUdemy講座を紹介します。

モダンJavaScriptの基礎から始める挫折しないためのReact入門

ReactはJavaScriptの基本的な構文を使用するため、Reactだけの理解では足りないでしょう。

こちらのReact講座は、JavaScriptでのTODOアプリを作成してからReactでのTODOアプリを作成するため、ReactだけではなくJavaScriptを基礎から学ぶことができます。

Reactからいきなり入らずにJavaScriptの基礎から学べる点が、初心者にとてもおすすめできる内容となっています。

【Reactアプリ開発】3種類のReactアプリケーションを構築して、Reactの理解をさらに深めるステップアップ講座

APIを使用してデータを取得したり、react-router-domでのページ遷移を学べるためより実践的な内容となっています。

3種類のReactアプリケーションを作成することができるため、Reactの基礎的な内容を学んだ人におすすめです。

タイトルとURLをコピーしました