同步父子之间的状态

问题描述 投票:0回答:1
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 属性,那么当模态打开时它应该也更新一下。

reactjs typescript next.js react-state
1个回答
0
投票

由于陈旧的闭包,您的 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);
  }, []);
};
© www.soinside.com 2019 - 2024. All rights reserved.