6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Reactでプルダウンをつくる

Last updated at Posted at 2022-01-18

背景

Reactを使ってプルダウンを実装することになったのですが、useRefやuseEffectなどの基本だけども初学者には理解しづらい内容だと思い、自分の理解を深めるために記事にまとめました。
改善点やアドバイスがあれば優しくご指摘いただければ幸いです。

制作イメージ

プルダウン.gif

要件

  • 生年月日のプルダウン
  • 年・月・日でそれぞれ別の選択肢をもつ
  • 年は西暦と和暦、両方表示

*平成と令和の切り替え、うるう年、30日と31日の表示分けは今回実装しません。

完成コード
import React, { useRef, useState, useEffect } from "react"; 

export const BirthDate = () => {
  const birthYearRef = useRef(null);
  const birthMonthRef = useRef(null);
  const birthDayRef = useRef(null);

  const [birthYear, setBirthYear] = useState();
  const [birthMonth, setBirthMonth] = useState();
  const [birthDay, setBirthDay] = useState();

  const setYear = () => {
    for (let i = 1920; i <= new Date().getFullYear(); i++) {
      const option = document.createElement('option');
      const date = new Date(Date.UTC(i));
      const jc = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {year: 'numeric'}).format(date);
      option.value = `${i}${jc})`;
      option.text = `${i}${jc})`;
      birthYearRef.current.appendChild(option);
    }
  }

  const setMonth = () => {
    for (let i = 1; i <= 12; i++) {
      const option = document.createElement('option');
      option.value = i;
      option.text = i;
      birthMonthRef.current.appendChild(option);
    }
  }

  const setDay = () => {
    for (let i = 1; i <= 31; i++) {
      const option = document.createElement('option');
      option.value = i;
      option.text = i;
      birthDayRef.current.appendChild(option);
    }
  }

  const selectBirthYear = (e) => {
    setBirthYear(e.target.value);
  }

  const selectBirthMonth = (e) => {
    setBirthMonth(e.target.value);
  }

  const selectBirthDay = (e) => {
    setBirthDay(e.target.value);
  }

  useEffect(() => {
    setYear();
    setMonth();
    setDay();
  }, []);

  return (
    <div>
      <p>-生年月日-</p>
      <label>
        <select ref={birthYearRef} value={birthYear} onChange={selectBirthYear}></select></label>
      <label>
        <select ref={birthMonthRef} value={birthMonth} onChange={selectBirthMonth}></select></label>
      <label>
        <select ref={birthDayRef} value={birthDay} onChange={selectBirthDay}></select></label>
    </div>
  )
}

西暦から和暦を表示

new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {year: 'numeric'}).format();を使うことで西暦→和暦に変換することができるんだけど、日付データの使い方を理解しておかないと昭和45年をずっと出力することになります。。

const i = 1920;
const date = new Date(i);
New Intl.DateTimeFormat('ja-JP-u-ca-japanese', {year: 'numeric'}).format(date);

たとえば上記のようにやっても正しく計算されずにdateは1970年になってしまい、昭和45年になってしまいます。

Dateオブジェクトは協定世界時(UTC)だと1970年1月1日 00:00:00からの経過時間を表しています
なので、new Date(i)としてしまうと、1970年になってしまうんですね、
そこで、Date.UTC()を用いることで引数の数値を1970年を起算日に計算してくれるようです。
逆に用いないと基準時間の1970年から計算されないため、1970年=昭和45年となってしまうんですね。

useRef

useRefとは、公式ドキュメントには以下のように記載されています。

useRef returns a mutable ref object whose .current property is initialized to the passed argument (initialValue).

直訳すると、useRefは渡された引数によって、.currentプロパティが初期化された可変的なREFオブジェクトが返されるそうです。
うーん、つまりuseRefを操作するには.currentを介してね、という感じでしょうか。。。?

以下の記事でわかりやすく解説されていました!
useRef は何をやっているのか

useRef で値を保持することの特性は 「再描画を発生させないこと」 これに尽きます。
なのでユースケースとしては
・描画には関係のない状態
を扱うために使うのが主となるでしょう。

プルダウンは再描画させる必要はない部分になるのでuseRefを使うのがよさそうですね。
今回はuseRefにoptionエレメントを追加するというかたちでプルダウンをつくっています。

const option = document.createElement('option');
      option.value = i;
      option.text = i;
      birthDayRef.current.appendChild(option);

useEffect

useEffectは、公式にて以下のように解説されています。

useEffect は何をやっているのか? このフックを使うことで、レンダー後に何かの処理をしないといけない、ということを React に伝えます。React はあなたが渡した関数を覚えており(これを「副作用(関数)」と呼ぶこととします)、DOM の更新の後にそれを呼び出します。この副作用の場合はドキュメントのタイトルをセットしていますが、データを取得したりその他何らかの命令型の API を呼び出したりすることも可能です。

React のライフサイクルに馴染みがある場合は、useEffect フックを componentDidMount と componentDidUpdate と componentWillUnmount がまとまったものだと考えることができます。

ライフサイクルとは、コンポーネントが最初に描画されてから破棄されるまでの時間の流れのことです。Class Components時代にはcomponentDidMount() "コンポーネントが描画される期間"componentDidUpdate() "コンポーネントが再描画される期間"componentWillUnMount() "コンポーネントが破棄される期間"の3つのメソッドで表現していたようです。React Hooksが主流になるとuseEffectでライフサクルを表現するようになったそうです。

要はuseEffectとは副作用のことで、副作用とはレンダリングされたときに実行される処理のことです。

参考: 【とらゼミ】トラハックのエンジニア学習講座
#08 新・日本一わかりやすいReact入門【基礎編】ライフサイクルと副作用を理解しよう
で詳しく解説されていてわかりやすいです

また、第二引数の依存関係によってレンダリングの再実行を制御できます。

// 毎回実行される
useEffect(() => {
  処理
})

// 初回レンダリングのみ実行
useEffect(() => {
  処理
}, [])

// triggerが変更される度に実行される
useEffect(() => {
  処理
}, [trigger])

// trigger1がtrigger2に変更される度に実行される
useEffect(() => {
  処理
}, [trigger1, trigger2])

引用: 【とらゼミ】トラハックのエンジニア学習講座
#08 新・日本一わかりやすいReact入門【基礎編】ライフサイクルと副作用を理解しよう

今回は、初回レンダリングのみプルダウン追加処理を行いたかったので、以下のように記載しました。

  useEffect(() => {
    setYear();
    setMonth();
    setDay();
  }, []);

コード改善版

もっと楽に実装する方法がありました!!
setYearなどの選択肢を追加する関数をdefinition.jsに切り離し、生成した配列を戻り値としてjsx側に渡すようにしました。

BirthDate.jsx
import React, { useRef, useState } from "react";
import { years, months, days } from "./definition";

export const BirthDate = () => {
  const birthYearRef = useRef(null);
  const birthMonthRef = useRef(null);
  const birthDayRef = useRef(null);

  const [birthYear, setBirthYear] = useState();
  const [birthMonth, setBirthMonth] = useState();
  const [birthDay, setBirthDay] = useState();

  const selectBirthYear = (e) => {
    setBirthYear(e.target.value);
  }

  const selectBirthMonth = (e) => {
    setBirthMonth(e.target.value);
  }

  const selectBirthDay = (e) => {
    setBirthDay(e.target.value);
  }

  return (
    <div>
      <p>-生年月日-</p>
      <label>
        <select ref={birthYearRef} value={birthYear} onChange={selectBirthYear}>
          {years.map((year) =>
            (<option value={year.year}>{year.year}{year.japaneseCalender}</option>)
          )}
        </select></label>
      <label>
        <select ref={birthMonthRef} value={birthMonth} onChange={selectBirthMonth}>
          {months.map((month) =>
            (<option value={month}>{month}</option>)
          )}
        </select></label>
      <label>
        <select ref={birthDayRef} value={birthDay} onChange={selectBirthDay}>
          {days.map((day) =>
            (<option value={day}>{day}</option>)
          )}
        </select></label>
    </div>
  )
}
definition.js
const setYears = () => {
  const dataYears = [];
  
  for (let i = 1920; i <= new Date().getFullYear(); i++) {
    const date = new Date(Date.UTC(i));
    const jc = new Intl.DateTimeFormat('ja-JP-u-ca-japanese', {year: 'numeric'}).format(date);
    dataYears.push( {year: i, japaneseCalender: jc} );
  }
  return dataYears;
}

export const years = setYears();

const setMonths = () => {
  const dataMonths = [];
  for (let i = 1; i <= 12; i++) {
    dataMonths.push(i);
  }
  return dataMonths;
}

export const months = setMonths();

const setDays = () => {
  const dataDays = [];
  for (let i = 1; i <= 31; i++) {
    dataDays.push(i);
  }
  return dataDays;
}

export const days = setDays();

まとめ

useRefやuseEffectが出てきたあたりから理解が難しくなってきた印象です。
公式ドキュメントも日本語が理解しづらい部分も多いため、そろそろ英語の勉強も開始しないとなあと思い始めてきた次第です。
日付変換などはReactというよりも日付理解が浅かったなー、という所感です。
今回はプルダウンを登録するだけで、まだまだコードの改善余地もあるでしょうし、平成→令和、末日ズレ(30日or31日)などもやらないと。大変だ。でも楽しい。

あと、単純にプルダウン実装するだけならreactstrapとかで簡単に作るのがオススメです。笑

参考

6
7
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
6
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?