Merge pull request #1074 from buster-so/big-nate-bus-1875-do-not-ask-twice-to-save-changes-to-metric

pass blocker as memoized function
This commit is contained in:
Nate Kelley 2025-09-23 12:34:09 -06:00 committed by GitHub
commit 38e38786e2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 56 additions and 28 deletions

View File

@ -1737,7 +1737,6 @@ Another escaped: \\<div\\>content\\</div\\>`;
for (let i = 0; i < 3; i++) {
const platejs = await markdownToPlatejs(editor, result);
result = await platejsToMarkdown(editor, platejs);
console.log(i, result);
}
// Metric should not be escaped
@ -1809,7 +1808,6 @@ Text with "quotes" and 'apostrophes'.`;
const platejs = await markdownToPlatejs(editor, markdown);
const serializedPlateJS = JSON.parse(JSON.stringify(platejs));
const backToMarkdown = await platejsToMarkdown(editor, serializedPlateJS);
console.log('backToMarkdown', backToMarkdown);
expect(backToMarkdown).not.toContain('\\<metric');
expect(backToMarkdown).toContain('<metric metricId="json-test-metric"');
@ -1818,8 +1816,6 @@ Text with "quotes" and 'apostrophes'.`;
const serializedPlateJS2 = JSON.parse(JSON.stringify(platejs2));
const finalMarkdown = await platejsToMarkdown(editor, serializedPlateJS2);
console.log('finalMarkdown', finalMarkdown);
// Metric tags should never be escaped during JSON round trips
expect(finalMarkdown).not.toContain('\\<metric');
expect(finalMarkdown).toContain('<metric metricId="json-test-metric"');

View File

@ -1,12 +1,16 @@
import { Store, useStore } from '@tanstack/react-store';
import { useCallback } from 'react';
export const blockerStore = new Store({
export const blockerStore = new Store<{
blocker: boolean;
cooldownBlockerTimer: ReturnType<typeof setTimeout> | null;
}>({
blocker: false,
cooldownBlockerTimer: null,
});
export const setBlocker = (blocker: boolean) => {
blockerStore.setState({ blocker });
blockerStore.setState((v) => ({ ...v, blocker }));
};
export const getBlocker = () => {
@ -14,7 +18,7 @@ export const getBlocker = () => {
};
export const resetBlocker = () => {
blockerStore.setState({ blocker: false });
blockerStore.setState((v) => ({ ...v, blocker: false }));
};
export const useIsBlockerEnabled = () => {
@ -28,3 +32,38 @@ export const useIsBlockerEnabled = () => {
resetBlocker,
};
};
//blocker cooldown timer
export const startCooldownTimer = (timeout: number = 750) => {
const timeoutId = setTimeout(() => {
cancelCooldownTimer();
}, timeout);
blockerStore.setState((v) => ({
...v,
cooldownBlockerTimer: timeoutId,
}));
};
export const cancelCooldownTimer = () => {
if (blockerStore.state.cooldownBlockerTimer) {
clearTimeout(blockerStore.state.cooldownBlockerTimer);
blockerStore.setState((v) => ({ ...v, cooldownBlockerTimer: null }));
}
};
export const useCooldownTimer = () => {
const cooldownBlockerTimer = useStore(
blockerStore,
useCallback(
(v: { cooldownBlockerTimer: typeof blockerStore.state.cooldownBlockerTimer }) =>
v.cooldownBlockerTimer,
[]
)
);
return {
cooldownBlockerTimer,
startCooldownTimer,
cancelCooldownTimer,
};
};

View File

@ -1,11 +1,11 @@
import { useBlocker } from '@tanstack/react-router';
import { useEffect, useRef, useState } from 'react';
import { useEffect } from 'react';
import type { ConfirmProps } from '@/components/ui/modal/ConfirmModal';
import { useMemoizedFn } from '@/hooks/useMemoizedFn';
import { useUnmount } from '@/hooks/useUnmount';
import { timeout } from '@/lib/timeout';
import { useOpenConfirmModal } from '../BusterNotifications';
import { setBlocker } from './blocker-store';
import { setBlocker, useCooldownTimer } from './blocker-store';
export const useBlockerWithModal = ({
onReset,
@ -24,24 +24,10 @@ export const useBlockerWithModal = ({
cancelButtonProps?: ConfirmProps['cancelButtonProps'];
enableBeforeUnload?: boolean;
}) => {
const [explicitlyUnblocked, setExplicitlyUnblocked] = useState(false);
const cooldownTimer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);
const { cooldownBlockerTimer, startCooldownTimer, cancelCooldownTimer } = useCooldownTimer();
const openConfirmModal = useOpenConfirmModal();
const cancelCooldownTimer = () => {
if (cooldownTimer.current) {
clearTimeout(cooldownTimer.current);
cooldownTimer.current = undefined;
}
setExplicitlyUnblocked(false);
};
const startCooldownTimer = () => {
setExplicitlyUnblocked(true);
cooldownTimer.current = setTimeout(() => {
cancelCooldownTimer();
}, 500);
};
const isBlockerDisabled = !enableBlocker || !!cooldownBlockerTimer;
const ensureBlockerIsUnBlocked = useMemoizedFn(async (iteration = 0) => {
if (enableBlocker === false || iteration > 30) {
@ -52,6 +38,12 @@ export const useBlockerWithModal = ({
return ensureBlockerIsUnBlocked(iteration + 1);
});
//this must be a moized funciton because shouldBlockFn caches the function
const checkShouldBlockFn = useMemoizedFn(() => {
if (isBlockerDisabled) return true;
return false;
});
useUnmount(() => {
cancelCooldownTimer();
});
@ -61,13 +53,13 @@ export const useBlockerWithModal = ({
}, [enableBlocker]);
useBlocker({
disabled: !enableBlocker || explicitlyUnblocked,
disabled: isBlockerDisabled,
shouldBlockFn: async () => {
if (!enableBlocker) return false;
if (checkShouldBlockFn()) return false;
const shouldLeave = await openConfirmModal<true>({
title: title || 'Unsaved changes',
content: content,
content,
primaryButtonProps: primaryButtonProps || {
text: 'Yes, leave',
},
@ -79,6 +71,7 @@ export const useBlockerWithModal = ({
await onReset();
await ensureBlockerIsUnBlocked();
startCooldownTimer();
await timeout(25);
return Promise.resolve(true);
},
onCancel: async () => {