かもメモ

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

TypeScript React forwardRef の型をがんばる

react-hook-form というライブラリを使ってフォームを作っていたのですが、input や textarea が Atom レベルのコンポーネントになっており、ライブラリの都合で ref を渡す必要があったので forwardRef の型を頑張ったメモ。

"react": "17.0.2"
"typescript": "4.4.2"

forwardRef<refの対象, props> で書けば OK

type ButtonProps = JSX.IntrinsicElements['button'];
const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => {
    return <button {...props} ref={ref} />
  }
);

ESLint で missing display name というエラーになる

ESLint の設定では次のようなエラーが表示されます。
Component definition is missing display name eslint(react/display-name)

DisplayName allows you to name your component. This name is used by React in debugging messages.
cf. eslint-plugin-react/display-name.md at master · yannickcr/eslint-plugin-react · GitHub

デバック時に関数名が無いと分かりづらいから関数名をつけることを強制するルールのようです。
つまり forwardRef で返す関数が無記名関数なアロー関数になっているので、ここに関数名を付けろということっぽい。

1. アロー関数をやめる

const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>(
- (props, ref) => {
+ (function ButtonComponent(props, ref) {
    return <button {...props} ref={ref} />
  }
);

エラーにならないし、スコープが違うから問題ないのだけれど ButtonComponent という変数と function ButtonComponent があってちょっと気持ち悪いかも?

2. displayName プロパティを付けてしまう

const ButtonComponent = forwardRef<HTMLButtonElement, ButtonProps>(
  (props, ref) => {
    return <button {...props} ref={ref} />
  }
);
+ ButtonComponent.displayName = 'ButtonComponent';

これはこれで無理矢理感ある気もするけれど…

ESLint のドキュメントには上記2つの書き方が載っていたので、どちらでも良さそうです。 (個人的にどちらもあまりしっくりこない)

おまけ forwardRef の型定義を見てみる

forwardRef

interface ForwardRefExoticComponent<P> extends NamedExoticComponent<P> {
        defaultProps?: Partial<P> | undefined;
        propTypes?: WeakValidationMap<P> | undefined;
    }

function forwardRef<T, P = {}>(
  render: ForwardRefRenderFunction<T, P>
): ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>>;

TP という型を受け取って ForwardRefRenderFunction<T, P> という型の render を引数にとって、ForwardRefExoticComponent<PropsWithoutRef<P> & RefAttributes<T>> 型を返す。
forwardRef() 内に渡すコンポーネントの部分は ForwardRefRenderFunction<T, P>

ForwardRefRenderFunction

type ForwardedRef<T> = ((instance: T | null) => void) | MutableRefObject<T | null> | null;

interface ForwardRefRenderFunction<T, P = {}> {
        (props: PropsWithChildren<P>, ref: ForwardedRef<T>): ReactElement | null;
        displayName?: string | undefined;
        // explicit rejected with `never` required due to
        // https://github.com/microsoft/TypeScript/issues/36826
        /**
         * defaultProps are not supported on render functions
         */
        defaultProps?: never | undefined;
        /**
         * propTypes are not supported on render functions
         */
        propTypes?: never | undefined;
    }

forwardRef() にわたすコンポーネントの部分は (props: PropsWithChildren<P>, ref: ForwardedRef<T>): ReactElement | null => {...}displayName のプロパティがあるから function Foo() を渡しても OK ってことかな? (理解できているわけではない)
forwardRef<T, P> なのにコンポーネント部分で (P, T) => {} になってるの紛らわしくないですか???

所管

Chakra UI とか使ってたらよしなに定義されてるから気にしてなかったけど、汎用性の有るコンポーネント作るのめちゃめちゃ難しい…
本件と関係ないけど react-hook-form 色んなものが any 型に推論されるせいでめちゃめちゃ難しい。どうやって使うの??? TypeScript なんもわからんくなった…
TypeScript まだまだ出来ないので型問題でハマるとめちゃめちゃ疲れる。


[参考]

Git リモートブランチのチェックアウトがエラーになる時のメモ

PR確認でリモートのブランチにチェックアウトしようとしたらエラーになってチェックアウトできなくなった。

$ git checkout -b feature/foo origin/feature/foo
fatal: 'origin/feature/foo' is not a commit and a branch 'feature/foo' cannot be created from it

fatal: '<リモートブランチ>' is not a commit and a branch '<作成するブランチ>' cannot be created from it

リモートのブランチが表示されない

$ git branch -a
* develop
  remotes/origin/HEAD -> origin/develop
  remotes/origin/feature
  remotes/origin/develop
  remotes/origin/main

fetch するとエラーになっている

$ git fetch
error: cannot lock ref 'refs/remotes/origin/feature/foo': 'refs/remotes/origin/feature' exists; cannot create 'refs/remotes/origin/feature/foo'
 ! [new branch]      feature/foo           -> origin/feature/foo  (unable to update local ref)

remote に feature というブランチが有る事になっているせいで feature/foo のブランチが作れなくなってるっぽい。リモートの feature ブランチは既にマージ済みでブランチも削除しているのだけどローカルからの参照で残っているようになっているのが原因っぽい

git gc と prune でローカルに保存されている履歴をキレイにすれば OK

$ git gc --prune=now
$ git remote prune origin

確認

$ git fetch
 * [new branch]      feature/foo -> origin/feature/foo
$ git branch -a 
* develop
  remotes/origin/HEAD -> origin/develop
  remotes/origin/develop
  remotes/origin/feature/foo
  remotes/origin/main

リモートブランチが意図したとおりに取得できました!

$ git checkout -b feature/foo origin/feature/foo
Branch 'feature/foo' set up to track remote branch 'feature/foo' from 'origin'.
Switched to a new branch 'feature/foo'

解決!


[参考]

ポンポさんの円盤楽しみ〜

Sass カラーバリエーションをまるっと作りたい

新規のプロジェクトとか受託案件とかの初期段階でない限りプロジェクトで使うカラーバリエーションをまるっと作る機会ってなさそうだけど機械的にカラーバリエーションを作れる方法ができたから記録として。

カラーバリエーションを生成する

// デフォルトのカラーセット
$colors: (
  default: #A0AEC0,
  primary: #3182CE,
  success: #38A169,
  info:    #00B5D8,
  warning: #D69E2E,
  danger:  #E53E3E,
) !default;

@function light($color) {
  @return saturate(lighten($color, 13%), 15%);
}

@function dark($color) {
  @return saturate(darken($color, 13%), 7%);
}

// カラーバリエーションの map を返す
@function create_colors_map($color) {
  @return (
    default: $color,
    light: light($color),
    dark: dark($color),
  );
}

// $colors のキーにカラーバリエーションが割り当てられた map を作成
$ui-colors: ();
@each $color, $hex in $colors {
  $map: ($color: create_colors_map($hex));
  $ui-colors: map-merge($ui-colors, $map);
}

// $ui-colors からキーワードとバリエーションのカラーを返す関数
@function ui-color($key: default, $type: default) {
  $colorSet: map-get($ui-colors, $key);
  @if $colorSet == null {
    @return null;
  }
  
  @return map-get($colorSet, $type);
}

使い方

.bg-praimary {
  background-color: ui-color(primary); // => #3182CE
}

.bg-praimary--light {
  background-color: ui-color(primary, light); // => #58a3e9
}

.bg-praimary--dark {
  background-color: ui-color(primary, dark); // => #1e609f
}

// each でまるっとクラス定義
@each $key in map-keys($colors) {
  .bg-#{$key} {
    background-color: ui-color($key);
     &--light {
      background-color: ui-color($key, light);
    }
    &--dark {
      background-color: ui-color($key, dark);
    }
  }
}

₍ ᐢ. ̫ .ᐢ ₎ できた。

機械的に色を変更するならこのコードを使えば簡単にカラーバリエーションが作れそうです。
ただ元の色によっては同じ設定だと色味が明るすぎるとか暗すぎるとかになると思うので、丁寧にやるなら個別設定とか設定上書きするとかですかね〜

所管

Sass (SCSS) とか書くの1億年ぶり過ぎて map もループも変数の扱いも綺麗サッパリ忘れちゃってたね!ガハハ

Sass (SCSS) ちょとした実験にもコンパイルが必要だから毎度 Code pen とかに書いてたけど sassmeister ってオンラインでどうコンパイルされるか見れるツールがあるんですね! (debug が見れたら最高だったのだが… )


[参考]