Material-UIのSelect内のテキスト部分のスタイルをいじる
したいこと
Material-UIのSelectコンポーネント内にあるテキスト部分のスタイルを変更したかった。 具体的には、下図のようにテキストエリアの範囲を縮小したかった。
変更前
変更後
タグ構造
Select周りの構造は下記の通りになっているとする。 今回「ここから〜ここまで」で囲まれている範囲のスタイルを変更することを目的とした。 該当部分のReactコンポーネントも記載しているが、jsx用のシンタックスハイライトを当てていないので、雰囲気でおなしゃす。
<div class="MuiInputBase-root MuiOutlinedInput-root makeStyles-statusMenu-16"> <!-- ここから --> <div class="MuiSelect-root MuiSelect-select MuiSelect-selectMenu MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input" tabindex="0" role="button" aria-haspopup="listbox" aria-labelledby="demo-simple-select-filled" id="demo-simple-select-filled" > 未着手 </div> <!-- ここまで --> <input aria-hidden="true" tabindex="-1" class="MuiSelect-nativeInput" value="waiting" /><svg class="MuiSvgIcon-root MuiSelect-icon MuiSelect-iconOutlined" focusable="false" viewBox="0 0 24 24" aria-hidden="true" > <path d="M7 10l5 5 5-5z"></path> </svg> <fieldset aria-hidden="true" class="PrivateNotchedOutline-root-29 MuiOutlinedInput-notchedOutline" style="padding-left: 8px" > <legend class="PrivateNotchedOutline-legend-30" style="width: 0.01px"> <span><200b></span> </legend> </fieldset> </div>
<Select id="demo-simple-select-filled" value={task.status} variant="outlined" className={classes.statusMenu} > <MenuItem value="waiting">未着手</MenuItem> <MenuItem value="working">作業中</MenuItem> <MenuItem value="completed">完了</MenuItem> <MenuItem value="pending">保留</MenuItem> </Select>
方法
MenuItem
のinput部分のMuiOutlinedInput-input
に対してスタイルを設定する
statusMenu: { '& .MuiOutlinedInput-input': { paddingBottom: '7px', paddingTop: '7px', }, },
JSのbind、及び`this`について理解を少し深める
bindそのものについて
JSのbindの用法を知り、「どういう時に使われるのか」が漠然とイメージできるようになりたかったため、概要と使われ方を調べた。
とりあえず以下のサイトのデモコードを自分なりにいじってどういう挙動なのかは調べた。
Function.prototype.bind() - JavaScript | MDN
const module = { a: 50, b: 30, text: 'this is test', showText: function(){ return this.text; }, }; const moduleShow = module.showText; console.log(moduleShow); // moduleから抜き出した関数にmodule自体をbindする // moduleShow内のthis = bindされたmoduleとなる // そのため、this.text = module.text、になる const boundGetX = moduleShow.bind(module); console.log(boundGetX());
その他、bindの概要、callとの違いについては下記の記事を見てイメージは出来た。 JavaScript thisの内容を指定する(bindメソッド) | ITSakura
ただ、こいつがどういう目的で使われるかは分からなかった。*1
単純に、関数内のthis(thisがない場合はbind内の第一引数にnullを指定する)を特定のものに設定する(=undefinedにしない)場合にbindを使う、という使い方でいいのだろうか。
アロー関数内でのthisの扱われ方とbindの比較
ここまで調べて、そういえばJSの通常関数とアロー関数ではthisの指すものが異なる、という話しを思い出した。
【JavaScript】アロー関数式を学ぶついでにthisも復習する話 - Qiita
JavaScript: 通常の関数とアロー関数の違いは「書き方だけ」ではない。異なる性質が10個ほどある。 - Qiita
両者のthis
に関する違いとしては、アロー関数はthis
を束縛し、通常関数はthis
を束縛しない(=関数呼び出し元(レシーバ)をthisとする)という点である。
下記のコードを例に比較する。
- 通常関数
this.name = "globalName"; // 通常関数 function showName() { console.log(this.name); } let arrowFunc = { name: "john", func: showName, }; arrowFunc.func(); => john // bindを使った場合 showName.bind(arrowFunc)(); => john
arrowFunc.func()
の返り値がjohn
となっており、これはarrowFunc.nameの値であることが分かる。
このことから、arrowFunc.func()
、つまりはarrowFunc.showName()
のshowName内部のthisはarrowFunc(レシーバ)を指していたことが分かる。
- アロー関数
this.name = "globalName"; // アロー関数 const showName = () => { console.log(this.name); }; let arrowFunc = { name: "john", func: showName, }; arrowFunc.func(); => globalName
arrowFunc.func()
の返り値が「globalName」となっている。これは最初に定義したthis.name = "globalName"
のnameの値が表示されていることが分かる。
ここで、アロー関数内のthisについてのリファレンスを見てみる。
アロー関数自身は this を持ちません。レキシカルスコープの this 値を使います。つまり、アロー関数内の this 値は通常の変数検索ルールに従います。このためスコープに this 値がない場合、その一つ外側のスコープで this 値を探します。
アロー関数であるshowName
が定義された際、関数内のthisは更に外側のthisを見ていたことになる。
今回Node上で上記コードを実行したため、thisとして定義されていたのは{name: "globalName"}
だったため、showName
関数内のconsole.logで表示されたthis.nameは、コードの最初に定義したthis.nameだった、ということになる。
ちなみにトップレベル、グローバルスコープあたりはここを参考にした。
JavaScriptのトップレベルスコープは常にグローバルスコープではなかった - Qiita
まとめ
改めてbind
、通常関数とアロー関数それぞれでのthisの使い方を確認した。
通常関数とアロー関数の違いについては他にも色々あるが、一旦知りたいと思っていたことを確認することができた。
需要が薄いMaterial-UIのvalidationの一例
空文字を許容しない、というバリデーションを一部分だけ書きたい、しかしそのために本格的なバリデーションの機構を組むのはめんどくさくて死にそう、という時にその場しのぎで書いたもの。
<TextField label="名前" variant="outlined" margin="normal" required // input.valueに入力された文字が入っているものとして、空文字の場合は input.value はfalseを返す // 上記の場合に{ error: true }を返すことで <TextField error /> と書いた時と同じようになる // { error: false } の場合はerrorは指定されていない状態になる {...(!input.value ? { error: true } : { error: false })} />
Material-UIのTextFieldをtype="date"にした際のdefaultValueに指定する日付のフォーマットについて備忘録
material-uiのTextFiled
というAPIの中で、type="date"
のようにタイプを指定することでDate pickerのように使用することができる。
Date picker, Time picker React components - Material-UI
その際、デフォルトの日付をdefaultValue
というオプションで設定できるが、この部分で少し詰まったので備忘録として残す。
ダメだった例
// 年、月、日の区切りがスラッシュになっているとdefaultValueとして認識されない const today = dayjs().format('YYYY/MM/DD') <TextField label="testDay" type="date" defaultValue={today} margin="normal" />
良かった例
// 年、月、日の区切りがハイフンだときちんとdefaultValueとして指定できる const today = dayjs().format('YYYY-MM-DD') <TextField label="testDay" type="date" defaultValue={today} margin="normal" />
詳細は調査中だが、同じようにdatapickerとして使用できるKeyboardDatePicker
ではformatにformat="MM/dd/yyyy"
のように指定してもいける感じなので、どうしてこのような仕様にしているかはよく分からない。
しかも表示される時にはYYYY/MM/DD
形式だし。スラッシュどこに行った。
https://material-ui.com/components/pickers/#material-ui-pickers
rbenvによるバージョン切り替えの際の備忘録
githubからforkしてきたプロジェクトのrubyのバージョン関連で問題が生じたので、経過とともにメモする。
経過&メモ
- forkしてきたプロジェクトの
.ruby-version
に書かれているrubyのバージョンを確認 rbenv install x.x.x
で該当のrubyのバージョンを指定してインストール- 念の為
rbenv rehash
実行
ここで、rbenv rehash
をする意味を再度確認した
「~/.rbenv/versions/2.x.y/bin/ 以下に置いてあるコマンド群を ~/.rbenv/shims/以下に置いて使えるようにする」
rbenv rehashは何をやっているのか? · DQNEO日記
bundle install
実行しようとしたら下記のエラーが表示されて出来ない
Warning: the running version of Bundler (1.17.2) is older than the version that created the lockfile (1.17.3). We suggest you upgrade to the latest version of Bundler by running `gem install bundler`. Your Ruby version is 2.6.3, but your Gemfile specified 2.6.5
bundlerのバージョンが古いと言う警告と、rubyのバージョンが.ruby-version
に書かれているものよりも古いというエラー。
- とりあえず、どこのrubyが呼ばれているか
which ruby
で確認
~$ vim ~/.bash_profile // .bash_profileが開かれるので、下記一行を追加して保存する export PATH="$HOME/.rbenv/shims:$PATH" // 変更を反映させる ~$ source ~/.bash_profile
- これで
bundle install
したところ正常にインストールできた
振り返り
前職に勤めていた時には、会社用のレポジトリは基本的に会社のPC上で触っていて、個人で何かする時には個人のPCでやっていたためrubyのバージョン切り替えをあまりやっていなかった。 今回、久方ぶりにforkしてきたリポジトリを動かす、ということをしたのでrubyのバージョン切り替え周りで少し手間取ってしまった。 rbenv、bundlerあたりはこういったケースをきちんとメモしておいてその都度理解を深めるのがいいかなと感じる。 実装周りが落ち着けば一度きちんと見たい。
setStateで古いstateを見てしまった
基本的なところだけど、少し悩んでしまったのでメモ。
アプリのタイマー機能を実装しようとした際、setIntervalを使用しても時間が加算されない問題が発生した。 原因としては、setStateにオブジェクトを渡した場合、stateを即座にアップデートすることを保証しないというもの。
今回のような、setIntervalでsetStateが呼ばれるなど参照したいstateが絶えず更新されているものである場合、「setStateに更新用の関数を渡し、その関数内の引数で最新のstateにアクセスして操作する」のが正しい実装内容となる。
駄目な例
下記の例だと、setState
にtimeに1を足した値を引数として渡しているが、この時にtimeが参照しているのは更新前のtimeである。
つまり、0秒から時間の加算が始まったとして、
// timeは親のコンポーネントからもらってくる数値 const [time, setTime] = useState(props.time || null); useEffect(() => { const { taskId, recordingTaskId } = props; if (taskId && taskId === recordingTaskId) { // setIntervalで時間加算の関数(addSecond)を1秒毎に実行させる setTimerId(setInterval(addSecond, 1000)); } else return; }, [props.recordingTaskId]); const addSecond = () => { // ここでsetStateに「time + 1」という値を渡してしまっているため、常に古いstateを見てしまっている setTime(time + 1); };
良い例
// timeは親のコンポーネントからもらってくる数値 const [time, setTime] = useState(props.time || null); useEffect(() => { const { taskId, recordingTaskId } = props; if (taskId && taskId === recordingTaskId) { // setIntervalで時間加算の関数(addSecond)を1秒毎に実行させる setTimerId(setInterval(addSecond, 1000)); } else return; }, [props.recordingTaskId]); const addSecond = () => { // 更新関数を渡しているため、この中のtは最新のstateが入っていることが保証されている setTime(t => t + 1); };
親から子コンポーネントのstateにcreateRefを使ってアクセスする
表題の通りだけど、個人的におすすめしない。
refでDOM要素にもコンポーネントにも自由にアクセスできるようにはなるが、せっかくpropsでコンポーネント間のやり取りができるようになっているので、あまりそれを崩したくない。 ちなみに、reactのリファレンスにはrefを使うのに適したケースとして以下が挙げられている。
- フォーカス、テキストの選択およびメディアの再生の管理
- アニメーションの発火
- サードパーティの DOM ライブラリとの統合
が、仕方ない場面はあるのでこういった方法を採用することもある。
下記のようにして、子コンポーネントのstateにアクセスできる。
// クラスA内にBという子コンポーネントがあるとする class A extends React.Component { constructor(props) { super(props); // stateを取得したい子コンポーネントにtestRefを渡すために、ここで作成 this.testRef = React.createRef(); } // クラスB内に作成したtestRefを渡す <B ref={this.testRef} /> // Bコンポーネント内の value というstateにアクセスできる handleBstate = () => { const test = this.testRef.current.state.value } }
繰り返すが、あまりおすすめはしない。 瞬間的に子コンポーネントの値を取得したい、関数を利用したいというケースでもなければあまり使わない方が無難だと思う。