noguzo blog

react-datepickerでcustomInputを使うとTSのエラーやrefのwariningがでてしまう

2020.10.25


概要

ReactでカレンダーUIを簡単に実装しようとした時非常に便利なのがreact-datepickerです。 手間も少なく数行のコードでカレンダーを表示できるので使用する機会も多いのですが、 テキストボックスのUIを変えたい時にTSのエラーやwarningなどが出てしまいます。

今回はそれらのエラーをどう解消したかを書いていきます。 最終的なコードはcodesandboxにあるのでよければ参考にしてください。

今回の説明ででてくるコンポーネント

主なファイルは以下です。 前提として、Input.tsxは共通コンポーネントとして使われており、DatePicker専用のコンポーネントではないとします。

- Input.tsx(テキストボックスのUI)
- App.tsx(DatePickerを使用しているコンポーネント)
// Input.tsx
import React from "react";

interface InputProps {
  className: string;
  value: string;
  onChange: (value: string) => void;
  onClick: () => void;
}
const Input: React.FC<InputProps> = ({
  className,
  value,
  onClick,
  onChange
}) => {
  return (
    <input
      className={className}
      type="text"
      value={value}
      onChange={e => onChange(e.target.value)}
      onClick={onClick}
    />
  );
};

export default Input;
// App.tsx
import React, { useState } from "react";
import DatePicker from "react-datepicker";

export default function App() {
  const [startDate, setStartDate] = useState<Date | null>(new Date());

  return (
    <div>
      <h1>React datepicker</h1>
      <DatePicker selected={startDate} onChange={(date: Date | null) => setStartDate(date)} />
    </div>
  );
}

①customInputに直接Inputコンポーネントを渡すとエラーになる

公式のdocを参考に実装するとこうなります。

// App.tsx
// import 追加
import Input from "./components/Input";
// ...
// customInput追加
      <DatePicker
        selected={startDate}
        customInput={<Input />}
        onChange={(date: Date | null) => setStartDate(date)}
      />

ただ、これだとInputコンポーネントに定義されているpropsを渡せていないのでエラーになってしまいます。

Type '{}' is missing the following properties from type 'InputProps': className, value, onChange, onClickts(2739)

Inputコンポーネントをwrapする形でコンポーネントを作ることでこのエラーは解消しました。

// App.tsx
// 追加
const CustomInput = (props: any) => {
  return <Input {...props} />;
};

// CustomInputに変更
      <DatePicker
        selected={startDate}
        customInput={<CustomInput />}
        onChange={(date: Date | null) => setStartDate(date)}
      />

※もしInputコンポーネントがonClickを受け取っていない場合カレンダーが開かないので追加してください。

これで画面上問題なく表示され、動作も確認できます。 一見いいように見えますが、consoleを確認するとwarningがでてしまっています。

②customInputでrefにアクセスできないというwarningがでてしまう

warningのメッセージはこちら。

Warning: Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?

メッセージにもこのIssueにもある通りforwardRefを使う必要があります。 Stateless customInput? · Issue #862 · Hacker0x01/react-datepicker · GitHub

// App.tsx
const CustomInput = forwardRef((props: any, ref) => {
  return <Input {...props} ref={ref} />;
});

今回はInputコンポーネントを使用しているため、InputコンポーネントでもforwardRefをします。

// Input.tsx
const Input: React.FC<InputProps> = (
  { className, value, onClick, onChange },
  ref
) => {
  return (
    <input
      className={className}
      type="text"
      value={value}
      ref={ref}
      onChange={(e) => onChange(e.target.value)}
      onClick={onClick}
    />
  );
};

export default forwardRef(Input);

react/display-nameのeslintエラーが出る場合は以下のようにfunctionを使うなり少し書き方を変える必要があります。

const CustomInput = forwardRef(function LocalCustomInput(props: any, ref) {
  return <Input {...props} ref={ref} />;
});

これでエラーがでなければ問題ないのですが、もし以下のようなエラーが出てしまう場合はforwardRefにわたすコンポーネントの型を明示します。 (ここがIssueにも書いてなくなかなか解決できませんでした。。)

Argument of type 'FC<InputProps>' is not assignable to parameter of type 'ForwardRefRenderFunction<unknown, InputProps>'.

// Input.tsx
export default forwardRef(Input as React.ForwardRefRenderFunction<HTMLInputElement, InputProps>);

最終的なコードはこちらにあります。 https://codesandbox.io/s/react-datepicker-custominput-sample-t8egb

参考


Frontend engineer.