個人開発チームBUILD UP 開発者ブログ

【ToDoアプリを作る】React Hooksを使って繰り返し処理をする。

今回もToDoアプリの制作について解説します。

今回はインプットボックスに文字を入力して「登録」ボタンをクリックするとリストにタスクが追加されるところまでを作ります。

今回の記事のメインの繰り返し処理については記事の後半で解説します。

前回までのコードを修正しますので、まだ過去の記事を見ていない方は以下の記事を参考にしていただければと思います。

コンポーネントの修正

まずはコンポーネントの修正を行います。

今回の記事では以下の3つのコンポーネントを修正します。

  • input.tsx
  • button.tsx
  • list.tsx

それでは一つずつ見ていきましょう。

input.tsx

今回の修正で親コンポーネントであるindex.tsxからpropsでデータを受け取ることになるので、typeを使ってpropsに型を定義しましょう。

type Props = {
  value: string,
  onChange: (event: React.ChangeEvent<HTMLInputElement>) => void
}

valueはインプットボックスに入力されるタスクを管理するための文字列型のステートを親コンポーネントから受け取ります。

onChangeはインプットボックスに文字が入力されるたびにイベントが発生するようにしますので、イベントの型として、(event: React.ChangeEvent<HTMLInputElement>) => voidと記述します。

イベントに対する方の書き方は、イベントの種類とHTMLの要素(今回だとインプットボックス)の組み合わせで決まります。

これ以外でイベントに型を定義する場合は、インターネットで調べると書き方に関する情報がたくさん見つかりますので参考にしてみてください。

続いて、親コンポーネントから受け取ったpropsを使用できるように準備します。

const DemoInput = (props: Props) => {
  const { value, onChange } = props;


}

DemoInputの引数としてpropsを渡します。

型は先ほど記述したPropsを割り当てます。

const { value, onChange } = props; この部分は分割代入という記述の方法をしております。

通常だと、propsで受け取ったデータを使用するために、props.valueというようにpropsを頭につける必要がありますが、分割代入をしておくことで、都度props.を書く必要がなくなります。

最後にreturnの中身を修正します。

const DemoInput = (props: Props) => {
  const { value, onChange} = props;

  return (
    <>
      <TextInput
        value={value}
        label="タスクを入力してください。"
        defaultValue={value}
        style={{ flex: 1 }}
        onChange={event => onChange(event)}
      />
    </>
  );
}

TextInputタグの中にvalueとonChangeを追加する必要があります。

後ほどindex.tsxの編集時に説明しますが、onChangeイベントで文字が入力されるたびにvalueの値を更新する記述としています。

button.tsx

次にbutton.tsxを修正します。

input.tsxと修正事項は概ね同じです。

まずは定義している型のうち、onClickイベントの型を修正します。

type Props = {
  value: string,
  onClick: (event: React.MouseEvent<HTMLButtonElement>) => void
}

前回まで、onCliskイベントの型をonClick: () => voidとしていたところを
onClick: (event: React.MouseEvent<HTMLButtonElement>) => void に変更します。

先ほどのinput.tsxと異なるところとして、マウスのクリック時に発生するイベントなので、MouseEventを使用しております。

また、HTMLの要素としてはインプットボックスではなく、ボタンを使用しますので、<HTMLButtonElement>を使用しています。

次にreturn内の<Button>タグに追加しているonClickイベントを以下の通り変更します。

const DemoButton = (props: Props) => {
  const { value, onClick } = props;

  return (
    <Button 
      variant="default" 
      color="orange" 
      radius="lg" 
      size="lg" 
      loaderPosition="center" 
      compact 
      uppercase 
      onClick={onClick}
    >
      {props.value}
    </Button>
  );

}

前回までの記述の場合、ボタンをクリックしても特に何も起こらない記述となっていたのですが、今回はクリックした際propsから渡されたonClickという関数を実行するように変更しております。

button.tsxの修正については以上になります。

list.tsx

最後にlist.tsxを修正します。

まずはprops用の型を定義します。

type taskType = {
  taskItem: string
  id: number 
}

type Props = {
  taskList: taskType[]
}

taskTypeについては、各タスク名と管理番号をそれぞれ文字列型と数値型で定義しており、各タスクをリストとして管理するための配列用にPropsという型を定義しております。

続いてfunctionとしている箇所をconstに変更して、propsを受け取れるようにします。

const DemoList = (props:Props) => {

  const{taskList} = props;

}

受け取るデータはtaskListのみとなります。

後ほどindex.tsxを修正する際にtaskListをいう名前のデータを渡すように記述しますが、双方の名前が統一されていれば自由に名前をつけていただいて問題ありません。

続いてreturnの中身を修正します。

ここで今回の記事の肝となる繰り返し処理について説明します。

繰り返し処理とは配列に含まれているデータの一つ一つに対して同様の処理を行う場合に使用します。

今回の場合だと、タスクの一つ一つに対して完了ボタンと合わせて表示する表示するために繰り返し処理を使用しています。

繰り返し処理の方法として、mapの使用方法をご紹介します。

mapは配列名.mapと記述することで使用することができます。

今回の場合では、index.tsxからtaskListという名前の配列を受け取り、繰り返し処理を行うので、taskList.mapと記述します。

繰り返し処理を行う中で配列内の各データに対する呼び名をつける必要があるので、map以降に引数として名前をつけます。

taskList((taskItem) => {} このように書くことで切り返し処理中の配列内の各データはtaskItemと記述することで引き出すことができるようになりました。

このtaskItemにはタスク名と管理番号となるidが含まれており、オブジェクトとして扱われます。

またmapによる繰り返しの具体的な処理内容は波括弧{}内に記述します。

これらを踏まえて以下の通りコードを修正します。

const DemoList = (props:Props) => {

  const{taskList} = props;
  return (
    <ul>
        {taskList.map((taskItem) => {
            return(
                <li key={index}>
                  {taskItem.taskItem}
                  <DemoButton 
                    key={index}
                    value={"完了"} 
                    onClickFunction={()=>handleComplete(taskItem.id)}
                  ></DemoButton>
                </li>
            )
        })}
    </ul>

  );
}

波括弧{}内でreturn()を書いて戻り値としてリストを表示するようにしております。

そのリストの内容としてはtaskItemというオブジェクトからタスク名であるtaskItemを引き出して表示しています。

あわせてボタンコンポーネントを呼び出しており、タスク名とセットで一つのリスト項目を構成しております。

そして、配列内の全てのデータに対してこの同じ処理を繰り返します。

liタグの中にkeyを設定してindexの番号を割り当てております。

mapを使って配列を処理する際にkeyを設定することが推奨されており、この設定をしておかないと後々エラーが発生する原因となりますので記述しておきましょう。各ボタンにkeyが割り当てられますが、表示されることはありません。

今回から、propsで受け取った配列を使用しますので、最初に仮のデータとしていたconst elements:string[] = [“task1”, “task2”, “task3”]は削除して構いません。

以上が各コンポーネントの修正となります。

親コンポーネントのindex.tsxを修正する。

子コンポーネントの修正が完了したので、次は親コンポーネントの修正をします。

今回はReact Hooksの一つであり、タスクの状態を管理するためのuseStateを使用しますので、まず初めにimportするために以下の一文を1行目に追加してください。

import {useState} from react

続いてtypeを使って型の定義をします。

タスク名用に文字列型と管理番号とするidを数値型で定義します。

型の不一致によるエラーが発生してしまうので、list.tsxにて定義しているtaskTypeと同じ内容にしておきましょう。

export type taskType = {
  taskItem: string
  id: number 
}

次に、useStateの初期設定をします。

初期設定はconstと配列[]の形で記述します。配列の第一引数は状態管理をするための管理名、第二引数には管理名の頭にsetをつけたものを記述します。
第二引数はキャメルケースで記述しますので、管理名の頭文字は大文字としてください。

この第二引数に記述している値を使うことで、stateの更新をすることができます。

更新方法は後ほど説明します。

例: cosnt [task, setTask]

配列[]の後に= useState<型>(初期値)を記述します。

型は管理するデータに合わせて記述します。
先ほどtypeで定義したtaskTypeを使うことも可能です。

useState<string>、useState<taskType>のように記述します。
また、<taskType[]>と書くことで、typeで定義した型を配列全体に反映させることも可能です。

最後にカッコ()の中に初期値を記述します。

値を直接書くこともできますし、定数などを書くことも可能です。

今回のToDoアプリでは初期値を空にしますので(“”)としたり、([])のように空の配列を入れたりします。

上記を踏まえて今回使用するuseStateを二つ初期設定します。

  const [taskItem, setTaskItem] = useState<string>("")
  const [taskList, setTaskList] = useState<taskType[]>([])

一つ目のtaskItemは一つのタスクがリストに登録されるまでの状態を管理するための一時的なstateです。

登録者がタスクを入力をして登録ボタンが押されるまでの間使用されます。

登録ボタンが押されるとstateに一時的に入力されたタスクの内容を確定してリストに移します。

その後stateをクリアして次のタスク登録処理がされるまでは空の状態となります。

二つ目のtaskListはtaskItemから受けとったタスク内容を保管して配列の形で複数データを保持するために使用します。

いずれも初期値はからの状態にしております。

関数を作成する

今回、三つの関数を作成する必要があります。

少し内容が複雑に感じるかもしれませんので一つずつ解説していきます。

タスクリスト更新用の関数

まず一つ目はtaskListにタスクを追加するための関数です。

コードは以下になります。

  const makeList = (taskItem: taskType) => {
    setTaskList([...taskList, taskItem]);
  };

makeListという名前で関数を作成します。

ここではアロー関数を使用して引数にtaskItemを使用します。

先ほどuseStateの第二引数はstateの更新時に使用すると説明しました。

setTaskListを使うと、taskListの状態をカッコ()内に記載した内容に更新することができます。

カッコ()内には配列[]があり、さらにその中には…taskListtaskItemが書かれています。

…taskListについては、現在のtaskListの内容を意味しており、taskListに複数のタスクが登録されている場合、そのすべてのタスクをstateの更新時にそのまま引き継いでいます。

二つ目にtaskItemが書かれておりますが、これは先ほど説明した通り、タスクが入力されて登録されるまで間のタスクデータが管理されています。

つまり、この関数が呼び出されると今まで登録されていたタスクに加えて、今回新たなタスクが追加されたtaskListに更新することを意味しています。

taskItem更新用の関数

二つ目はtaskItemを更新するための関数を作成します。

コードは以下となります。

  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setTaskItem(event.target.value)
  }

handleInputChangeという名前作成しています。

引数にeventを渡しています。

内容と指定はHTMLのインプットボックスに変更が発生した場合、その変更内容を型として定義していますが、決まり文句のようなものなので上記の通り記述していただければと思います。

内容はシンプルでsetTaskItem(event.target.value)となっております。

この関数が実行されると、taskItemがevent.target.valueに更新されます。

event.target.valueが何かというとHTMLのインプットボックスに変更が生じた際の実際に変更されたデータそのものになります。

簡単にいうとターゲットとなっているテキストボックスの中身がtaskItemの中身になります。

例えば「あ」と入力されることで変更が発生し、taskItemの内容が「あ」に更新されます。

次に「い」と入力すると、今度はインプットボックスの中身が「あい」になるのでtaskItemは「あい」となります。

登録者が一文字入力するたびにstateを更新し続けます。

そして先ほどのmakeList関数を実行した際にその時のtaskItemがtaskListに追加される仕組みになっています。

登録ボタンをクリックした際にすべての処理を実行する関数

最後にaddTodoという関数を作ります。

  const addTask = () => {
    const newTask = {
      taskItem: taskItem,
      id: Math.floor(Math.random() * 1e5),
    };
    makeList(newTask);
    setTaskItem("");
  };

addTaskが実行してされると定数であるnewTaskがmakeList関数に渡されます。

newTaskの中身はtaskItemとタスクを管理するランダムな数字のidがオブジェクトとして格納されています。

Math.floor()は整数を取得するために使用しています。整数とする数字はというとカッコ()内のMath.random()でランダムな数字を生成し、ここに10の5乗をかけることで5けたのランダムなidを生成しています。

addTaskが実行されたタイミングで、インプットボックスに入力されている文字列と、生成されたランダムな数字のオブジェクトが引数となってmakeList関数が発動します。

少しややこしくなりますが、taskListで受けとったtaskItemの中身はここで宣言されているnewTaskになりますので、タスク名と管理用idの二つが配列に追加されます。

そして最後にsetTaskItemによってtaskItemの中身は空の文字列(””)となっております。

したがって、インプットボックスの中身をクリアすることになります。

JSX内の記述を修正する。

前回までで作成しているindex.tsxのreturn内の記述を少し修正します。

少し長くなりますが、<Container>タグの中身をすべて以下に書きました。

そのうち、修正した箇所についてはアンダーラインを引いております。

修正内容としては、<DemoInput>タグ、<DemoButton>タグ、<DemoList>タグに関数や定数を渡しています。

        <Container    bg="rgba(0, 0, 0, .3)">
          <Grid justify="center" align="center">
            <Grid.Col span={6}>
              <DemoInput
                value={taskItem} 
                onChange={handleInputChange}
              ></DemoInput>
            </Grid.Col>


            <Grid.Col span={3}>
              <Flex
                mih={50}
                gap="md"
                justify="flex-start"
                align="center"
                direction="row"
                wrap="wrap"
              >
                <DemoButton
                  value={"タスクを登録"} onClick={addTask}
                ></DemoButton>
              </Flex>
            </Grid.Col>
          </Grid>


          <Flex
            mih={50}
            gap="md"
            justify="flex-start"
            align="center"
            direction="column"
            wrap="wrap"
          >
            <DemoList
              taskList={taskList}
            ></DemoList>
          </Flex>
        </Container>

DemoInputにはonChangeとしてhandleInputChange関数を渡しています。

こうすることでインプットボックスに変更が加えられるたびに関数が実行され、先述された通り、stateの更新が文字入力のたびに行われます。

DemoButtonにはonClickとしてaddTask関数を渡しています。

ボタンがクリックされたタイミングで関数が実行されて、インプットボックス内の文字をリストに追加し、インプットボックスを空にする一連の操作が実行されます。

DemoListにはtaskListの配列を渡しています。

taskListを受けったDemoListコンポーネント内で配列をmapにて処理してリスト表示される仕組みとなっております。

今回のTodoリスト作成作業はここまでにしたいと思います。

ここまで作業が完了したら、サーバーを起動しlocalhostにアクセスして、動作確認をしてみましょう。

インプットボックスに適当な文字を入力して、登録ボタンをクリックすると入力された文字列がタスクとしてリストに追加されたと思います。

今回タスク完了の関数を作成していないので完了ボタンを押してもタスクを消すことはできません。

次回はタスクが完了した際にリストから消去する動作を作成したいと思います。

この記事は役に立ちましたか?

もし参考になりましたら、下記のボタンで教えてください。

関連記事

コメント

この記事へのコメントはありません。