1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

ちゃちゃっとMUIでモーダル作ったときのワーニングを解決した話

Posted at

はじめに

MUI(Materiar UI)という、Reactコンポーネントのライブラリがあります。

これを使うことで見た目が整ったコンポーネントを手軽に作成できるようになるので、現場でよく使っています。

その中で、モーダルを使ったときに謎のワーニングがでたので、それを解決した話をまとめます。

何が起きたか

やったこと

以下のように、ちゃちゃっとMUIを使ってモーダルを作成しました。

MyComponent.jsx
import { useState } from "react";
import { Modal, Button } from "@mui/material";
import ChildrenComponent from "./ChildComponent";

const MyComponent = () => {
  const [isOpen, setIsOpen] = useState(false);
  const handleOpenModal = () => setIsOpen(true);
  const handleCloseModal = () => setIsOpen(false);
  return (
    <>
      <Button variant="contained" onClick={handleOpenModal}>
        開く
      </Button>
      <Modal open={isOpen} onClose={handleCloseModal}>
        <ChildrenComponent handleCloseModal={handleCloseModal} />
      </Modal>
    </>
  );
};

export default MyComponent;

子コンポーネントは以下のとおりです。

ChildComponent.jsx
import { Button, Typography, Box } from "@mui/material";

const ChildrenComponent = ({ handleCloseModal }) => {
  return (
    <Box
      sx={{
        padding: "10px",
        margin: "30px auto",
        width: "50%",
        bgcolor: "white",
      }}
    >
      <Typography variant="h6" component="h4">
        テストモーダル
      </Typography>
      <Button variant="contained" onClick={handleCloseModal}>
        閉じる
      </Button>
    </Box>
  );
};

export default ChildrenComponent;

画面は以下のようになります。

モーダルもこの通り。

起きたこと

サクサク作れてMUIめっちゃ便利〜と思っていると、コンソールに以下のようなワーニングが出ているではありませんか。

image.png

Warning: Failed prop type:
Invalid prop `children` supplied to `ForwardRef(Modal)`. 
Expected an element that can hold a ref.
Did you accidentally use a plain function component for an element instead?
For more information see https://mui.com/r/caveat-with-refs-guide

どうやら、Modalコンポーネントのchildrenとして設定しているChildrenComponentがよくないらしいです。refを受け取れるものでなければならないとのこと。

何が原因か

ワーニング内で提示されているURLを確認します。

すべて英語になっていますが、今はChatGPTやClaudeなどのAIがあるので恐れることはないです。

要点をかいつまむと、以下のことが書かれています。

  • MUIのコンポーネントの中にはDOMにアクセスするものがいる
  • DOMにアクセスするにはrefが必要
  • refを受け取ることができるコンポーネントは限られている

つまり、私が作成したChildrenComponentrefを受け取る事ができないコンポーネントであるために、不適切であるといわれていたわけです。

先程のサイトにはrefを受け取ることのできるコンポーネントが全部で6種類列挙されています。
現在一般的にコンポーネントを書く際に使われている関数コンポーネントはその中になく、refを受け取ることができません。

※ここでいうrefとはReactにおけるDOMを操作するためのものです。
Reactは基本的に仮想DOMとなっていますが、refを使うことで直接DOMを操作することができるようになります。

どう解決したか

イケてない解決法

MUIのコンポーネントはすべてrefを受け取ることができるため、以下のようにすればサクッと解決できます。

MyComponent.jsx
import { useState } from "react";
import { Modal, Button, Typography, Box } from "@mui/material";
import ChildrenComponent from "./ChildComponent";

const MyComponent = () => {
  const [isOpen, setIsOpen] = useState(false);
  const handleOpenModal = () => setIsOpen(true);
  const handleCloseModal = () => setIsOpen(false);
  return (
    <>
      <Button variant="contained" onClick={handleOpenModal}>
        開く
      </Button>
      <Modal open={isOpen} onClose={handleCloseModal}>
        <Box
          sx={{
            padding: "10px",
            margin: "30px auto",
            width: "50%",
            bgcolor: "white",
          }}
        >
          <Typography variant="h6" component="h4">
            テストモーダル
          </Typography>
          <Button variant="contained" onClick={handleCloseModal}>
            閉じる
          </Button>
        </Box>
      </Modal>
    </>
  );
};

export default MyComponent;

ChildComponent内に定義していたMUIのコンポーネントをすべて移植しただけです。
ただ、これだとかなり冗長になってしまうので、できれば別コンポーネントに切り出したいところです。

いい感じの解決法

一般的によく使われるであろう解決法は、forwardRef()でラップすることです。
これは公式サイトでも推奨されています。

どのようにするのか、見てみます。

MyComponent.jsx
import { forwardRef, useState } from "react";
import { Modal, Button } from "@mui/material";
import ChildrenComponent from "./ChildComponent";
// forwardRefを使ってコンポーネントを定義
const ChildrenComponentWithRef = forwardRef((props, ref) => {
  return <ChildrenComponent {...props} ref={ref} />;
});

const MyComponent = () => {
  const [isOpen, setIsOpen] = useState(false);
  const handleOpenModal = () => setIsOpen(true);
  const handleCloseModal = () => setIsOpen(false);
  return (
    <>
      <Button variant="contained" onClick={handleOpenModal}>
        開く
      </Button>
      <Modal open={isOpen} onClose={handleCloseModal}>
        {/* 定義したコンポーネントを設定 */}
        <ChildrenComponentWithRef handleCloseModal={handleCloseModal} />
      </Modal>
    </>
  );
};

export default MyComponent;

まず、forwardRefを使ってChildComponentをラップした、新しいコンポーネントを作製します。
forwardRefrefを受け取ることができるので、ワーニングが出なくなります。

そして、ラップした新しいコンポーネントを先程のChildComponentの代わりに指定するだけです。

これだけであっさりワーニングが出なくなりました。

まとめ

ワーニングはプログラムが動かなくなることはないので、ついつい放置してしまいがちです。
しかし、望ましい状態であることには変わりないので確実に潰しておきたいですね。
(よく配列をmapで回すときにkeyを指定し忘れてワーニングでたりしますよね。)

また、MUIはセンスのない私でもお手軽にきれいなサイトを作ることができるので、使い方を覚えておきたいと思いました。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?