クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【React】フックでonChangeイベントを設定、ツマミが動いた!(ジャンプ率ジェネレーター)

Reactジャンプ率ジェネレーターの続きです。前回はAboutページ作成とinput(type="range")タグの配置を行いました。しかしinputタグに初期値valueを入れたらツマミが動かなくなりました。今回はこのツマミを動かすべく、onChange設定をしたいきたく思います。それではいきましょう!

【目次】

※参考:前回記事
【React】Aboutページ作成、input(type="range")タグ配置(ジャンプ率ジェネレーター) - クモのようにコツコツと

※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com

前回のおさらい

Otherページを改造してAboutページ作成。ルーティング設定も変更した。 https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210317/20210317040932.jpg

メインページにinput(type="range")タグを配置。しかしinputタグのツマミがまだ動かない。初期値valueを入れたため。 https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210317/20210317044651.jpg

これを動かすにはonChange設定が必要なようだ。

※参考:【React】Aboutページ作成、input(type="range")タグ配置(ジャンプ率ジェネレーター) - クモのようにコツコツと

onChangeとは

コンソールには下記のエラーメッセージ

Warning: You provided a `value` prop to a form field without an `onChange` handler. This will render a read-only field. If the field should be mutable use `defaultValue`. Otherwise, set either `onChange` or `readOnly`.

警告: onChangeハンドラーなしでフォームフィールドにvalueプロパティを指定しました。 これにより、読み取り専用フィールドがレンダリングされます。 フィールドを変更可能にする必要がある場合は、 defaultValueを使用します。 それ以外の場合は、 onChangeまたはreadOnlyのいずれかを設定します。


onChangeについては以前、こちらでもやったReactのイベント設定。関数コンポーネントで設定している。

※参考:【React】イベント属性を使ってみた(onChangeとonClick) - クモのようにコツコツと

Reactのドキュメントではクラスコンポーネントの事例になっている。

※参考:フォーム – React

ドキュメントの「独自フック」のところでonChangeの事例があるのでフックを使えば関数コンポーネントでも問題なさそう。

※参考:独自フックの作成 – React

クラスコンポーネントと関数コンポーネントの比較記事もあった♪

※参考:React Hooksに対応した複数のonChangeハンドラを処理する書き方 | 会津ラボブログ

関数コンポーネントでonChangeを設定

最初にinputタグにダメ元でonChange={}(中身は空)を書いてみると

          <InputRange type="range" name="range" min="10" max="50" value="35" onChange={}></InputRange>

当然ながらエラーになる

SyntaxError: /(パス)/react_app/jump-rate-generator/src/Inner.tsx: JSX attributes must only be assigned a non-empty expression (18:86)

JSX属性には、空でない式のみを割り当てる必要があります

ふむ、そうだよね。


次に架空(未設定)の関数名testを入れてみる

          <InputRange type="range" name="range" min="10" max="50" value="35" onChange={test}></InputRange>

まあ当然まだエラー

TypeScript error in /(パス)/react_app/jump-rate-generator/src/Inner.tsx(18,78):
No overload matches this call.

この呼び出しに一致する過負荷はありません。


関数test()を作ってみる。

const test = () => {
  console.log('動いた!');
};

コンソールに「動いた!」と表示するだけ。

お、エラーが消えた!
f:id:idr_zz:20210319040535j:plain
ツマミを動かすとコンソールの「動いた!」の数が増える!

しかし肝心のツマミの位置はまだ動かない。初期値が変わらないからだ… https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210317/20210317044651.jpg

とりあえずonChangeで関数を実行するとエラーが消えることだけはわかった。

Reactの値はステートで制御する

再びReactドキュメントのフォームのところを読むと

HTML では <input><textarea>、そして<select> のようなフォーム要素は通常、自身で状態を保持しており、ユーザの入力に基づいてそれを更新します。React では、変更されうる状態は通常はコンポーネントの state プロパティに保持され、setState() 関数でのみ更新されます。

React の state を “信頼できる唯一の情報源 (single source of truth)” とすることで、上述の 2 つの状態を結合させることができます。そうすることで、フォームをレンダーしている React コンポーネントが、後続するユーザ入力でフォームで起きることも制御できるようになります。このような方法で React によって値が制御される入力フォーム要素は「制御されたコンポーネント」と呼ばれます。

※参考:フォーム – React

おお、たぶんここ大事なところ。通常のフォーム要素は自分自身で状態を保持するが、Reactではstateで状態を保持し、setState()のみで更新すると。

stateのドキュメントにはこうある

setState() はコンポーネントの state オブジェクト更新をスケジュールします。state が更新されると、コンポーネントはそれに再レンダーで応じます。

※参考:コンポーネントの state – React

そして以前、自分もstateプロパティやsetState()関数の設定をステートの記事の時にやっている。このときはonClickイベントだがやってることは同じはず。

※参考:【React】Reactプロジェクトでステートを事始め(setState()、タイマー処理、イベント処理) - クモのようにコツコツと

で、そのステート設定を関数コンポーネントで実現するためにフックを使う、と。この時もonClickイベントで設定してるな。

※参考:【React】フック(React Hooks)事始め:useState、useEffect、useContext - クモのようにコツコツと

で、先ほどの「独自フック」のドキュメントではonChangeを設定している。

※参考:独自フックの作成 – React

e.target.valueを取得してみる

フックを使うまえにもう一つ試してみたいこと。e.target.valueを取得するとどうなるか。

const test = (e) => {
  const defaultValue = e.target.value;
  console.log(defaultValue);
};
  • 引数eでイベントを取得
  • 変数defaultValuee.target.valueを取得
  • コンソールで表示

おっと、TypeScriptに怒られた。

TypeScript error in /(パス)/react_app/jump-rate-generator/src/Inner.tsx(11,15):
Parameter 'e' implicitly has an 'any' type.  TS7006

「パラメータ「e」は暗黙的に「any」型を持っています。 TS7006」


まあ困ったときにはany型を付ければエラーは消えるのだが…

const test = (e: any) => {
  const defaultValue = e.target.value;
  console.log(defaultValue);
};

他にふさわしい型はあるのかな。


anyを使うのはちょっと気が引けるということであれば

event: React.ChangeEvent<HTMLInputElement>

※参考:TypeScript - react eventHandlerのエラーが解消できない|teratail

お?React.ChangeEvent<HTMLInputElement>とはなんぞや。

こちらの記事のようにVSCodeでonChangeにマウスオーバーすると表示される f:id:idr_zz:20210319052622j:plain

※参考:any型で諦めない React.EventCallback - Qiita

「@types/react」の中で定義されているようだ。


型をReact.ChangeEvent<HTMLInputElement>に変更してみる!

const test = (e: React.ChangeEvent<HTMLInputElement>) => {
  const defaultValue = e.target.value;
  console.log(defaultValue);
};

おお、エラーはなくなった!rangeのツマミを動かすとコンソールに変更されたvalueの値が表示されている!
f:id:idr_zz:20210319053118j:plain

しかし、ブラウザ上のツマミの位置はまだ動かない。 https://cdn-ak.f.st-hatena.com/images/fotolife/i/idr_zz/20210317/20210317044651.jpg

フックでステートの値を更新する

いよいよフックを使ってみるか。

useStateをインポート

import React, { useState } from 'react';

フックを設定

const [lineLength, setLineLength] = useState(35);
  • 変数lineLengthsetLineLengthにフックを設定、useState()で初期値は35

setLineLengthの値変更設定

  const changelineLength = (e: React.ChangeEvent<HTMLInputElement>) => {
    let changeValue: number = Number(e.target.value);
    console.log(changeValue);
    setLineLength(changeValue);
  };
  • changelineLengthはアロー関数、引数はe(イベント)、eの型はReact.ChangeEvent<HTMLInputElement>
  • 変数changeValueの型はnumbere.target.valueを取得(Number()で文字列を数値に変換)
  • setLineLength()の引数でchangeValueを取得

e.target.valueの値は文字列だった。そのままだとTypeScriptエラーになるのでNumber()で数値に変換した。


JSX部分にフックを反映

        <section>
          <h2>行長</h2>
          <p>値:{lineLength}</p>
          <InputRange type="range" name="range" min="10" max="50" defaultValue={lineLength} onChange={changelineLength}></InputRange>
        </section>
        <section>
  • pタグを追加し「値:」のあとにlineLengthを埋め込む
  • inputタグのvalue属性をdefaultValue属性に変更し、値にlineLengthを埋め込む
    onChangeイベントでchangelineLength() を実行

valueのままだとrangeのツマミが固定されて動かなかった。

Reactではvalueの代わりにdefaultValueを使うことで初期値を更新できる。

※参考:非制御コンポーネント – React

ブラウザ挙動

一つ目の「行長」のところ、ページをロードすると初期値の「35」になっている。文字も表示されている。 f:id:idr_zz:20210319063925j:plain

ツマミを右に動かす。ちゃんと動いた!値表示も「41」に増えた f:id:idr_zz:20210319063929j:plain

今後はツマミを左に動かす。値表示が「21」に減った f:id:idr_zz:20210319063933j:plain


コード(GitHub)

※参考:GitHub - ryo-i/jump-rate-generator at 11c227cba0639eee7224fc309b45ad15f20322de

プレビュー(GitHub Pages)

※参考:React App

最後に

ようやくinputタグのrangeのツマミが動くようになりましたー。過去に自分が経験してきたはずのステートやフック設定などの復習にもなり、また点と点をつなげて線になるのは自分の中での理解が深まって嬉しいことです。

まだ、動きとしてはinputの値を変更したり文字として表示しているだけですが、これからのこの値をCSSスタイルに反映したりして、ジャンプ率ジェネレーターの形に近づけていきたいと思います。

それではまた!


※参考:ReactでWebアプリを作るシリーズまとめ
qiita.com