2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ReactとかNextを使うことになったから頑張る③

Posted at

はじめに

①の記事:https://qiita.com/atsu123456789/items/c055959b288f53841324
②の記事:https://qiita.com/atsu123456789/items/9fc5b6bf02dafd9e0212

参考にしているもの↓

今回の記事ではReactのみを使ったTodoアプリの開発についてです。
バックエンドやDBなどは使っていないので、データの保存等はできませんが、Reactをどう動かしていくのかのイメージが大分つくのではないでしょうか。

対象者

  • 基本的なプログラミングの考え方が分かる(メソッドなど)
  • 最低限のJavascriptが分かる(自信ない方は①の記事へGo)
  • 最低限のReactの書き方が分かる(自信ない方は②の記事へGo)
  • ReactやNext.jsに興味がある
  • 最低限のHTML、CSSが分かる

ひな形の作成

index.js
import React from 'react';
import ReactDom from 'react-dom'
import { App } from './App';

ReactDom.render(<App />, document.getElementById("root"));
App.jsx
import React from "react"
import './App.css';

export const App = () => {
  return (
    <>
      <div className="input-area">
        <input placeholder='TODOを入力' />
        <button>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          <div className="list-row">
            <li>ああああ</li>
            <button>完了</button>
            <button>削除</button>
          </div>
          <div className="list-row">
            <li>いいいい</li>
            <button>完了</button>
            <button>削除</button>
          </div>
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          <div className="list-row">
            <li>うううう</li>
            <button>戻す</button>
          </div>
        </ul>
      </div>
    </>
  );
};

CSSは適当に設定しています。
するとこんな感じ。

image.png

未完了のTODOのコンポーネント化

useStateを使って

const [incompleteTodos, setIncompleteTodos] = useState(['ああああ', 'いいいい']);

こうしてmap関数を用いることで

App.jsx
<div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          { incompleteTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>完了</button>
                <button>削除</button>
              </div>
            )
          }) }
        </ul>
      </div>

このように書き換えることが出来る。
ここでreturn内のdivタグにkeyを設定する。
これはレンダリングする際にkeyの変化のみを書き換えてねーっていうことを教えてあげるものらしい

完了のTODOのコンポーネント化

先ほどと同様にして完了のTODOも書き換える

const [completeTodos, setCompleteTodos] = useState(['うううう']);
App.jsx
<div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo) => {
            return (
              <div key={todo} className="list-row">
                <li>{ todo }</li>
                <button>戻す</button>
              </div>
            );
          })}
        </ul>
      </div>

追加ボタン押下時の処理

image.png
こいつを押したときに未完了のTODOに内容を追加する。

まずApp.jsxの内容を書き換える

App.jsx
<div className="input-area">
    <input
      placeholder='TODOを入力'
      value={todoText}
      onChange={onChangeTodoText}
    />
    <button onClick={onClickAdd}>追加</button>
</div>

これでinputタグ内に文字を入力すると、onChangeTextという関数が、buttonクリック時にはonClickAddという関数が働く。

ではそれぞれの関数の処理を書いていく。

App.jsx
  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

onChangeTodoTextでは、inputタグの変化に応じてその値を取得し、toDoTextというStateで定義した値の中に入れる。

onClickAddでは上で指定したtodoTextが空でない場合に限り、incompleteTodos配列にtodoTextを追加し、新たなリストとして、Stateで定義したincompleteTodosに格納している。

こうすることで追加ボタン押下時に未完了のTODOに値が入るようになった。

image.png

削除の機能

image.png

次はこの削除ボタン押下時に未完了のTODOから要素が削除されるように実装していく。

まずは削除ボタン押下時に処理が走ってほしいので

App.jsx
<ul>
          { incompleteTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button>完了</button>
                <button onClick={() => onClickDelete(index)}>削除</button>
              </div>
            )
          }) }
        </ul>

onClickDeleteという関数を与える。
この時、何番目の要素を削除するかをわからせるためにmap関数でindexをとり、それをonClickDeleteに渡している。
※Reactでは関数に引数を渡したいとき、

<button onClick={() => onClickDelete(index)}>削除</button>

このように、アロー関数の形で書く必要がある。

では実際にonClickDeleteの処理を書いていく。

App.jsx
  const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

やっていることは至極単純で、incompleteTodosからspliceを用いてindex番号目の要素を削除し、その配列をincompleteTodosに渡しているだけである。

これで削除機能の実装ができた。

完了機能の実装

image.png

次に完了ボタン押下時に、未完了のTODOから完了のTODOへ移行させる処理を書く。

削除の時と同様、完了ボタン押下時に処理を走らせたいので

App.jsx
<ul>
  { incompleteTodos.map((todo, index) => {
    return (
      <div key={todo} className="list-row">
        <li>{todo}</li>
        <button onClick={() => onClickComplete(index)}>完了</button>
        <button onClick={() => onClickDelete(index)}>削除</button>
      </div>
    )
  }) }
</ul>

完了ボタン押下時にonClickCompleteという関数を呼び出す。
ではonClickCompleteの処理を書いていく。

App.jsx
  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

ここも先ほどの削除と似たような処理を行っており、やっていることとしては
①incompleteTodosからnewIncompleteTodosを作成
②newIncompleteTodosのindex番目の要素を削除
③completeTodosとincompleteTodosのindex番目の要素を連結した配列をnewCompleteTodosとして定義
④incompleteTodos、completeTodosの中身を更新

これで完了ボタンの実装ができた。

戻るボタンの実装

image.png

戻すボタン押下時に完了のTODOから未完了のTODOに移行するように実装していく。

中身的には先ほどの完了の処理とほとんど同じである。

App.jsx
<ul>
  {completeTodos.map((todo, index) => {
    return (
      <div key={todo} className="list-row">
        <li>{ todo }</li>
        <button onClick={() => onClickBack(index)}>戻す</button>
      </div>
    );
  })}
</ul>

これでbutton押下時にonClickBackという関数が走るので、その関数を実装する

App.jsx
  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };

こちらは先ほどの完了ボタンとほぼ逆のことをしているだけであり、
①completeTodosからnewCompleteTodosを作成
②newCompleteTodosのindex番目の要素を削除
③incompleteTodosとcompleteTodosのindex番目の要素を連結した配列をnewIncompleteTodosとして定義
④completeTodos、incompleteTodosの中身を更新

これで戻る機能の実装ができた。

↓App.jsxの最終形

App.jsx
import React from "react"
import { useState } from "react";
import './App.css';

export const App = () => {
  const [todoText, setTodoText] = useState('');
  const [incompleteTodos, setIncompleteTodos] = useState([]);
  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

  const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };

  return (
    <>
      <div className="input-area">
        <input
          placeholder='TODOを入力'
          value={todoText}
          onChange={onChangeTodoText}
        />
        <button onClick={onClickAdd}>追加</button>
      </div>
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>
        <ul>
          { incompleteTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{todo}</li>
                <button onClick={() => onClickComplete(index)}>完了</button>
                <button onClick={() => onClickDelete(index)}>削除</button>
              </div>
            )
          }) }
        </ul>
      </div>
      <div className="complete-area">
        <p className="title">完了のTODO</p>
        <ul>
          {completeTodos.map((todo, index) => {
            return (
              <div key={todo} className="list-row">
                <li>{ todo }</li>
                <button onClick={() => onClickBack(index)}>戻す</button>
              </div>
            );
          })}
        </ul>
      </div>
    </>
  );
};

コンポーネント化

ここまで進めると、とりあえず動くTodoアプリができた。
ただApp.jsxをみると、長いファイルとなっており、どこに何の処理をしているかが分かりにくい。
そこでコンポーネントの分割を行っていく。

./components/InputTodo.jsx
import React from 'react';

export const InputTodo = (props) => {
	const { todoText, onChange, onClick } = props;
	return (
		<div className="input-area">
			<input
			placeholder='TODOを入力'
			value={todoText}
			onChange={onChange}
			/>
			<button onClick={onClick}>追加</button>
    	</div>
	);
};
App.jsx
{/* ファイルのよびだしを忘れずに! */}
import { InputTodo } from './components/InputTodo'


{/* 40行目くらい */}
<InputTodo todoText={todoText} onChange={onChangeTodoText} onClick={onClickAdd} />
      <div className="incomplete-area">
        <p className="title">未完了のTODO</p>

./components/InputTodo.jsxは先ほどのApp.jsxのinputの部分を別のファイルに書き換えたものである。

App.jsxでInputTodoを呼び出し、引数としてtodoText、onChange、onClickを渡し、これを./components/InputTodo.jsxの中でpropsから分割代入することで値を取得している。

どうように他の要素もコンポーネント化すると先ほどの最終形が以下のようになる

App.jsx
import React from "react"
import { useState } from "react";
import './App.css';
import { InputTodo } from './components/InputTodo'
import { IncompleteTodos } from "./components/IncompleteTodos";
import { CompleteTodos } from "./components/CompleteTodos";

export const App = () => {
  const [todoText, setTodoText] = useState('');
  const [incompleteTodos, setIncompleteTodos] = useState([]);
  const [completeTodos, setCompleteTodos] = useState([]);

  const onChangeTodoText = (e) => setTodoText(e.target.value);

  const onClickAdd = () => {
    if (todoText === "") return;
    const newTodos = [...incompleteTodos, todoText];
    setIncompleteTodos(newTodos);
    setTodoText("");
  };

  const onClickDelete = (index) => {
    const newTodos = [...incompleteTodos];
    newTodos.splice(index, 1);
    setIncompleteTodos(newTodos);
  };

  const onClickComplete = (index) => {
    const newIncompleteTodos = [...incompleteTodos];
    newIncompleteTodos.splice(index, 1);

    const newCompleteTodos = [...completeTodos, incompleteTodos[index]];
    setIncompleteTodos(newIncompleteTodos);
    setCompleteTodos(newCompleteTodos);
  };

  const onClickBack = (index) => {
    const newCompleteTodos = [...completeTodos];
    newCompleteTodos.splice(index, 1);

    const newIncompleteTodos = [...incompleteTodos, completeTodos[index]];
    setCompleteTodos(newCompleteTodos);
    setIncompleteTodos(newIncompleteTodos);
  };

  return (
    <>
      <InputTodo
        todoText={todoText}
        onChange={onChangeTodoText}
        onClick={onClickAdd}
      />
      <IncompleteTodos
        todos={incompleteTodos}
        onClickComplete={onClickComplete}
        onClickDelete={onClickDelete}
      />
      <CompleteTodos
        todos={completeTodos}
        onClickBack={onClickBack}
      />
    </>
  );
};

だいぶスッキリ。

image.png

最終形はこんな感じで追加や完了などのボタンを押すと動きます。Reactってすごいね

最後に

ここまで読んでくれてありがとうございます。
とりあえずここまでで自分自身、なんとなくReactが見えてきたのでもう少し使い慣れたらNext.jsいじってみたいと思います。
④の記事は当分先になるかも(m´・ω・`)m ゴメン…

参考文献

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?