問題
FullCalendar と 同じページに shadcn Dialog を組み込み、 shadcn (Radix UI) Dialog の open ステートを useState で管理していると、 ダイアログの open/close 時に FullCalendar が再レンダリングされてしまう。
- GoogleCalendarのイベントや、独自イベントがある場合、再レンダリングされると、 イベントが消えてしまう。
解決方法
- ref, forwardRefs, useImperativeHandle を使って、再レンダリングを防ぐ。
以下詳細
まずは Dialog側
import {
Ref,
useMemo,
useCallback,
useState,
forwardRef,
useImperativeHandle,
} from 'react';
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
} from '@/components/ui/dialog';
type SampleProps = {
};
export interface SampleDialogRefInterface {
open: () => void;
close: () => void;
}
const SampleDialogButtonInner = (_: SampleProps, ref: Ref<SampleDialogRefInterface>) => {
const [isOpen, setIsOpen] = useState(false);
useImperativeHandle(ref, () => ({
open: () => setIsOpen(true),
close: () => setIsOpen(false),
}));
return (
<Dialog open={isOpen} onOpenChange={setIsOpen}>
{/* 外部からOpenしたいので、DialogTriggerは使わない */}
{/*
<DialogTrigger asChild>
<Button>ボタン</Button>
</DialogTrigger>
*/}
<DialogContent className='h-[90%] overflow-hidden'>
{/* 省略 */}
</DialogContent>
</Dialog>
);
// }
};
const SampleDialogButton = forwardRef(SampleDialogButtonInner);
export default SampleDialogButton;
- SampleProps
- 親側から渡す props
- SampleDialogRefInterface
- ダイアログとして公開したいメソッドを定義する
_: SampleProps
- props は使わないので、
_
で受け取る。propsがないとエラー
- props は使わないので、
ref: Ref<SampleDialogRefInterface>
- ref を受け取る
const [isOpen, setIsOpen] = useState(false);
- ダイアログの open/close ステートを useState で管理
<Dialog open={isOpen} onOpenChange={setIsOpen}>
isOpen
,setIsOpen
を Dialog のopen
,onOpenChange
で紐付け
useImperativeHandle
- ref として公開して、外部から呼ばれた場合の動作を定義
- Dialogの開閉を外部から制御できるようにする
const SampleDialogButton = forwardRef(SampleDialogButtonInner)
forwardRef
で ref を受け取るコンポーネントを作成
FullCalendar 側(親側)
import { EventClickArg } from '@fullcalendar/core';
import FullCalendar from '@fullcalendar/react';
import { useRef, useCallback } from 'react';
import SampleDialogButton, {
SampleDialogRefInterface,
} from '@/app/_components/SampleDialogButton';
export default function Calendar() {
const sampleDialogRef = useRef<SampleDialogRefInterface>(null);
const onDateClick = useCallback(
(arg: DateClickArg) => {
const target = arg.dateStr;
sampleDialogRef.current.open();
},
[],
);
const onEventClick = useCallback((arg: EventClickArg) => {
if (!sampleDialogRef.current) {
return;
}
sampleDialogRef.current.open();
}, []);
return (
<>
<SampleDialogButton ref={sampleDialogRef} />
<FullCalendar
dateClick={onDateClick}
eventClick={onEventClick}
/>
</>
);
}
const sampleDialogRef = useRef<SampleDialogRefInterface>(null);
- Dialog側の公開IF型の ref を作成
const onDateClick = useCallback(
- FullCalendar の dateClick イベントハンドラ
- クリックされた日付を取得し、ダイアログを開く
const onEventClick = useCallback((arg: EventClickArg) => {
- FullCalendar の eventClick イベントハンドラ
- クリックされたイベントを取得し、ダイアログを開く
<SampleDialogButton ref={sampleDialogRef} />
- Dialogコンポーネントをレンダリング
- ref に
sampleDialogRef
を渡す