mirror of
https://github.com/documenso/documenso.git
synced 2026-08-15 02:53:32 +10:00
fix: back envelope editor state with synchronous external store
This commit is contained in:
@@ -15,7 +15,7 @@ import { useToast } from '@documenso/ui/primitives/use-toast';
|
|||||||
import { useLingui } from '@lingui/react/macro';
|
import { useLingui } from '@lingui/react/macro';
|
||||||
import { EnvelopeType, Prisma, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
import { EnvelopeType, Prisma, ReadStatus, SendStatus, SigningStatus } from '@prisma/client';
|
||||||
import type React from 'react';
|
import type React from 'react';
|
||||||
import { createContext, useCallback, useContext, useMemo, useRef, useState } from 'react';
|
import { createContext, useCallback, useContext, useMemo, useRef, useState, useSyncExternalStore } from 'react';
|
||||||
import { useSearchParams } from 'react-router';
|
import { useSearchParams } from 'react-router';
|
||||||
|
|
||||||
import type { TDocumentEmailSettings } from '../../types/document-email';
|
import type { TDocumentEmailSettings } from '../../types/document-email';
|
||||||
@@ -107,7 +107,39 @@ export const EnvelopeEditorProvider = ({
|
|||||||
|
|
||||||
const [_searchParams, setSearchParams] = useSearchParams();
|
const [_searchParams, setSearchParams] = useSearchParams();
|
||||||
|
|
||||||
const [envelope, _setEnvelope] = useState(initialEnvelope);
|
/**
|
||||||
|
* The envelope is kept in a ref-backed external store instead of useState so
|
||||||
|
* that async consumers (debounced autosave callbacks, flushAutosave, resetForms)
|
||||||
|
* can synchronously read the latest value via `getEnvelope`.
|
||||||
|
*
|
||||||
|
* React subscribes to the store through useSyncExternalStore, keeping renders in
|
||||||
|
* sync without maintaining a separate copy of the state.
|
||||||
|
*/
|
||||||
|
const envelopeStoreRef = useRef(initialEnvelope);
|
||||||
|
const envelopeStoreSubscribersRef = useRef(new Set<() => void>());
|
||||||
|
|
||||||
|
const subscribeToEnvelopeStore = useCallback((onStoreChange: () => void) => {
|
||||||
|
envelopeStoreSubscribersRef.current.add(onStoreChange);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
envelopeStoreSubscribersRef.current.delete(onStoreChange);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const getEnvelope = useCallback(() => envelopeStoreRef.current, []);
|
||||||
|
|
||||||
|
const setEnvelope = useCallback((action: React.SetStateAction<TEditorEnvelope>) => {
|
||||||
|
const next = typeof action === 'function' ? action(envelopeStoreRef.current) : action;
|
||||||
|
|
||||||
|
envelopeStoreRef.current = next;
|
||||||
|
|
||||||
|
for (const onStoreChange of envelopeStoreSubscribersRef.current) {
|
||||||
|
onStoreChange();
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const envelope = useSyncExternalStore(subscribeToEnvelopeStore, getEnvelope, getEnvelope);
|
||||||
|
|
||||||
const [autosaveError, setAutosaveError] = useState<boolean>(false);
|
const [autosaveError, setAutosaveError] = useState<boolean>(false);
|
||||||
|
|
||||||
const isCscMode = IS_INSTANCE_CSC_MODE();
|
const isCscMode = IS_INSTANCE_CSC_MODE();
|
||||||
@@ -135,8 +167,6 @@ export const EnvelopeEditorProvider = ({
|
|||||||
};
|
};
|
||||||
}, [isCscMode, providedEditorConfig]);
|
}, [isCscMode, providedEditorConfig]);
|
||||||
|
|
||||||
const envelopeRef = useRef(initialEnvelope);
|
|
||||||
|
|
||||||
const externalFlushCallbacksRef = useRef<Map<string, () => Promise<void>>>(new Map());
|
const externalFlushCallbacksRef = useRef<Map<string, () => Promise<void>>>(new Map());
|
||||||
const pendingMutationsRef = useRef<Set<Promise<unknown>>>(new Set());
|
const pendingMutationsRef = useRef<Set<Promise<unknown>>>(new Set());
|
||||||
|
|
||||||
@@ -156,14 +186,6 @@ export const EnvelopeEditorProvider = ({
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const setEnvelope: typeof _setEnvelope = (action) => {
|
|
||||||
_setEnvelope((prev) => {
|
|
||||||
const next = typeof action === 'function' ? action(prev) : action;
|
|
||||||
envelopeRef.current = next;
|
|
||||||
return next;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const isEmbedded = editorConfig.embedded !== undefined;
|
const isEmbedded = editorConfig.embedded !== undefined;
|
||||||
|
|
||||||
const editorFields = useEditorFields({
|
const editorFields = useEditorFields({
|
||||||
@@ -192,16 +214,18 @@ export const EnvelopeEditorProvider = ({
|
|||||||
try {
|
try {
|
||||||
let recipients: TEditorEnvelope['recipients'] = [];
|
let recipients: TEditorEnvelope['recipients'] = [];
|
||||||
|
|
||||||
|
const currentEnvelope = getEnvelope();
|
||||||
|
|
||||||
if (!isEmbedded) {
|
if (!isEmbedded) {
|
||||||
const response = await setRecipientsMutation.mutateAsync({
|
const response = await setRecipientsMutation.mutateAsync({
|
||||||
envelopeId: envelope.id,
|
envelopeId: currentEnvelope.id,
|
||||||
envelopeType: envelope.type,
|
envelopeType: currentEnvelope.type,
|
||||||
recipients: localRecipients,
|
recipients: localRecipients,
|
||||||
});
|
});
|
||||||
|
|
||||||
recipients = response.data;
|
recipients = response.data;
|
||||||
} else {
|
} else {
|
||||||
recipients = mapLocalRecipientsToRecipients({ envelope, localRecipients });
|
recipients = mapLocalRecipientsToRecipients({ envelope: currentEnvelope, localRecipients });
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnvelope((prev) => ({
|
setEnvelope((prev) => ({
|
||||||
@@ -211,9 +235,7 @@ export const EnvelopeEditorProvider = ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
// Reset the local fields to ensure deleted recipient fields are removed.
|
// Reset the local fields to ensure deleted recipient fields are removed.
|
||||||
editorFields.resetForm(
|
editorFields.resetForm(getEnvelope().fields);
|
||||||
envelope.fields.filter((field) => recipients.some((recipient) => recipient.id === field.recipientId)),
|
|
||||||
);
|
|
||||||
|
|
||||||
setAutosaveError(false);
|
setAutosaveError(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -248,16 +270,18 @@ export const EnvelopeEditorProvider = ({
|
|||||||
try {
|
try {
|
||||||
let fields: TSetEnvelopeFieldsResponse['data'] = [];
|
let fields: TSetEnvelopeFieldsResponse['data'] = [];
|
||||||
|
|
||||||
|
const currentEnvelope = getEnvelope();
|
||||||
|
|
||||||
if (!isEmbedded) {
|
if (!isEmbedded) {
|
||||||
const response = await setFieldsMutation.mutateAsync({
|
const response = await setFieldsMutation.mutateAsync({
|
||||||
envelopeId: envelope.id,
|
envelopeId: currentEnvelope.id,
|
||||||
envelopeType: envelope.type,
|
envelopeType: currentEnvelope.type,
|
||||||
fields: localFields,
|
fields: localFields,
|
||||||
});
|
});
|
||||||
|
|
||||||
fields = response.data;
|
fields = response.data;
|
||||||
} else {
|
} else {
|
||||||
fields = mapLocalFieldsToFields({ envelope, localFields });
|
fields = mapLocalFieldsToFields({ envelope: currentEnvelope, localFields });
|
||||||
}
|
}
|
||||||
|
|
||||||
setEnvelope((prev) => ({
|
setEnvelope((prev) => ({
|
||||||
@@ -309,7 +333,7 @@ export const EnvelopeEditorProvider = ({
|
|||||||
try {
|
try {
|
||||||
const response = !isEmbedded
|
const response = !isEmbedded
|
||||||
? await updateEnvelopeMutation.mutateAsync({
|
? await updateEnvelopeMutation.mutateAsync({
|
||||||
envelopeId: envelope.id,
|
envelopeId: getEnvelope().id,
|
||||||
data,
|
data,
|
||||||
meta,
|
meta,
|
||||||
})
|
})
|
||||||
@@ -467,12 +491,14 @@ export const EnvelopeEditorProvider = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const resetForms = () => {
|
const resetForms = () => {
|
||||||
|
const currentEnvelope = getEnvelope();
|
||||||
|
|
||||||
editorRecipients.resetForm({
|
editorRecipients.resetForm({
|
||||||
recipients: envelopeRef.current.recipients,
|
recipients: currentEnvelope.recipients,
|
||||||
documentMeta: envelopeRef.current.documentMeta,
|
documentMeta: currentEnvelope.documentMeta,
|
||||||
});
|
});
|
||||||
|
|
||||||
editorFields.resetForm(envelopeRef.current.fields);
|
editorFields.resetForm(currentEnvelope.fields);
|
||||||
};
|
};
|
||||||
|
|
||||||
const flushAutosave = async (): Promise<TEditorEnvelope> => {
|
const flushAutosave = async (): Promise<TEditorEnvelope> => {
|
||||||
@@ -488,7 +514,7 @@ export const EnvelopeEditorProvider = ({
|
|||||||
await Promise.allSettled(Array.from(pendingMutationsRef.current));
|
await Promise.allSettled(Array.from(pendingMutationsRef.current));
|
||||||
}
|
}
|
||||||
|
|
||||||
return envelopeRef.current;
|
return getEnvelope();
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user