🐖

REACT Component内のイベントハンドラ留意事項

2022/06/15に公開

概要

  • REACT + Typescriptフロントエンド環境でAtomic Designの考え方を踏襲し、ボタン等のコンポーネントを最小単位のAtomsとして作成した。
  • 作成したAtomsのボタンコンポーネントをインポートし、onClickなどのイベントハンドラ呼び出しをしても機能しない。
    • onClickがpropsとして扱われている。
  • onClickなどのイベントハンドラを機能させるためには、propsが引き渡された子コンポーネント先で実行するのが正解だと思われる。

既存コード

  • ロジックとView分離のためカスタムフック化した関数を呼び出し、コンポーネント内でonClickイベントハンドラをトリガーに関数を実行。
App.tsx
import { useCount } from "../src/hooks/useCount";
import "./styles.css";

export default function App() {
  const { count, onClickCountUp } = useCount();

  return (
    <div className="App">
      {count}
      <br />
      <button onClick={onClickCountUp}>CountUp</button>
     </div>
  );
}
../src/hooks/useCount.tsx (カスタムフック)
import { useCallback, useState } from "react";

export const useCount = () => {
  const [count, setCount] = useState(0);
  const onClickCountUp = useCallback(() => setCount(count + 1), [
    count,
    setCount
  ]);
  return { count, setCount, onClickCountUp };
};

ATOMSコンポーネントを適用

  • ここで、ボタンにATOMSでデザインしたPrimaryButtonコンポーネントを適用する。
  • エラー:Property 'onClick' does not exist on type 'IntrinsicAttributes & { children?: ReactNode; }'.ts(2322) が発生する。
    • <PrimaryButton onClick={onClickCountUp}>のonClickがpropsとして認識され、子コンポーネントへ渡そうとしている。
App.tsx
import { useCount } from "../src/hooks/useCount";

import { PrimaryButton } from "../src/components/atoms/button/PrimaryButton";
import "./styles.css";

export default function App() {
  const { count, onClickCountUp } = useCount();

  return (
    <div className="App">
      {count}
      <br />
      <PrimaryButton onClick={onClickCountUp}>CountUp</PrimaryButton>
    </div>
  );
}
../src/components/atoms/button/PrimaryButton.tsx (Atomsコンポーネント)
import { FC, memo, ReactNode } from "react";
import styled from "styled-components";

const SButton = styled.button`
  color: #ffffff;
  background-color: #cc6633;
  padding: 8px 40px;
  border: none;
  border-radius: 1000px;
  outline: none;
  &:hover {
    cursor: pointer;
    opacity: 0.7;
  }
`;

type Props = {
  children: ReactNode;
};

export const PrimaryButton: FC<Props> = memo((Props) => {
  const { children } = Props;
  return <SButton>{children}</SButton>;
});

修正したコード

  • propsが引き渡された先の子コンポーネントで、関数として受け取り実行する。
  • カスタムフック化して関数を受け取り、さらに受け取った関数を実行先の子コンポーネントへ引き渡している。
  • onClickイベントの無いbuttonタグはほぼ考えられないため、ATOMSコンポーネントとして機能しているので、一旦はこれで良しとする。(本当かな。。)
App.tsx
import { useCount } from "../src/hooks/useCount";

import { PrimaryButton } from "../src/components/atoms/button/PrimaryButton";
import "./styles.css";

export default function App() {
  const { count, onClickCountUp, onClickCountDown } = useCount();

  return (
    <div className="App">
      {count}
      <br />
      <PrimaryButton onClick={onClickCountUp}>CountUp</PrimaryButton>
    </div>
  );
}
../src/components/atoms/button/PrimaryButton.tsx (Atomsコンポーネント)
import { FC, memo, ReactNode } from "react";
import styled from "styled-components";

const SButton = styled.button`
  color: #ffffff;
  background-color: #cc6633;
  padding: 8px 40px;
  border: none;
  border-radius: 1000px;
  outline: none;
  &:hover {
    cursor: pointer;
    opacity: 0.7;
  }
`;

type Props = {
  children: ReactNode;
  onClick: () => void;
};

export const PrimaryButton: FC<Props> = memo((Props) => {
  const { children, onClick } = Props;
  return <SButton onClick={onClick}>{children}</SButton>;
});

Discussion