かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

React JSX の中で if で分岐させたい

React のアプリで例えば編集モードの時にはフィールドで、そうでない時はテキストを表示するなど、条件によって表示を変えたい時など JSX 内で分岐をさせたい場合とか。 ふと思った。

JSX 内で直接 if 文は使えない

JSX は { } 内で JavaScript が実行できるものだと思いこんでいたのですが、実行できるのは式 ( expression ) だけで、iffor のような文は実行できないようです。

if statements and for loops are not expressions in JavaScript, so they can’t be used in JSX directly.
ref. JSX In Depth – React

直接 if 文を使おうとするとエラーになります

function Content(props) {
  return (
    <div className="content">
      {
        if (props.isEdit) {
          return (
            <form onSubmit={props.onUpdate}>
              <input type="text" value={props.text} />
              <button type="submit">UPDATE</button>
            </form>
          )
        } else {
          return (
            <p className="text">{props.text}</p>
          )
        }
       }
    </div>
  );
}

=> Error:Parsing error: Unexpected token`

即時関数内で if 文を使う

関数は JSX 内で実行できるので、即時関数を使ってその中で if 文で分岐させ出力するコンポーネントreturn する

function Content(props) {
  return (
    <div className="content">
      {
        !function() {
          if (props.isEdit) {
            return(
              <form onSubmit={props.onUpdate}>
                <input type="text" value={props.text} />
                <button type="submit">UPDATE</button>
              </form>
            );
          } else {
            return (
              <p className="text">{props.text}</p>
            );
          }
        }()
       }
    </div>
  );
}
Arrow 関数でもOK
function Content(props) {
  return (
    <div className="content">
      {
        (() => {
          if (props.isEdit) {
            return( ... );
          } else {
            return ( ... );
          }
        })()
       }
    </div>
  );
}

即時関数の注意点 最後に ; があるとエラーになる

JSX 内で即時関数を使う場合、関数を実行する (); が付いているとエラーになりるようです。

function Content(props) {
  return (
    <div className="content">
      {
        (() => {
          // ...
        })();
        /* ↑ ; があるとエラーになる */
       }
    </div>
  );
}

=> Parsing error: Unexpected token, expected "}"

三項演算子を使う

三項演算子演算子なので JSX 内で使うことができる

function Content(props) {
  return (
    <div className="content">
      {
        props.isEdit?
          (<form onSubmit={props.onUpdate}>
            <input type="text" value={props.text} />
            <button type="submit">UPDATE</button>
          </form>)
          :
          (<p className="text">{props.text}</p>)
      }
    </div>
  );
}

三項演算子で直接 HTML を返すような書き方は、個人的に見通しが悪いように思うのであまり良くなさそうかなと思いました。出力する HTML を別コンポーネントにすればまだ見通しが立つように思います。

function InputField(props) {
  return (
    <form onSubmit={props.onUpdate}>
      <input type="text" value={props.text} />
      <button type="submit">UPDATE</button>
    </form>
  );
}

function ContentBody(props) {
  return (
    <p className="text">{props.text}</p>
  );
}

function TestComponent(props) {
  return (
    <div className="content">
      {
        props.isEdit?
          (<InputField {...props} />)
          :
          (<ContentBody {...props} />)
      }
    </div>
  );
}

三項演算子の場合も即時関数のときと同じで最後に ; があると Parsing error になる。

true の時だけ表示したいような場合は && が利用できる

JavaScript の比較演算子 &&, || は Bool値を返すのではなく値を返すことを利用すれば、{(条件) && <trueの時に返される値>} と書くことができます。

function TestComponent(props) {
  return (
    <div className="content">
      {props.isNew && (<span>NEW</span>)}
      // ...
    </div>
  );
}
note.
  • `||` は 左辺が `true` なら左辺を返し、左辺が `false` なら右辺を返す
  • `&&` は 左辺が `true` なら右辺を返し、左辺が `false` なら左辺を返す

コンポーネントの形で関数を呼び出し、関数内で if 文などを使う

JSX の <Component /> という記法で Function Component を呼び出せているので、呼び出した関数内では通常の JavaScript が使用できるから、そこで分岐など行えば良い

function InputField(props) {
  return ( ... );
}

function ContentBody(props) {
  return ( ... );
}

function TestComponent(props) {
  const NewLabel = (isNew) => {
    if ( isNew ) {
      return (<span>NEW</span>);
    }
  }

  const Content = (props) => {
    if ( props.isEdit ) {
      return (<InputField {...props} />)
    } else {
      return (<ContentBody {...props} />)
    }
  }

  return (
    <div className="content">
      <NewLabel isNew={props.isNew} />
      <Content {...props}/>
    </div>
  );
}

この方法が個人的には一番見通しが良くメンテナンスもしやすそうだなと思いました。

ポエム

JSX の { } 内で直接実行できるのは式 ( expression ) だけというのは、勘違いしてたので知れてよかったです。( どれが式なのかとかちゃんと理解できて入わけではないのですが… Vue.js には v-ifv-elsev-for という制御するための構文が用意されていますが、もしかする JSX が構文使えないことを改善するために用意されたのかもなーと思いました。(属性で制御するのが見やすいかどうかは、あまり使ってないから何とも言えないし慣れの問題な気もする)

と、即時関数や三項演算子 && などを使えば JSX 内で分岐させる事もできるのですが、個人的に見通しが悪くなるように感じました。
React のチームが JSX 内で if などの文を直接使えなくしているのは、構文を使わなければならないようなモノは別のコンポーネントにしろっていう思想なのではないかなー。と思ったのでした。

別件だけど Arrow関数の即時関数 !() => {}()と書けないみたいなので、改めて調べたい。


[参考]

入門 React ―コンポーネントベースのWebフロントエンド開発

入門 React ―コンポーネントベースのWebフロントエンド開発

React Hooks 親コンポーネントから子コンポーネントをのDOMを ref を使って触りたい。

React Hooks を使ったアプリでボタンの状態やフィールドへのフォーカスなど ref を使った操作を、そのボタンやフィールドの親コンポーネントから呼び出したい時のメモ。

日本語が不自由なのでサンプルを
例えばボタンを押した時に非同期処理をするから、処理が完了するまでボタンを disabled にしておきたいみたいな時

単純に useRef したもの props で渡しても上手くいかない

コンポーネント

import React, { useRef } from 'react';
import SubmitButton from './SubmitButton';

function PostList(props) {
  const submitBtn = useRef(null);

  const onSubmitHandler = async (e) => {
    submitBtn.current.disabled = true;
    try {
      // ...非同期処理
    } catch (err) { ... }
    submitBtn.current.disabled = false;
  };

  return (
    <>
      {/* ...処理 */}
      <SubmitButton
        ref={submitBtn}
        onClick={onSubmitHandler}
      />
    </>
  );  
}

SubmitButton.js

import React from 'react';

function SubmitButton(props) {
  return (
    <button
      className="p-submitBtn"
      ref={props.ref}
      onClick={props.onClick}
    />SUBMIT</button>
  );
}
export default SubmitButton;

useRef したものを props で渡してボタンの ref 属性に付けても親コンポーネントでは null になってしまいうまくいかない。
上記の例ではボタンを押した時に実行される onSubmitHandler 内で submitBtn.currentnull なのでエラーになってしまい意図したとおりに動作しない。

  const onSubmitHandler = async (e) => {
    // submitBtn.current は null
    submitBtn.current.disabled = true;
    // ...
  };

Forwarding Refs を使う

useImperativeHandle customizes the instance value that is exposed to parent components when using ref. As always, imperative code using refs should be avoided in most cases. useImperativeHandle should be used with forwardRef
ref. Hooks API Reference – React #useimperativehandle

React Hooks では子コンポーネント内で useImperativeHandle を使い ref.current から呼び出せる関数を定義して、子コンポーネント自体を forwardRef() でに渡しておくことで親コンポーネントref.current を通じて子コンポーネントref にアクセスすることができる

コンポーネント

import React, { useRef } from 'react';
import SubmitButton from './SubmitButton';

function PostList(props) {
  const submitBtn = useRef(null);

  const onSubmitHandler = async (e) => {
    // ref.current を通じて関数を呼び出すように変更
    submitBtn.current.disabled(false);
    try {
      // ...非同期処理
    } catch (err) { ... }
    submitBtn.current.disabled(false);
  };

  return (
    <>
      {/* ...処理 */}
      <SubmitButton
        ref={submitBtn}
        onClick={onSubmitHandler}
      />
    </>
  );  
}

SubmitButton.js

import React, { useRef, forwardRef, useImperativeHandle } from 'react';

function SubmitButton(props, ref) {
  const btnRef = useRef();

  useImperativeHandle(ref, () => {
    // 親コンポーネントの ref.current から実行できる関数を定義したオブジェクトを返す
    return {
      disabled: (flg) => btnRef.current.disabled = !!flg
    }
  });

  return (
    <button
      className="p-submitBtn"
      ref={btnRef}
      onClick={props.onClick}
    />SUBMIT</button>
  );
}
// コンポーネントを forwardRef でラップ
SubmitButton = forwardRef(SubmitButton);
export default SubmitButton;

これでボタンをクリックした時に、非同期処理が完了するまでボタンを disabled できるようになりました。

ポエム

useImperativeHandle で返すオブジェクトに setter として関数を定義したら親コンポーネントからプロパティみたいにアクセスできるのかな? チョット気になってきたので、時間があるときにでも試してみたい。

react-create-app もデフォルトで Function Component になったし、GW中にだいぶ React Hooks と仲良くなれたので、まとめたものを近々書いておきたい。 (まだ触れてない Hooks もたくさんあるけど…


[参考]

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

React入門 React・Reduxの導入からサーバサイドレンダリングによるUXの向上まで (NEXT ONE)

大人用品… og:image ネタのためだけに👆貼ったけどリスキーだったかな…

JavaScript 配列をオブジェクトに変換したい。

[{id:1, ...}, {id:2, ...}. {id:3, ...}, ...] みたいな配列を{id: {...}, id: {...}, id: {...}, ...} なオブジェクトに形式に変換したい時のメモ。

e. g.

元の配列

const arg = [
  {id: 1, name: '星宮いちご', type: 'cute'},
  {id: 2, name: '霧矢あおい', type: 'cool'},
  {id: 3, name: '紫吹蘭', type: 'sexy'},
];

👇こう変換したい

{
 '1': { id: 1, name: '星宮いちご', type: 'cute' },
 '2': { id: 2, name: '霧矢あおい', type: 'cool' },
 '3': { id: 3, name: '紫吹蘭', type: 'sexy' },
}

for

const obj = {};
for(let i = 0, l = arg.length; i < l; i += 1) {
  const data = arg[i];
  obj[data.id] = data;
}

Array.forEach

const obj = {}
arg.forEach((data, i) => {
  obj[data.id] = data;
});

Array.reduce

初期値に空オブジェクト{}を渡して、それにデータを追加していけばオブジェクトに変換できます。

const obj = arg.reduce((obj, data) => {
  obj[data.id] = data;
  return obj;
}, {});

Array.reduce を使ってもっと短く書く

const obj = arg.reduce((obj, data) => ({...obj, [data.id]: data}), {});

{...obj}obj をコピーして、{[data.id]: data} はオブジェクトのキーを変数で指定して追加しています。 そしてアロー関数で return を省略してオブジェクトを返す際は、返すオブジェクトを括弧 () で囲う必要があるので ({...obj, [data:id]:id}) という形になっています。

ポエム

json-server や Firebase の Cloud Firestore からデータを取得した時とかこんな感じの配列な事が多いイメージです。変更の度に全件データを取得してDOMを更新するのはチョットイケてない感があるので delete や update しやすくするために id で対象のデータを触れるオブジェクト形式に変更したい。みたいなケースがあったので色々ためしてみました。

Array.reduce してオブジェクトをスプレッド演算子でコピーして、 ってパターンは短く書けてイケてる感があるといえばあるのですが、 Hack っぽいし都度オブジェクトをコピーしてる分パフォーマンスは良くなさそうという印象です。
ブラウザで実行されるアプリなら JavaScript はCOOLな書き方より、泥臭くても可読性が良くてパフォーマンスが良い書き方の方が「正義」なんじゃないかな〜と思ってます。


[参考]

ヘヴィーオブジェクト (電撃文庫)

ヘヴィーオブジェクト (電撃文庫)