
九保すこひです(フリーランスのITコンサルタント、エンジニア)
さてさて、ここ最近はReact
の面白さに触れてしまって、ずっとReact
関連の記事が続いています。(初めてコロコロ掃除をしたとき止まらなくなった感覚に似てます)
ということで、他にも何かユーザビリティ関連でコンポーネントが作れないかなと考えていたところ、地味に便利なものを思い出しました。
それは・・・・・・
ワンクリックでクリアできる入力ボックス
です。例えばこんなカンジです。
つまり、通常の<input>
では、文字を削除するときバックスペースで1文字ずつ消すか、全体を選択状態にして削除しないと全クリアにはなりません。
しかし、もしスピードを求められる現場での入力ボックスだったとしたら全クリアがあると便利ですよね。
そこで
今回は、Laravel + React
で「ワンクリックでクリアできる入力ボックス」を作ってみます。
ぜひ何かの参考になりましたら嬉しいです。
「名曲イージュー★ライダーは、
もう旅猿バージョンでしか
脳内再生されません」
開発環境: Laravel 9.x、React、Vite、Inertia.js、TailwindCSS
目次 [非表示]
Font Awesome をインストールする
まず、今回はクリアボタンに以下のアイコンフォントを使いたいので、先にFont Awesome
をインストールしておきます。
以下のコマンドでパッケージをインストールしてください。
npm i font-awesome --save
そして、Vite
がアクセスできるようにfont-awesome
を追加します。
resources/css/app.css
@import 'font-awesome/css/font-awesome.css';
@tailwind base;
@tailwind components;
@tailwind utilities;
【ご注意】
どうやら@import
は一番最初に書かなければいけないらしく、もし@tailwind
の後に追加してしまうとエラーになってしまいます。
お気をつけください。m(_ _)m
ルートをつくる
では、続いてルートをつくります。
今回はひとつだけでOKです。
routes/web.php
use Inertia\Inertia;
// 省略
Route::get('clear_input', fn() => Inertia::render('ClearInput/Index'));
※ 本来はコントローラーをつくる方がいいと思いますが、テストなので省略形にしています。
ビューをつくる
次にブラウザから実際にアクセスされることになるビューになります。
なお、今回は次の項目でつくるClearInput
を3つ設置してちゃんと区別できるかチェックできるようにします。
resources/js/Pages/ClearInput/Index.jsx
import {useState, useEffect} from 'react';
import ClearInput from '@/Components/ClearInput';
export default function Index() {
// Data
const [value1, setValue1] = useState('');
const [value2, setValue2] = useState('');
const [value3, setValue3] = useState('初期値あり');
// Effect
useEffect(() => {
console.log('--------------------------------');
console.log(`value1 → ${value1}`);
console.log(`value2 → ${value2}`);
console.log(`value3 → ${value3}`);
}, [value1, value2, value3]);
return (
<div className="p-4">
<h1 className="font-bold mb-3">📝 React: ワンクリックでクリアできる入力ボックス</h1>
<div className="mb-3 w-64">
<ClearInput value={value1} onChange={value => setValue1(value)} />
</div>
<div className="mb-3 w-64">
<ClearInput value={value2} onChange={value => setValue2(value)} />
</div>
<div className="mb-3 w-64">
<ClearInput value={value3} onChange={value => setValue3(value)} />
</div>
</div>
);
}
コンポーネントをつくる
では、今回のメインのコンポーネント「ClearInput」です。
resources/js/Components/ClearInput.jsx
import {useState, useRef, useEffect} from 'react';
export default function ClearInput(props) {
// 入力ボックス
const inputRef = useRef();
const [value, setValue] = useState(props.value || '');
useEffect(() => { // value が変化したら実行される
const icon = iconRef.current;
// クリアボタンの表示・非表示
if(value.length > 0) {
icon.classList.remove('invisible');
} else {
icon.classList.add('invisible');
}
if(typeof props.onChange === 'function') { // 親要素に onChange があれば実行する
props.onChange(value);
}
}, [value]);
// 全削除アイコン
const iconRef = useRef();
const handleIconClick = () => { // アイコンをクリックしたら実行される
setValue('');
inputRef.current.focus();
};
const setIconPosition = () => { // 全削除アイコンの位置を調整する
const input = inputRef.current;
const icon = iconRef.current;
if(input && icon) {
const inputHeight = input.clientHeight;
const iconHeight = icon.clientHeight;
const iconTop = (inputHeight - iconHeight) / 2;
icon.style.top = iconTop +'px';
}
};
useEffect(() => { // ページが読み込まれたら実行される
setIconPosition();
}, []);
return (
<div className="relative">
{/* 入力ボックス */}
<input
value={value}
ref={inputRef}
className="w-full px-1.5 py-1 border border-gray-300 text-gray-900"
onChange={e => setValue(e.target.value)} />
{/* 全削除アイコン */}
<a
href="#"
ref={iconRef}
tabIndex="-1"
className="absolute right-2.5 invisible"
onClick={e => handleIconClick(e)}>
<i className="fa fa-times-circle"></i>
</a>
</div>
);
}
では、この中でやっていることをひとつずつ見ていきましょう
【追記:2023.1.17】コードが間違っていたので修正しました。ご不便おかけしてスミマセン!
コード全体
まずコード全体の構成ですが、これまではVue
で言うところの「Options API」のように「変数は変数でまとめて書く」「メソッドはメソッドで…」というようなグループ分けをして書いていましたが、今回はVue 3
で導入された「Composition API」のように「役割」でグループ分けをしています。
そのため、今回の構造としては大きく「入力ボックス」と「閉じるボタン」の2ブロックになっています。
入力ボックス
ここでは主にクリックすると入力内容が消える「全削除ボタン」表示/非表示を切り替えるために用意しています。
まずuseRef()
を使って<input>
に簡単にアクセスできるようにしています。
そして、その入力ボックスにバインディングする変数はvalue
です。
なお、初期値をセットできるようにしておきたいので、もし親要素からデータ送信されていればそれを使い、なければ空白が適用されるようになっています。
useState(props.value || '');
そして、useEffect()
の中身ですが、全削除アイコンの表示/非表示する条件は以下のとおりです。
- 何か文字が入力されている → 表示
- 何も入力がない → 表示しない
そのため、useEffect()
内では、value
の中身が変化したときに実行されるようにしています。(第2引数の[value]
の部分でどの値が変化するのを監視するか指定できます)
全削除アイコン
クリックしたときに入力内容をすべて削除する機能です。
まずここでもuseRef()
を使ってアイコン部分にアクセスしやすいようにし、さらにクリックされたらsetValue()
を使って入力内容をクリアします。
また、ここが少しだけ複雑ですが、setIconPosition()
は「全削除アイコン」の上下位置が真ん中にくるようにしています。
というのも、今回は入力ボックスの大きさ(高さ)は固定なので、スタイルシートでtop:10px
というように指定しても問題ありません。
しかし、今後高さが変わることも想定されます(sm
, md
, lg
などサイズ指定ができるようにする、そもそもclassName
を変更できるようにする)ので、それを想定して「どんな高さになっても自動的に計算して真ん中に表示される」ようにしているというわけです
(もしかしてモダンなCSSなら対応できますかね…)
なお、閉じるアイコンの<a>
タグには、
tabIndex="-1"
がついていますが、これはタブキーを押したときにフォーカスが当たってしまう(=次の入力ボックスに飛べない)のを防止しています。
テストしてみる
では、実際にテストしてみましょう
まずはブラウザで「http://******/clear_input」へアクセスします。
すると、以下のような表示になりました。
初期値が表示されていて、さらに「全削除ボタン」も表示されています。
では、一番上の入力ボックスに「one」と入力してみましょう。
どうなるでしょうか・・・・・・
はい
文字が入力されて、全削除アイコンが表示されました。
では、コンソールの方も確認しておきましょう。
1文字追加されるごとに内容が変化していることがわかります
では、次に表示された全削除アイコンをクリックしてみましょう。
どうなるでしょうか・・・・・・??
はい
想定通り「one」が消え、何もなくなりました。
成功です
では、最後に1番目に「one」2番めに「two」と入力し、「初期値あり」の全削除アイコンをクリックすることで、コンポーネントが個別で動いていることを確認してみます。
うまくいくでしょうか…
はい
いくつか操作してみましたが、それぞれスタンドアローンで動いていますね。
すべて成功です
デモページを用意しました
せっかくなのでデモページを用意しました。
ぜひ実際に触ってみてください
企業様へのご提案
今回のようにウェブシステムは「使う人に合わせた」機能を開発することができます。
ITリテラシーがあまりない方のために、この間公開した「ソフトウェアキーボード」での入力も可能になりますし、よく入力される文字を入力候補にして選択させることもできます。
そして、こうすることで業務効率化をすることで、より注力すべき内容にエネルギーを使うことができるようになります。
もしこういった業務改善をご希望でしたら、いつでもお気軽にご連絡ください。
お待ちしております。m(_ _)m
おわりに
ということで、今回はLaravel + React
で「ワンクリックでクリアできる入力ボックス」をつくってみました。
ちなみに、今回は記事中でも書いたとおり「役割でグループ化した書き方」をしましたがいかがだったでしょうか。
正直なところ、「データはデータでまとめる」やり方の方が学習には適しているのかなという気はしているのですが、リアルの開発では今回のような書き方の方が多いような気もしますし、記事を書く側としては悩ましかったりします(笑)
ぜひ皆さんもいろいろと試してみてくださいね。
ではでは〜
「道を譲ったら、
溝にハマって自転車パンク…
こんなブーメランがあっていいのか…」