かもメモ

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

React Material UI テキストフィールドを readonly にしたい

Material UI の <TextField /> コンポーネントは出力時にいい感じに div で囲ってくれるからか、直接 readonly 属性を渡しても設定されなかった。

  • @mui/material@5.2.7

NG: <TextField /> に readonly 属性を渡しても readonly な状態にはならない

disabled は効くけど readonly は効かない

import { TextField } from '@mui/material';

const MyComponent = (): JSX.Element => {
  return  (
    <>
      <TextField label="disabled" disabled /> {/* disabled になる */}
      <TextField label="readonly" readonly /> {/* readonly にはならない */}
    </>
  );
}

inputProps を使えば OK

inputProps 属性に input タグに渡したい属性を props として渡せるっぽいので、この props 経由で readonly を指定できる

import { TextField } from '@mui/material';

const MyComponent = (): JSX.Element => {
  return  (
    <>
      <TextField label="disabled" disabled /> {/* disabled になる */}
      <TextField label="readonly" inputProps={{ readonly: true }} /> {/* readonly になる */}
    </>
  );
}

<Input /> コンポーネントには直接 readonly を指定できる

<TextField /> コンポーネントは HelpText とか色々とよしなに出力してくれますが、デザイン的に問題がないならシンプルに input を出力する <Input /> コンポーネントを使えば簡単です。

import { Input } from '@mui/material';

const MyComponent = (): JSX.Element => {
  return  (
    <>
      <Input label="disabled" disabled /> {/* disabled になる */}
      <Input label="readonly" readonly /> {/* readonly になる */}
    </>
  );
}

ドキュメントには <FormControl /> で囲って FormControl に disabled を設定して子の <Input /> を disabled にする方法が載ってるけど、readonly も disabled も直接 <Input /> コンポーネントに指定することができました。

cf. Inputs | Text Field React component - MUI

 
今回は単純にライブラリの使い方のメモでした。
おわり


[参考]

かわよい

React + TypeScript import 順をキレイにしたい

React のプロジェクトで import の並び順を揃えて見通しを良くしたい

前提

create-react-app --template typescript で作成したプロジェクトに ESLint と Prettier を導入してある

  • "react-scripts": "^5.0.0"
  • "react": "^17.0.2"
  • "typescript": "~4.1.5"
  • "eslint": "^8.6.0"
  • "prettier": "2.5.1"

1. 使用してない import 自動削除する

eslint-plugin-unused-imports を使用する

$ npm i -D eslint-plugin-unused-imports

.eslintrc.json に設定を追加する

{
  "plugins": [
    "react",
    "@typescript-eslint",
+   "unused-imports"
  ],
  "rules": {
+   "@typescript-eslint/no-unused-vars": "off", // or "no-unused-vars"
+   "unused-imports/no-unused-imports": "error",
+   "unused-imports/no-unused-vars": [
+     "warn",
+     { "vars": "all", "varsIgnorePattern": "^_", "args": "after-used", "argsIgnorePattern": "^_" }
+   ],
  }
}
  • "@typescript-eslint/no-unused-vars" … unused-imports のルールとバッティングするので off にする
  • "unused-imports/no-unused-imports" … unused-imports の使用していない import に対するルール
  • "unused-imports/no-unused-vars" … unused-imports の no-unused-vars ルール
    • "vars": "all" … global スコープを含め全ての変数をチェックする
    • "varsIgnorePattern": "^_", … チェックしない変数を Regexp で指定できる。 _ で始まる変数が使用されてなくても OK にする
    • "args": "after-used" … 引数の使用してない変数に対するチェック。使用している変数が引数の最後の変数であれば、それより前の引数は使ってなくても OK
    • "argsIgnorePattern": "^_" … varsIgnorePattern の引数版。チェックしない引数を Regexp で指定できる。_ から始まる引数は使用のチェックを行わない

cf. no-unused-vars - Rules - ESLint - Pluggable JavaScript linter

2. import の並び順を揃える

eslint-plugin-import で import の順番を強制できる

$ npm i -D eslint-plugin-import

.eslintrc.json に設定を追加

{
  "plugins": [
    "react",
    "@typescript-eslint",
    "unused-imports",
+   "import"
  ],
  "rules": {
    // … 略
+   "import/order": []
  }
}

import の並び順のルール

.eslintrc.jsonimport/order ルール内に import の並び順のルールを記述する
GitHub にあるルールのドキュメント が参考になる

e.g.

{
  "rules": {
    "import/order": [
      "error",
      {
        // グループごとの並び順
        "groups": [
          "builtin",  // 1. fsや path などの node "builtin" のモジュール 
          "external",  // 2. npm install したパッケージ
          "internal", // 3. webpack などでパス設定したモジュール
          ["parent", "sibling"], // 4. 親階層と小階層のファイル
          "object",  // object"-imports
          "type",  // 型だけをインポートする type imports
          "index" // 同階層のファイル
        ],
        // グループごとに改行を入れる
        "newlines-between": "always", // "never" を指定すると改行なし
        // FIXME: ちょっとよく分かってない
        // This defines import types that are not handled by configured pathGroups. This is mostly needed when you want to handle path groups that look like external imports.
        "pathGroupsExcludedImportTypes": ["builtin"],
        // アルファベット順・大文字小文字を区別しない
        "alphabetize": { "order": "asc", "caseInsensitive": true },
        // パターンマッチしたものをグループにする
        // "newlines-between": "always" の場合は pathGroups  ごとに空行が入る
        "pathGroups": [
          // react 関連を external より前にする
          // "pathGroupsExcludedImportTypes": ["react"], にしてみたが `react`, `react-dom` などが別グループになってしまったので pattern で無理やり同じグループにした
          {
            "pattern": "react**",
            "group": "external",
            "position": "before"
          },
          // `@/app`, `@/features/`, `@/libs` の import をひとグループにして internal の前に
          {
            "pattern": "{@/app/**,@/features/**,@/libs/**}",
            "group": "internal",
            "position": "before"
          },
          // `@/components`, `@/pages` の import をグルーピング
          {
            "pattern": "{@/components/**,@/pages/**}",
            "group": "internal",
            "position": "before"
          },
          // CSS module を一番最後に
          {
            "pattern": "./**.module.css",
            "group": "index",
            "position": "after"
          }
        ]
      }
    ]
  }
}

ESLint でフォーマットするスクリプトを追加

未使用の import の削除も import の順番の強制も ESLint のルールなので eslint --fix で修正できる
コードフォーマットの prettier 合わせてフォーマットできるように npm-script を作成しておくと便利

package-json

{
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
+   "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'",
+   "lint:fix": "eslint --fix 'src/**/*.{js,jsx,ts,tsx}'",
+   "format": "npm run lint:fix && prettier --write 'src/**/*.{js,jsx,ts,tsx}'",
    "eject": "react-scripts eject"
  }

👇 フォーマット

$ npm run format

ESLint が実行された後に prettier でフォーマットされていれば OK

VS Code の保存時にフォーマットする

コマンド叩き忘れがないように保存時に自動フォーマットするようにしておく

.vscode/settings.json

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true
  },
}

おわり

ESLint 関連のルール ドキュメントの説明を見てもよくわからんのが結構あるので英語力…ってなる。
個人的にはどうなるのかのサンプルコードを載せていたほうが親切だよなって感じたので、自分がなにか公開する際はそうしようって思いました。

ESLint + Prettier の設定毎回毎回何度も作ってる気がするので、そろそろテンプレート化しておきたい。(作る度にパッケージのアップデートで指定方法が変わってたりするから毎回手直しすることにになってるけど…)


[参考]

ts-node で path alias が効かないにハマる

TypeScript な Express を ts-node で動かしてて tsconfig.json に path alias を設定したんだけどエラーになってなんもわからん。ってなったのでメモ

環境
  • typescript: ^4.5.4
  • ts-node: ^10.4.0
  • ts-node-dev: ^1.1.8

結論

tsconfig-paths を使って ts-node 実行時に -r tsconfig-paths/register オプションを渡してあげれば OK

package.json (entry point が src/index.ts の場合)

{
  "scripts": {
    "start": "ts-node -r tsconfig-paths/register src/index.ts",
    "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts",
    "build": "tsc"
  }
}

tsconfig.json

{
  "compilerOptions": {
    // …
    "baseUrl": "./",
    "paths": {
      "@/*": ["src/*"]
    }
}

/src 内のファイルを @/ で import できるようにパスエイリアスを設定した

npm script

package.json

{
  "scripts": {
    "start": "ts-node src/index.ts",
    // … 
  }
}

ts-node で path alias が効かなくてエラーになる

$ npm run start
> server@1.0.0 start
> ts-node src/index.ts
[ERROR] 23:53:57 Error: Cannot find module '@/data/xxx'

パスエイリアスを使っている module のところでエラーになる
なんもわからん…

ts-node は単体では path alias に対応してないっぽい

That's the node.js error - TypeScript is working fine, but node will never be able to resolve an alias like that unless TypeScript emits code that rewrites paths.
cf. tsconfig.json/paths not working with ts-node · Issue #138 · TypeStrong/ts-node · GitHub

tsconfig-paths を使う

ts-node -r tsconfig-paths/register として呼び出せば ts-node でも path alias が効くようになるっぽい!

$ npm i -D tsconfig-paths

package.json を書き換える

{
  "scripts": {
-   "start": "ts-node src/index.ts",
+   "start": "ts-node -r tsconfig-paths/register src/index.ts",
    // … 
  }

再実行

$ npm run start
> server@1.0.0 start
> ts-node src/index.ts

Start on port 4000.

₍ ᐢ. ̫ .ᐢ ₎👌 解☆決

tsconfig.json に設定する方法もある

typescript-transform-paths を使えば設定を tsconfig.json にまとめられて npm script で -r tsconfig-paths/register オプションを渡さなくてもOKになる

cf. tsconfig.json/paths not working with ts-node · Issue #138 · TypeStrong/ts-node · GitHub

$ npm i -D typescript-transform-paths

package.json

{
  "scripts": {
-   "start": "ts-node -r tsconfig-paths/register src/index.ts",
+   "start": "ts-node src/index.ts",
    // … 
  }

tsconfig.json

{
+ "ts-node": {
+  "transpileOnly": true,
+  "require": [
+   "typescript-transform-paths/register",
+   "tsconfig-paths/register"
+   ]
+ },         
  "compilerOptions": {
    // …

👇

$ npm run start
> server@1.0.0 start
> ts-node src/index.ts

Start on port 4000.

₍ ᐢ. ̫ .ᐢ ₎👌


ts-node-dev での path alias

ts-node では開発時にファイルを監視してのホットリロードができません。以前は nodemon を使ってたのですが ts-node-dev の方が高速でナウでヤングらしいので導入してみました。

$ npm i -D ts-node-dev

ts-node-dev もデフォルトではパスエイリアスが効かない

ts-node-dev も ts-node と同様でデフォルトでは tsconfig.json に書かれているパスエイリアスが効かないので実行時に -r tsconfig-paths/register オプションを渡してあげる必要があります

package.json

{
  "scripts": {
   "start": "ts-node -r tsconfig-paths/register src/index.ts",
   "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts"
    // … 
  }
}

👇

$ npm run dev
> server@1.0.0 dev
> ts-node-dev -r tsconfig-paths/register src/index.ts

[INFO] 00:55:03 ts-node-dev ver. 1.1.8 (using ts-node ver. 9.1.1, typescript ver. 4.5.4)
Start on port 4000.

src/index.ts や import しているファイルを変更した際に自動的に再起動されればOK
₍ ᐢ. ̫ .ᐢ ₎👌👌👌

⚠ ts-node-dev では typescript-transform-paths を使って tsconfig.json にまとめる方法がうまく動作しない

ts-node と同じように tsconfig.json にパスエイリアスの設定をまとめられるかと思ったけど、うまく動作させられなかった

tsconfig.jsonts-node-dev のセクションを追加

{
  "ts-node": {
    // …
  },
+ "ts-node-dev": {
+  "transpileOnly": true,
+  "require": [
+   "typescript-transform-paths/register",
+   "tsconfig-paths/register"
+   ]
+ },         
  "compilerOptions": {
    // …

package.json の npm script からオプションを削除

{
  "scripts": {
   "start": "ts-node  src/index.ts",
-  "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts"
+  "dev": "ts-node-dev src/index.ts"
    // … 
  }

👇 パスエイリアスが解決できない

$ npm run dev
> server@1.0.0 dev
> ts-node-dev src/index.ts

[INFO] 01:01:28 ts-node-dev ver. 1.1.8 (using ts-node ver. 9.1.1, typescript ver. 4.5.4)
Error: Cannot find module '@/data/xxxx'[ERROR] 01:01:32 Error: Cannot find module '@/data/xxxx'

typescript-transform-pathsts-node-dev のセクションを作れないっぽいです。
もしかしたら tsconfig.jsoncompilerOptions 内で設定すればうまく動作させられるかもですが、そこまでは調べませんでした。

所感

typescript-transform-paths を使うとパスエイリアスの解決関連を tsconfig.json にまとめられて npm script の見た目はシンプルになるけど、開発で使うだろう ts-node-dev でいい感じに動作させられなかったし、パッケージも増えてしまうのでシンプルに ts-node, ts-node-dev 両方に -r tsconfig-paths/register オプションを渡してしまうのが良さそうだと思いました。

package.json

{
  "scripts": {
   "start": "ts-node -r tsconfig-paths/register src/index.ts",
   "dev": "ts-node-dev -r tsconfig-paths/register src/index.ts"
    // … 
  }
}

おわり。


[参考]

銀河

銀河

Amazon