VSCodeでTabキーを押すとエディタ画面の入力状態が終わってしまう
超小ネタ。
VSCode でコーディングをしていたら、入力動作がおかしくなった。
入力中に Tab キーを押すと、エディタ画面からカーソルが消える。つまり入力状態が終わってしまう。
調べてみると、「Toggle Tab Key Moves Focus」というモードが ON になっていた?かもしれない。
どのようなキッカケで ON になったかは不明だが、これを OFF にすることで問題は解決した。
自分はコマンドパレットから実行して OFF にした。

React testing library のテスト環境にて、tsyringe による依存注入を有効にする
tsyringe による依存注入
tsyringe を使うと TypeScript アプリケーションにて依存注入を実現することができます。
GitHub - microsoft/tsyringe: Lightweight dependency injection container for JavaScript/TypeScript
アプリケーションコードは以下のような感じ。(とてもシンプルな例)
// クラス定義側 import { injectable } from "tsyringe"; @injectable() export class Foo { constructor(private storage: MyStorage) {} getItem = () => { return this.storage.get('foo') } } // クラス利用側 import "reflect-metadata"; // 依存解決に必要 import { container } from "tsyringe"; import { Foo } from "./foo"; const instance = container.resolve(Foo);
React 環境への組み込み
React 環境への組み込み方は、いくつかの方法があるかもしれませんが、Context 経由で渡す場合は以下のようになります。
// Context提供側 import 'reflect-metadata'; import { createContext } from 'react'; import { DependencyContainer, container } from 'tsyringe'; import { Foo } from "./foo"; container.register("Foo", { useClass: Foo }) // Fooクラスを "Foo" というトークンで登録 export const diContainerContext = createContext<DependencyContainer>(container); export const ContextProvider = ({ children }) => { return ( <diContainerContext.Provider value={container}> {children} </diContainerContext.Provider> ) } export { container }; // Context利用側(useFoo.test.ts) export const useFoo = () => { const diContainer = useContext(diContainerContext) const foo = diContainer.resolve('Foo') // 依存したクラスのメソッドを利用 const item = foo.getItem() return { item } }
もちろん、hooks ではなくコンポーネントからも利用可能です。
テスト環境のための tsyringe 設定
useFoo に対するユニットテストを作成しようとすると、依存性が解決できない、のようなエラーが出ます。
まず、テスト環境でも reflect-metadata を読み込めるようにします。
Jest や vitest なら setupTests.ts のような、ユニットテスト環境をセットアップするためのファイルを作成するかと思います。ここで reflect-metadata を読み込みます。
import 'reflect-metadata'; // 他には、以下のような初期化処理があるかもしれません。 import '@testing-library/jest-dom';
次に、テストファイルで Context を参照できるようにします。
これは、 @testing-library/react の renderHook のオプションを利用すれば OK です。第二引数に wrapper というキー名で、ContextProvider コンポーネントを渡します。
import { renderHook, waitFor } from '@testing-library/react'; import { ConfigProvider } from 'ConfigProvider'; import { useFoo } from './useFoo'; describe('useFoo', () => { it('itemを返す, async () => { renderHook(() => useFoo(), { wrapper: ConfigProvider }); });
以上で、React テスト環境でも tsyringe を使った依存注入が動くようになると思います。
おまけ
ちなみに、tsyringe を使ってモック登録する例が以下の通り。便利ですね。
import { renderHook, waitFor } from '@testing-library/react'; import { ConfigProvider, container } from 'ConfigProvider'; import { useFoo } from './useFoo'; describe('useFoo', () => { it('itemを返す, async () => { // モック関数を登録 container.register('Foo', { useValue: { getItem: () => 'aaa' } }); // 同じ container インスタンスを利用する点に注意 const { item } = renderHook(() => useFoo(), { wrapper: ConfigProvider }); expect(item).toBe('aaa') });
yup で require なフィールドを持つオブジェクト自体を optional にする方法
スキーマバリデーションのライブラリである yup の話です。
以下のようなネストになったスキーマがあるとします。
yup.object({ foo: yup.object({ bar: yup.string.require() }) })
foo は require ではないので任意項目のはずです。しかし、内部の bar が require なため、 foo が存在しないとバリデーションエラーとなります。
以下のように、 default(undefined) を指定すれば OK
yup.object({ foo: yup.object({ bar: yup.string.require() }).default(undefined) })
webpackで特定のwarningを消す(Craco対応
webpack で特定の warning 表示を消す場合。
ignoreWarnings を使う。
module.exports = { //... ignoreWarnings: [ { module: /module2\.js\?[34]/, // A RegExp }, { module: /[13]/, message: /homepage/, }, /warning from compiler/, (warning) => true, ], };
regex を使って file, message, module でのターゲティングが可能。
https://webpack.js.org/configuration/other-options/#ignorewarnings
Craco の場合は webpack.configure.ignoreWarnings というネスティングになる。
module.exports = function() { return { webpack: { configure: { //... ignoreWarnings: [
【TypeScript】yup のカスタムバリデータを追加する
yup でカスタムバリデータを追加する方法です。
サンプルとして「日付っぽい文字列かどうか」のバリデータを追加しました。 Dateライブラリには dayjs を利用しています。
import * as yup from 'yup'; import dayjs from 'dayjs'; import customParseFormat from 'dayjs/plugin/customParseFormat'; dayjs.extend(customParseFormat); // 型定義 declare module 'yup' { interface StringSchema { dateLike(format?: string): StringSchema; } } // カスタムバリデータ yup.addMethod(yup.string, 'dateLike', function (format = 'YYYY/MM/DD') { return this.test({ name: 'dateLike', // 任意の名称 message: '正しい日付を入力してください', test: (value) => value === '' ? true : dayjs(value, format, true).isValid(), }); }); export default yup;
入力された文字列が、事前に受け取ったフォーマットに適合しているかどうかを判定します。不適合なら 正しい日付を入力してください エラーになります。
ちなみに、空文字の場合は always true です。必須チェックをするなら、利用側で .required() をチェーン呼び出しするのが良いと思います。
利用側はこのような感じ。
const schema = yup.object({ start_at: yup.string().dateLike('YYYY/M/D'), end_at: yup.string().dateLike('YYYY/M/D'), })
あとは、 React Hook Form などに渡して、ビュー側で利用できます。
yup のバリデーションで min 指定でも optional にする方法
スキーマバリデーションの yup の話です。
※この記事執筆時点での yup のバージョンは 1.1.1 となります。
文字列の最小文字数を指定するには、 min を使います。
const schema = yup.object().shape({ password: yup.string().default('').min(8).optional() })
この例は「パスワードの入力は必須ではないが、入力するなら8文字以上」という指定のはずでした。
が、フォームを submit しようとすると「未入力」エラーが発生…。optional を指定しているのに…。
調べてみた結果、 when を使って条件文にすると解決できました。
const schema = yup.object().shape({ password: yup.string().when('password', { // もし空文字なら is: (password: string) => password === '', // 任意項目 then: (schema) => schema.optional(), // 空文字でなければ、8文字以上が必須 otherwise: (schema) => schema.default('').min(8).required(), }), [['password', 'password']], })
ちなみに、この書き方だと cyclic dependency エラーが発生しました。
その対策として、 shape の第二引数として [['password', 'password']] を指定しています。
AntDesign のバージョンアップデートにより、css-dev-only-do-not-override に差分が発生してしまう問題への対処法
React のコンポーネントライブラリである AntDesign の話。
AntDesign を利用したコンポーネントに対するスナップショットテストが落ちていた。スナップショットの変更箇所は以下のような感じ。
<!-- before --> <div class="ant-select _select_1826e5 css-dev-only-do-not-override-w8mnev ant-select-single ant-select-show-arrow"> <!-- after --> <div class="ant-select _select_1826e5 css-dev-only-do-not-override-1jr9qlj ant-select-single ant-select-show-arrow">
css-dev-only-do-not-override-... というクラス名が変わっている。調べてみると、 AntDesign をアップデートしたことによる影響だった。
AntDesignのレポジトリにて、以下のイシューを発見。
そもそも、 css-dev-only-do-not-override-... はマイクロフロントエンドなどの戦略を取っているサービスに必要なもの。複数の異なるバージョンの AntDesign が混ざっている場合の、識別子のようなもの。
以下のように、 AntDesign の ConfigProvider を利用すれば OK。
<ConfigProvider theme={{ hashed: false }}> ..<your app here>.. </ConfigProvider>
あるいは、setupファイルで設定してもOK。以下は vitest の例。
vi.mock('antd', async () => { const antd = await vi.importActual('antd'); // @ts-expect-error importActualの型付けをしていない antd.theme.defaultConfig.hashed = false; return antd; });
【AntDesign】Calendar コンポーネントに日本語ロケールを指定する
概要
React のコンポーネントライブラリである Ant Design
その Ant Design の Calendar コンポーネントにて、日本語ロケールを指定する方法。
結論
結論から書くと、以下のように指定しました。 ※アプリケーション全体から参照できるよう、共通コンポーネントとして定義しています。
import dayjs from 'dayjs'; import { Calendar as Original, type CalendarProps } from 'antd'; import enUS from 'antd/es/calendar/locale/en_US'; export const Calendar: React.FC<CalendarProps<dayjs.Dayjs>> = (props) => { return ( <Original {...props} locale={{ ...enUS, lang: { ...enUS.lang, locale: 'ja_JP', placeholder: '日付を選択', rangePlaceholder: ['開始日', '終了日'], today: '今日', now: '現在', month: '月', year: '年', monthBeforeYear: false, shortWeekDays: ['日', '月', '火', '水', '木', '金', '土'], shortMonths: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'], // 他にも指定したいものがある場合、ここに追記 }, }} /> ); };
説明
Ant Design でロケール指定する方法はいくつかありそうです。
まずは ConfigProvider。
import { ConfigProvider } from 'antd'; import frFR from 'antd/locale/fr_FR'; return ( <ConfigProvider locale={frFR}> <App /> </ConfigProvider> );
https://ant.design/docs/react/i18n
次にコンポーネントレベル?での指定。
// The default locale is en-US, if you want to use other locale, just set locale in entry file globally. // import dayjs from 'dayjs'; // import 'dayjs/locale/zh-cn'; // dayjs.locale('zh-cn'); <Calendar dateCellRender={dateCellRender} monthCellRender={monthCellRender} onPanelChange={onPanelChange} onSelect={onSelect} />
内部的に dayjs を利用しているから、 dayjs で指定してくれ、ということらしい。
https://ant.design/components/calendar
しかし、いづれの方法もうまく行かず…。月が Jan, Feb, Mar... 曜日が Su, Mo, Tu... のまま。
そこで、ロケールファイルの全容を発見。
{ "lang": { "locale": "en_US", "placeholder": "Select date", "rangePlaceholder": ["Start date", "End date"], "today": "Today", "now": "Now", "backToToday": "Back to today", "ok": "OK", "clear": "Clear", "month": "Month", "year": "Year", "timeSelect": "Select time", "dateSelect": "Select date", "monthSelect": "Choose a month", "yearSelect": "Choose a year", "decadeSelect": "Choose a decade", "yearFormat": "YYYY", "dateFormat": "M/D/YYYY", "dayFormat": "D", "dateTimeFormat": "M/D/YYYY HH:mm:ss", "monthFormat": "MMMM", "monthBeforeYear": true, "previousMonth": "Previous month (PageUp)", "nextMonth": "Next month (PageDown)", "previousYear": "Last year (Control + left)", "nextYear": "Next year (Control + right)", "previousDecade": "Last decade", "nextDecade": "Next decade", "previousCentury": "Last century", "nextCentury": "Next century", "shortWeekDays": ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], "shortMonths": [ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" ] }, "timePickerLocale": { "placeholder": "Select time" }, "dateFormat": "YYYY-MM-DD", "dateTimeFormat": "YYYY-MM-DD HH:mm:ss", "weekFormat": "YYYY-wo", "monthFormat": "YYYY-MM" }
https://github.com/ant-design/ant-design/blob/master/components/date-picker/locale/example.json
要は、この JSON のフォーマットに沿って、必要な項目を指定すれば OK そう。ということで、冒頭の結論に繋がります。