const [counter, setCounter] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCounter((prevCounter) => prevCounter + 1);
}, 1000);
return () => clearInterval(intervalId);
}, []);
const handleOpenModal = () => {
showModal({
title: "Confirmation",
key: `modal-${counter}-${Date.now()}`,
content: () => (
<div>
<p>Counter Value: {counter}</p>
</div>
),
size: "xl",
intent: "danger",
primaryLabel: "Confirm",
secondaryLabel: "Cancel",
onPrimaryAction: () => console.log("Confirmed!"),
onToggle: (isOpen) => setIsModalOpen(isOpen),
});
};
上面的两个代码块是我父组件的一部分
type ModalProps = {
isVisible: boolean;
onPrimaryAction?: () => void;
onClose?: () => void;
onSecondaryAction?: () => void;
onToggle?: (isOpen: boolean) => void;
title?: string;
intent?: Intent;
primaryLabel?: string;
secondaryLabel?: string;
size?: "sm" | "default" | "lg" | "xl";
content?: ReactNode | ((...args: any[]) => ReactNode);
key?: string;
};
const ModalContext = createContext<{
showModal: (props: Partial<ModalProps>) => void;
hideModal: () => void;
} | null>(null);
function ModalProvider({ children }: { children: ReactNode }) {
const [isVisible, setIsVisible] = useState(false);
const [modalProps, setModalProps] = useState<Partial<ModalProps>>({});
const showModal = useCallback((props: Partial<ModalProps> = {}) => {
setModalProps(props);
setIsVisible(true);
}, []);
const hideModal = useCallback(() => {
setIsVisible(false);
}, []);
return (
<ModalContext.Provider value={{ showModal, hideModal }}>
{children}
<Modal
{...modalProps}
isVisible={isVisible}
onClose={hideModal}
key={modalProps.key}
/>
</ModalContext.Provider>
);
}
export const useModal = () => {
const context = useContext(ModalContext);
if (!context) {
throw new Error("useModal must be used within a ModalProvider");
}
return context;
};
function Modal({
isVisible,
onPrimaryAction,
onClose,
onSecondaryAction,
onToggle,
title,
intent = "default",
primaryLabel,
secondaryLabel,
size = "default",
content,
}: ModalProps) {
useEffect(() => {
const handleKeyDown = (event: KeyboardEvent) => {
if (event.key === "Escape" && isVisible) onClose?.();
};
if (isVisible) document.addEventListener("keydown", handleKeyDown);
return () => document.removeEventListener("keydown", handleKeyDown);
}, [isVisible, onClose]);
useEffect(() => {
onToggle?.(isVisible);
}, [isVisible, onToggle]);
return (
<AnimatePresence>
{isVisible && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={onClose}
>
<motion.div
initial={{ y: 10 }}
animate={{ y: 0 }}
exit={{ y: 10 }}
transition={{ type: "spring", duration: 0.3, bounce: 0.25 }}
onClick={(e) => e.stopPropagation()}
>
<div>
<Text size="lead" weight="semibold">
{title || "Modal Title"}
</Text>
<button type="button" className="modal-btn" onClick={onClose}>
<IoCloseOutline />
</button>
</div>
<div className="flex-1 p-6">
{typeof content === "function" ? content() : content}
</div>
{(secondaryLabel || primaryLabel) && (
<div>
{secondaryLabel && (
<Button
variant="outline"
className="capitalize"
onClick={() => {
onSecondaryAction?.();
onClose?.();
}}
>
{secondaryLabel}
</Button>
)}
{primaryLabel && (
<Button
onClick={onPrimaryAction}
variant="solid"
intent={intent}
className="capitalize "
>
{primaryLabel}
</Button>
)}
</div>
)}
</motion.div>
</motion.div>
)}
</AnimatePresence>
);
}
export default ModalProvider;
现在这是我的模态组件
问题是,当我将计数器(每秒递增)传递给内容并触发模式打开时,它只获取计数器所处的最后状态,它不会随着计数器递增而更新
这只是将动态内容传递到模式的 content 属性的测试用例。
我尝试使用 useEffect 和 onToggle 给我的值重新渲染模态,但这会导致大量重新渲染,而且并不是真正的最佳方法。 问题是什么?我该如何解决它?
我希望我的模态组件能够接受动态和/或静态内容,如果父组件的状态偶尔会发生变化,并且该状态要传递给模态的 content 属性,那么当模态打开时它应该也更新一下。
由于陈旧的闭包,您的 Modal 组件不具有内容的最新值。为了避免这种情况,您可以将最新的计数器值存储在 useRef 中,这允许您访问当前值而无需过时的闭包。
const [counter, setCounter] = useState(0);
const counterRef = useRef(counter);
// Update the ref whenever the counter changes
useEffect(() => {
counterRef.current = counter;
}, [counter]);
useEffect(() => {
const intervalId = setInterval(() => {
setCounter((prevCounter) => prevCounter + 1);
}, 1000);
return () => clearInterval(intervalId);
}, []);
};