Help us understand the problem. What is going on with this article?

Reactコンポーネントのoptional propsにデフォルト値を設定する

Reactコンポーネントを作っている際、storybookからpropsなしで呼び出してもエラーにならない方法が欲しかったので調べました。

Optional Props

typeに?をつければoptional propsを作れます。

import React from 'react';

export type HogeProps = {
  name?: string;
  age?: number;
};

const Hoge: React.FC<HogeProps> = (props: HogeProps) => {
  return (
    <>
      <div>{props.name}</div>
      <div>{props.age}</div>
    </>
  );
};

export default Hoge;

が、コンポーネント内で値をいじろうとするとundefinedの可能性があるためエラーとなります。
undefinedチェックを行えば回避可能ですがめんどくさいです。

import React from 'react';

export type HogeProps = {
  name?: string;
  age?: number;
};

const Hoge: React.FC<HogeProps> = (props: HogeProps) => {
  const fuga = props.name.substring(0, 2); // Object is possibly 'undefined'.
  const piyo = 100 + props.age; // Object is possibly 'undefined'.
  const fugaValue = props.name === undefined ? 'undefined' : props.name // OK

  return (
    <>
      <div>{fuga}</div>
      <div>{piyo}</div>
    </>
  );
};

export default Hoge;

デフォルト値を設定する

defaultPropsでデフォルト値を設定できますが、関数内では(string or number) | undefinedなのでエラーは解決できません。

import React from 'react';

... // Object is possibly 'undefined'.

Hoge.defaultProps = {
  name: "hage",
  age: 35
}

export default Hoge;

解決法

これを解決するには、コンポーネントの呼び出しではundefinedを許容しつつ内部ではundefinedが除外された別のtypeとして処理するようにします。

import React from 'react';

type PropsBase = {
  name?: string;
  age?: number;
};
const defaultValue = {
  name: "hage",
  age: 35
};
const PropsDefault: Required<
  Pick<PropsBase, { [Key in keyof PropsBase]-?: Key }[keyof PropsBase]>
> = defaultValue;
type Props = PropsBase & typeof PropsDefault;

export { defaultValue as hogeDefaultValue };
export type HogeProps = Props;

const Hoge: React.FC<PropsBase> = (_props: PropsBase) => {
  const props = _props as Props;
  const fuga = props.name.substring(0, 2); // OK
  const piyo = 100 + props.age; // OK

  return (
    <>
      <div>{fuga}</div>
      <div>{piyo}</div>
    </>
  );
};
Hoge.defaultValue = defaultValue;

export default Hoge;

----------

import React from 'react';
import Hoge from './Hoge'

const FooBar: React.FC = () => {
  return(
    <>
      <Hoge> // OK
      <Hoge name={"fusa"}> // OK
      <Hoge name={"fusa"} age={50}> // OK
    </>
  )
};

export default FooBar;

テストの際などにどうなるのか調べてないのでコメントで教えていただければ嬉しいです。

参考

Why don't use optional? in props params when interface work with defaultProps ?
https://tinyurl.com/y4c33as9 (Credit: BY/bocong#4119 at Reactiflux Discord server)

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away