mirror of
https://github.com/documenso/documenso.git
synced 2026-07-26 09:54:51 +10:00
chore: cleanup useEffect
This commit is contained in:
@@ -1,23 +1,23 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
|
||||
export const useElementBounds = (elementOrSelector: HTMLElement | string, withScroll = false) => {
|
||||
const [bounds, setBounds] = useState({
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: 0,
|
||||
width: 0,
|
||||
});
|
||||
const [forceRecalc, setForceRecalc] = useState(0);
|
||||
|
||||
const calculateBounds = useCallback(() => {
|
||||
const bounds = useMemo(() => {
|
||||
const $el =
|
||||
typeof elementOrSelector === 'string'
|
||||
? document.querySelector<HTMLElement>(elementOrSelector)
|
||||
: elementOrSelector;
|
||||
|
||||
if (!$el) {
|
||||
throw new Error('Element not found');
|
||||
return {
|
||||
top: 0,
|
||||
left: 0,
|
||||
height: 0,
|
||||
width: 0,
|
||||
};
|
||||
}
|
||||
|
||||
if (withScroll) {
|
||||
@@ -32,15 +32,11 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
||||
width,
|
||||
height,
|
||||
};
|
||||
}, [elementOrSelector, withScroll]);
|
||||
|
||||
useEffect(() => {
|
||||
setBounds(calculateBounds());
|
||||
}, []);
|
||||
}, [elementOrSelector, withScroll, forceRecalc]);
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
setBounds(calculateBounds());
|
||||
setForceRecalc((prev) => prev + 1);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
@@ -61,7 +57,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
setBounds(calculateBounds());
|
||||
setForceRecalc((prev) => prev + 1);
|
||||
});
|
||||
|
||||
observer.observe($el);
|
||||
@@ -69,7 +65,7 @@ export const useElementBounds = (elementOrSelector: HTMLElement | string, withSc
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, []);
|
||||
}, [elementOrSelector]);
|
||||
|
||||
return bounds;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/consistent-type-assertions */
|
||||
import { RefObject, useEffect, useState } from 'react';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
/**
|
||||
* Calculate the width and height of a text element.
|
||||
@@ -64,13 +64,7 @@ export function useElementScaleSize(
|
||||
fontSize: number,
|
||||
fontFamily: string,
|
||||
) {
|
||||
const [scalingFactor, setScalingFactor] = useState(1);
|
||||
|
||||
useEffect(() => {
|
||||
const scaleSize = calculateTextScaleSize(container, text, `${fontSize}px`, fontFamily);
|
||||
|
||||
setScalingFactor(scaleSize);
|
||||
}, [text, container, fontFamily, fontSize]);
|
||||
|
||||
return scalingFactor;
|
||||
return useMemo(() => {
|
||||
return calculateTextScaleSize(container, text, `${fontSize}px`, fontFamily);
|
||||
}, [container, text, fontSize, fontFamily]);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import type { Field } from '@prisma/client';
|
||||
|
||||
@@ -6,20 +6,20 @@ import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-c
|
||||
import { PDF_VIEWER_PAGE_SELECTOR } from '@documenso/lib/constants/pdf-viewer';
|
||||
|
||||
export const useFieldPageCoords = (field: Field) => {
|
||||
const [coords, setCoords] = useState({
|
||||
x: 0,
|
||||
y: 0,
|
||||
height: 0,
|
||||
width: 0,
|
||||
});
|
||||
const [forceRecalc, setForceRecalc] = useState(0);
|
||||
|
||||
const calculateCoords = useCallback(() => {
|
||||
const coords = useMemo(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
`${PDF_VIEWER_PAGE_SELECTOR}[data-page-number="${field.page}"]`,
|
||||
);
|
||||
|
||||
if (!$page) {
|
||||
return;
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
height: 0,
|
||||
width: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const { top, left, height, width } = getBoundingClientRect($page);
|
||||
@@ -31,21 +31,17 @@ export const useFieldPageCoords = (field: Field) => {
|
||||
const fieldHeight = (Number(field.height) / 100) * height;
|
||||
const fieldWidth = (Number(field.width) / 100) * width;
|
||||
|
||||
setCoords({
|
||||
return {
|
||||
x: fieldX,
|
||||
y: fieldY,
|
||||
height: fieldHeight,
|
||||
width: fieldWidth,
|
||||
});
|
||||
}, [field.height, field.page, field.positionX, field.positionY, field.width]);
|
||||
|
||||
useEffect(() => {
|
||||
calculateCoords();
|
||||
}, [calculateCoords]);
|
||||
};
|
||||
}, [field.height, field.page, field.positionX, field.positionY, field.width, forceRecalc]);
|
||||
|
||||
useEffect(() => {
|
||||
const onResize = () => {
|
||||
calculateCoords();
|
||||
setForceRecalc((prev) => prev + 1);
|
||||
};
|
||||
|
||||
window.addEventListener('resize', onResize);
|
||||
@@ -53,7 +49,7 @@ export const useFieldPageCoords = (field: Field) => {
|
||||
return () => {
|
||||
window.removeEventListener('resize', onResize);
|
||||
};
|
||||
}, [calculateCoords]);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const $page = document.querySelector<HTMLElement>(
|
||||
@@ -65,7 +61,7 @@ export const useFieldPageCoords = (field: Field) => {
|
||||
}
|
||||
|
||||
const observer = new ResizeObserver(() => {
|
||||
calculateCoords();
|
||||
setForceRecalc((prev) => prev + 1);
|
||||
});
|
||||
|
||||
observer.observe($page);
|
||||
@@ -73,7 +69,7 @@ export const useFieldPageCoords = (field: Field) => {
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [calculateCoords, field.page]);
|
||||
}, [field.page]);
|
||||
|
||||
return coords;
|
||||
};
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
|
||||
const subscribe = () => {
|
||||
return () => {};
|
||||
};
|
||||
|
||||
const getSnapshot = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const getServerSnapshot = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const useIsMounted = () => {
|
||||
const [isMounted, setIsMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setIsMounted(true);
|
||||
}, []);
|
||||
|
||||
return isMounted;
|
||||
return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,23 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useSyncExternalStore } from 'react';
|
||||
|
||||
export const ClientOnly = async ({ children }: { children: React.ReactNode }) => {
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
}, []);
|
||||
|
||||
return mounted ? children : null;
|
||||
const subscribe = () => {
|
||||
return () => {};
|
||||
};
|
||||
|
||||
const getSnapshot = () => {
|
||||
return true;
|
||||
};
|
||||
|
||||
const getServerSnapshot = () => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const ClientOnly = ({
|
||||
children,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}): React.ReactElement | null => {
|
||||
const isClient = useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
|
||||
|
||||
return isClient ? <>{children}</> : null;
|
||||
};
|
||||
|
||||
@@ -18,7 +18,6 @@ import {
|
||||
} from 'lucide-react';
|
||||
import { useFieldArray, useForm } from 'react-hook-form';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { prop, sortBy } from 'remeda';
|
||||
|
||||
import { getBoundingClientRect } from '@documenso/lib/client-only/get-bounding-client-rect';
|
||||
import { useDocumentElement } from '@documenso/lib/client-only/hooks/use-document-element';
|
||||
@@ -103,6 +102,10 @@ export const AddFieldsFormPartial = ({
|
||||
|
||||
const [isMissingSignatureDialogVisible, setIsMissingSignatureDialogVisible] = useState(false);
|
||||
|
||||
const handleMissingSignatureDialogOpenChange = (value: boolean) => {
|
||||
setIsMissingSignatureDialogVisible(value);
|
||||
};
|
||||
|
||||
const { isWithinPageBounds, getFieldPosition, getPage } = useDocumentElement();
|
||||
const { currentStep, totalSteps, previousStep } = useStep();
|
||||
const canRenderBackButtonAsRemove =
|
||||
@@ -511,18 +514,24 @@ export const AddFieldsFormPartial = ({
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const defaultSelectedSigner = useMemo(() => {
|
||||
const recipientsByRoleToDisplay = recipients.filter(
|
||||
(recipient) =>
|
||||
recipient.role !== RecipientRole.CC && recipient.role !== RecipientRole.ASSISTANT,
|
||||
);
|
||||
|
||||
setSelectedSigner(
|
||||
return (
|
||||
recipientsByRoleToDisplay.find((r) => r.sendStatus !== SendStatus.SENT) ??
|
||||
recipientsByRoleToDisplay[0],
|
||||
recipientsByRoleToDisplay[0]
|
||||
);
|
||||
}, [recipients]);
|
||||
|
||||
if (selectedSigner && !recipients.find((r) => r.id === selectedSigner.id)) {
|
||||
setSelectedSigner(defaultSelectedSigner);
|
||||
} else if (!selectedSigner && defaultSelectedSigner) {
|
||||
setSelectedSigner(defaultSelectedSigner);
|
||||
}
|
||||
|
||||
const recipientsByRole = useMemo(() => {
|
||||
const recipientsByRole: Record<RecipientRole, Recipient[]> = {
|
||||
CC: [],
|
||||
@@ -539,29 +548,6 @@ export const AddFieldsFormPartial = ({
|
||||
return recipientsByRole;
|
||||
}, [recipients]);
|
||||
|
||||
const recipientsByRoleToDisplay = useMemo(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
return (Object.entries(recipientsByRole) as [RecipientRole, Recipient[]][])
|
||||
.filter(
|
||||
([role]) =>
|
||||
role !== RecipientRole.CC &&
|
||||
role !== RecipientRole.VIEWER &&
|
||||
role !== RecipientRole.ASSISTANT,
|
||||
)
|
||||
.map(
|
||||
([role, roleRecipients]) =>
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
[
|
||||
role,
|
||||
sortBy(
|
||||
roleRecipients,
|
||||
[(r) => r.signingOrder || Number.MAX_SAFE_INTEGER, 'asc'],
|
||||
[prop('id'), 'asc'],
|
||||
),
|
||||
] as [RecipientRole, Recipient[]],
|
||||
);
|
||||
}, [recipientsByRole]);
|
||||
|
||||
const handleAdvancedSettings = () => {
|
||||
setShowAdvancedSettings((prev) => !prev);
|
||||
};
|
||||
@@ -995,7 +981,7 @@ export const AddFieldsFormPartial = ({
|
||||
|
||||
<MissingSignatureFieldDialog
|
||||
isOpen={isMissingSignatureDialogVisible}
|
||||
onOpenChange={(value) => setIsMissingSignatureDialogVisible(value)}
|
||||
onOpenChange={handleMissingSignatureDialogOpenChange}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { forwardRef, useEffect, useState } from 'react';
|
||||
import { forwardRef, useMemo, useState } from 'react';
|
||||
|
||||
import type { MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
@@ -160,22 +160,32 @@ export const FieldAdvancedSettings = forwardRef<HTMLDivElement, FieldAdvancedSet
|
||||
|
||||
const defaultState: FieldMeta = getDefaultState(field.type);
|
||||
|
||||
const [fieldState, setFieldState] = useState(() => {
|
||||
const savedState = localStorage.getItem(localStorageKey);
|
||||
return savedState ? { ...defaultState, ...JSON.parse(savedState) } : defaultState;
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
const initialFieldState = useMemo(() => {
|
||||
if (fieldMeta && typeof fieldMeta === 'object') {
|
||||
const parsedFieldMeta = ZFieldMetaSchema.parse(fieldMeta);
|
||||
|
||||
setFieldState({
|
||||
return {
|
||||
...defaultState,
|
||||
...parsedFieldMeta,
|
||||
});
|
||||
};
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [fieldMeta]);
|
||||
|
||||
const savedState = localStorage.getItem(localStorageKey);
|
||||
return savedState ? { ...defaultState, ...JSON.parse(savedState) } : defaultState;
|
||||
}, [fieldMeta, defaultState, localStorageKey]);
|
||||
|
||||
const [fieldState, setFieldState] = useState(initialFieldState);
|
||||
|
||||
if (fieldMeta && typeof fieldMeta === 'object') {
|
||||
const parsedFieldMeta = ZFieldMetaSchema.parse(fieldMeta);
|
||||
const expectedState = {
|
||||
...defaultState,
|
||||
...parsedFieldMeta,
|
||||
};
|
||||
|
||||
if (JSON.stringify(fieldState) !== JSON.stringify(expectedState)) {
|
||||
setFieldState(expectedState);
|
||||
}
|
||||
}
|
||||
|
||||
const handleFieldChange = (
|
||||
key: FieldMetaKeys,
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import type { MouseEvent, PointerEvent, RefObject, TouchEvent } from 'react';
|
||||
import { useMemo, useRef, useState } from 'react';
|
||||
import { useLayoutEffect } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { Undo2 } from 'lucide-react';
|
||||
import type { StrokeOptions } from 'perfect-freehand';
|
||||
import { getStroke } from 'perfect-freehand';
|
||||
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import {
|
||||
SIGNATURE_CANVAS_DPI,
|
||||
SIGNATURE_MIN_COVERAGE_THRESHOLD,
|
||||
@@ -247,7 +247,7 @@ export const SignaturePadDraw = ({
|
||||
onChange?.($el.current.toDataURL());
|
||||
};
|
||||
|
||||
unsafe_useEffectOnce(() => {
|
||||
useLayoutEffect(() => {
|
||||
if ($el.current) {
|
||||
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
|
||||
$el.current.height = $el.current.clientHeight * SIGNATURE_CANVAS_DPI;
|
||||
@@ -270,7 +270,7 @@ export const SignaturePadDraw = ({
|
||||
|
||||
img.src = value;
|
||||
}
|
||||
});
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={cn('h-full w-full', className)}>
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
import { useRef } from 'react';
|
||||
import { useLayoutEffect, useRef } from 'react';
|
||||
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { motion } from 'framer-motion';
|
||||
import { UploadCloudIcon } from 'lucide-react';
|
||||
|
||||
import { unsafe_useEffectOnce } from '@documenso/lib/client-only/hooks/use-effect-once';
|
||||
import { SIGNATURE_CANVAS_DPI } from '@documenso/lib/constants/signatures';
|
||||
|
||||
import { cn } from '../../lib/utils';
|
||||
@@ -97,7 +96,7 @@ export const SignaturePadUpload = ({
|
||||
}
|
||||
};
|
||||
|
||||
unsafe_useEffectOnce(() => {
|
||||
useLayoutEffect(() => {
|
||||
// Todo: Not really sure if this is required for uploaded images.
|
||||
if ($el.current) {
|
||||
$el.current.width = $el.current.clientWidth * SIGNATURE_CANVAS_DPI;
|
||||
@@ -121,7 +120,7 @@ export const SignaturePadUpload = ({
|
||||
|
||||
img.src = value;
|
||||
}
|
||||
});
|
||||
}, [value]);
|
||||
|
||||
return (
|
||||
<div className={cn('relative h-full w-full', className)}>
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { useEffect } from 'react';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
@@ -16,7 +14,7 @@ import {
|
||||
DOCUMENT_SIGNATURE_TYPES,
|
||||
} from '@documenso/lib/constants/document';
|
||||
import { SUPPORTED_LANGUAGES } from '@documenso/lib/constants/i18n';
|
||||
import { DEFAULT_DOCUMENT_TIME_ZONE, TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { TIME_ZONES } from '@documenso/lib/constants/time-zones';
|
||||
import { ZDocumentEmailSettingsSchema } from '@documenso/lib/types/document-email';
|
||||
import type { TTemplate } from '@documenso/lib/types/template';
|
||||
import { extractDocumentAuthMethods } from '@documenso/lib/utils/document-auth';
|
||||
@@ -113,7 +111,8 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
meta: {
|
||||
subject: template.templateMeta?.subject ?? '',
|
||||
message: template.templateMeta?.message ?? '',
|
||||
timezone: template.templateMeta?.timezone ?? DEFAULT_DOCUMENT_TIME_ZONE,
|
||||
timezone:
|
||||
template.templateMeta?.timezone ?? Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
||||
dateFormat: (template.templateMeta?.dateFormat ??
|
||||
DEFAULT_DOCUMENT_DATE_FORMAT) as TDocumentMetaDateFormat,
|
||||
@@ -152,14 +151,6 @@ export const AddTemplateSettingsFormPartial = ({
|
||||
)
|
||||
.otherwise(() => false);
|
||||
|
||||
// We almost always want to set the timezone to the user's local timezone to avoid confusion
|
||||
// when the document is signed.
|
||||
useEffect(() => {
|
||||
if (!form.formState.touchedFields.meta?.timezone && !template.templateMeta?.timezone) {
|
||||
form.setValue('meta.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
|
||||
}
|
||||
}, [form, form.setValue, form.formState.touchedFields.meta?.timezone]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<DocumentFlowFormContainerHeader
|
||||
|
||||
Reference in New Issue
Block a user