From 29b20e63d571b2b3b3f6ea26b02b40db385a3215 Mon Sep 17 00:00:00 2001 From: Cody Olsen Date: Fri, 13 Dec 2024 19:21:58 +0100 Subject: [PATCH] fix: replace unsafe `useMemo` with `useState` --- .../components/panes/debug/DebugPane.tsx | 4 ++-- .../tests/formBuilder/utils/TestForm.tsx | 2 +- .../components/scroll/scrollContainer.tsx | 20 +++++++------------ .../context/SanityCreateConfigProvider.tsx | 4 ++-- .../inputs/PortableText/PortableTextInput.tsx | 11 ++++++---- .../inputs/ReferenceInput/useReferenceInfo.ts | 4 ++-- .../src/core/form/store/useFormState.ts | 8 ++++---- .../src/core/form/studio/FormBuilder.test.tsx | 6 +++--- .../scheduledPublishing/hooks/useTimeZone.tsx | 5 ++--- .../filter/inputs/date/CommonDateRange.tsx | 6 +++--- .../tasksFormBuilder/useTasksFormBuilder.ts | 4 ++-- packages/sanity/src/router/RouterProvider.tsx | 2 +- .../structure/components/pane/PaneLayout.tsx | 2 +- .../pane/__workshop__/ResizeStory.tsx | 2 +- .../documentPanel/documentViews/FormView.tsx | 2 +- .../panes/documentList/useDocumentList.ts | 6 +++--- .../structureResolvers/useResolvedPanes.ts | 2 +- 17 files changed, 43 insertions(+), 47 deletions(-) diff --git a/dev/test-studio/components/panes/debug/DebugPane.tsx b/dev/test-studio/components/panes/debug/DebugPane.tsx index d73f97ede52..b0f7de8d704 100644 --- a/dev/test-studio/components/panes/debug/DebugPane.tsx +++ b/dev/test-studio/components/panes/debug/DebugPane.tsx @@ -1,7 +1,7 @@ import {ChevronDownIcon, ChevronRightIcon, ControlsIcon, LinkIcon} from '@sanity/icons' import {Box, Card, Code, Flex, Stack, Text} from '@sanity/ui' import type * as React from 'react' -import {useMemo} from 'react' +import {useMemo, useState} from 'react' import {usePaneRouter, type UserComponent} from 'sanity/structure' function usePaneChildLinkComponent(props: { @@ -48,7 +48,7 @@ export const DebugPane: UserComponent = function DebugPane(props) { // notice that the ID is only created on mount and should not change between // subsequent re-renders, therefore this ID will only change when the parent // component re-renders. - const randomId = useMemo(() => Math.floor(Math.random() * 10000000).toString(16), []) + const [randomId] = useState(() => Math.floor(Math.random() * 10000000).toString(16)) return ( diff --git a/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx b/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx index 742c063ac92..96b71978868 100644 --- a/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx +++ b/packages/sanity/playwright-ct/tests/formBuilder/utils/TestForm.tsx @@ -96,7 +96,7 @@ export function TestForm(props: TestFormProps) { }, ) const [focusPath, setFocusPath] = useState(() => focusPathFromProps || []) - const patchChannel = useMemo(() => createPatchChannel(), []) + const [patchChannel] = useState(() => createPatchChannel()) useGlobalCopyPasteElementHandler({ element: wrapperRef.current, diff --git a/packages/sanity/src/core/components/scroll/scrollContainer.tsx b/packages/sanity/src/core/components/scroll/scrollContainer.tsx index 071aa7e84d2..b7af75ebdb9 100644 --- a/packages/sanity/src/core/components/scroll/scrollContainer.tsx +++ b/packages/sanity/src/core/components/scroll/scrollContainer.tsx @@ -8,8 +8,8 @@ import { useContext, useEffect, useImperativeHandle, - useMemo, useRef, + useState, } from 'react' import {ScrollContext} from 'sanity/_singletons' @@ -20,8 +20,6 @@ export interface ScrollContainerProps onScroll?: (event: Event) => () => void } -const noop = () => undefined - /** * This provides a utility function for use within Sanity Studios to create scrollable containers * It also provides a way for components inside a scrollable container to track onScroll on their first parent scroll container @@ -41,22 +39,18 @@ export const ScrollContainer = forwardRef(function ScrollContainer(forwardedRef, () => ref.current) const parentContext = useContext(ScrollContext) - const childContext = useMemo(() => createPubSub(), []) + const [childContext] = useState(() => createPubSub()) useEffect(() => { - if (onScroll) { - // emit scroll events from children - return childContext.subscribe(onScroll) - } - return noop + if (!onScroll) return undefined + // emit scroll events from children + return childContext.subscribe(onScroll) }, [childContext, onScroll]) useEffect(() => { + if (!parentContext) return undefined // let events bubble up - if (parentContext) { - return childContext.subscribe(parentContext.publish) - } - return noop + return childContext.subscribe(parentContext.publish) }, [parentContext, childContext]) useEffect(() => { diff --git a/packages/sanity/src/core/create/context/SanityCreateConfigProvider.tsx b/packages/sanity/src/core/create/context/SanityCreateConfigProvider.tsx index e604546cd0d..d1ae36a1105 100644 --- a/packages/sanity/src/core/create/context/SanityCreateConfigProvider.tsx +++ b/packages/sanity/src/core/create/context/SanityCreateConfigProvider.tsx @@ -1,4 +1,4 @@ -import {type ReactNode, useMemo} from 'react' +import {type ReactNode, useMemo, useState} from 'react' import {SanityCreateConfigContext} from 'sanity/_singletons' import {useSource} from '../../studio' @@ -19,7 +19,7 @@ export function SanityCreateConfigProvider(props: SanityCreateConfigProviderProp const {children} = props const {beta} = useSource() - const appIdCache = useMemo(() => createAppIdCache(), []) + const [appIdCache] = useState(() => createAppIdCache()) const value = useMemo((): SanityCreateConfigContextValue => { return { diff --git a/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx b/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx index a3aeac6e850..6f504675967 100644 --- a/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx +++ b/packages/sanity/src/core/form/inputs/PortableText/PortableTextInput.tsx @@ -138,10 +138,13 @@ export function PortableTextInput(props: PortableTextInputProps): ReactNode { const toast = useToast() // Memoized patch stream - const patchSubject: Subject<{ - patches: EditorPatch[] - snapshot: PortableTextBlock[] | undefined - }> = useMemo(() => new Subject(), []) + const [patchSubject] = useState( + () => + new Subject<{ + patches: EditorPatch[] + snapshot: PortableTextBlock[] | undefined + }>(), + ) const patches$ = useMemo(() => patchSubject.asObservable(), [patchSubject]) const handleToggleFullscreen = useCallback(() => { diff --git a/packages/sanity/src/core/form/inputs/ReferenceInput/useReferenceInfo.ts b/packages/sanity/src/core/form/inputs/ReferenceInput/useReferenceInfo.ts index 82e7e074de3..cd6aa1a083c 100644 --- a/packages/sanity/src/core/form/inputs/ReferenceInput/useReferenceInfo.ts +++ b/packages/sanity/src/core/form/inputs/ReferenceInput/useReferenceInfo.ts @@ -1,5 +1,5 @@ import {observableCallback} from 'observable-callback' -import {useMemo} from 'react' +import {useMemo, useState} from 'react' import {useObservable} from 'react-rx' import {concat, type Observable, of} from 'rxjs' import {catchError, concatMap, map, startWith} from 'rxjs/operators' @@ -34,7 +34,7 @@ export function useReferenceInfo( getReferenceInfo: GetReferenceInfo, ): Loadable { // NOTE: this is a small message queue to handle retries - const [onRetry$, onRetry] = useMemo(() => observableCallback(), []) + const [[onRetry$, onRetry]] = useState(() => observableCallback()) const referenceInfoObservable = useMemo( () => diff --git a/packages/sanity/src/core/form/store/useFormState.ts b/packages/sanity/src/core/form/store/useFormState.ts index a57912da68b..591bab50dbb 100644 --- a/packages/sanity/src/core/form/store/useFormState.ts +++ b/packages/sanity/src/core/form/store/useFormState.ts @@ -1,7 +1,7 @@ /* eslint-disable camelcase */ import {type ObjectSchemaType, type Path, type ValidationMarker} from '@sanity/types' -import {useMemo} from 'react' +import {useMemo, useState} from 'react' import {type FormNodePresence} from '../../presence' import {useCurrentUser} from '../../store' @@ -53,9 +53,9 @@ export function useFormState< // note: feel free to move these state pieces out of this hook const currentUser = useCurrentUser() - const prepareHiddenState = useMemo(() => createCallbackResolver({property: 'hidden'}), []) - const prepareReadOnlyState = useMemo(() => createCallbackResolver({property: 'readOnly'}), []) - const prepareFormState = useMemo(() => createPrepareFormState(), []) + const [prepareHiddenState] = useState(() => createCallbackResolver({property: 'hidden'})) + const [prepareReadOnlyState] = useState(() => createCallbackResolver({property: 'readOnly'})) + const [prepareFormState] = useState(() => createPrepareFormState()) const reconcileFieldGroupState = useMemo(() => { let last: StateTree | undefined diff --git a/packages/sanity/src/core/form/studio/FormBuilder.test.tsx b/packages/sanity/src/core/form/studio/FormBuilder.test.tsx index 86ac5c75b11..8b80e8a88ba 100644 --- a/packages/sanity/src/core/form/studio/FormBuilder.test.tsx +++ b/packages/sanity/src/core/form/studio/FormBuilder.test.tsx @@ -2,7 +2,7 @@ import {type SanityClient} from '@sanity/client' import {defineType, type Path} from '@sanity/types' import {render} from '@testing-library/react' -import {useMemo} from 'react' +import {useMemo, useState} from 'react' import {beforeEach, describe, expect, it, type Mock, vi} from 'vitest' import {createMockSanityClient} from '../../../../test/mocks/mockSanityClient' @@ -77,7 +77,7 @@ describe('FormBuilder', () => { throw new Error('schema type is not an object') } - const patchChannel = useMemo(() => createPatchChannel(), []) + const [patchChannel] = useState(() => createPatchChannel()) const formState = useFormState({ schemaType, @@ -174,7 +174,7 @@ describe('FormBuilder', () => { throw new Error('schema type is not an object') } - const patchChannel = useMemo(() => createPatchChannel(), []) + const [patchChannel] = useState(() => createPatchChannel()) const formState = useFormState({ schemaType, diff --git a/packages/sanity/src/core/scheduledPublishing/hooks/useTimeZone.tsx b/packages/sanity/src/core/scheduledPublishing/hooks/useTimeZone.tsx index e321aba0f48..115f972ab95 100644 --- a/packages/sanity/src/core/scheduledPublishing/hooks/useTimeZone.tsx +++ b/packages/sanity/src/core/scheduledPublishing/hooks/useTimeZone.tsx @@ -1,7 +1,7 @@ import {useToast} from '@sanity/ui' import {getTimeZones} from '@vvo/tzdb' import {formatInTimeZone, utcToZonedTime, zonedTimeToUtc} from 'date-fns-tz' -import {useCallback, useEffect, useMemo, useState} from 'react' +import {useCallback, useEffect, useState} from 'react' import ToastDescription from '../components/toastDescription/ToastDescription' import {DATE_FORMAT, LOCAL_STORAGE_TZ_KEY} from '../constants' @@ -55,8 +55,7 @@ function getStoredTimeZone(): NormalizedTimeZone { } const useTimeZone = () => { - const initialTimeZone = useMemo(() => getStoredTimeZone(), []) - const [timeZone, setTimeZone] = useState(initialTimeZone) + const [timeZone, setTimeZone] = useState(() => getStoredTimeZone()) const toast = useToast() useEffect(() => { diff --git a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/inputs/date/CommonDateRange.tsx b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/inputs/date/CommonDateRange.tsx index 0798cdac1cb..b5c04ebe4a4 100644 --- a/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/inputs/date/CommonDateRange.tsx +++ b/packages/sanity/src/core/studio/components/navbar/search/components/filters/filter/inputs/date/CommonDateRange.tsx @@ -1,6 +1,6 @@ import {Flex, Stack} from '@sanity/ui' import {addDays} from 'date-fns' -import {useCallback, useMemo} from 'react' +import {useCallback, useState} from 'react' import {useTranslation} from '../../../../../../../../../i18n' import {useSearchState} from '../../../../../contexts/search/useSearchState' @@ -29,8 +29,8 @@ export function CommonDateRangeInput({ * For placeholder values: Use the current date for the end date input, and an arbitrary date * in the past (e.g. -7 days from now) for the start date input. */ - const placeholderStartDate = useMemo(() => addDays(new Date(), PLACEHOLDER_START_DATE_OFFSET), []) - const placeholderEndDate = useMemo(() => new Date(), []) + const [placeholderStartDate] = useState(() => addDays(new Date(), PLACEHOLDER_START_DATE_OFFSET)) + const [placeholderEndDate] = useState(() => new Date()) const handleDatePickerChange = useCallback( ({date, endDate}: {date?: Date | null; endDate?: Date | null}) => { diff --git a/packages/sanity/src/core/tasks/components/form/tasksFormBuilder/useTasksFormBuilder.ts b/packages/sanity/src/core/tasks/components/form/tasksFormBuilder/useTasksFormBuilder.ts index 3bef232bf03..271211db1a3 100644 --- a/packages/sanity/src/core/tasks/components/form/tasksFormBuilder/useTasksFormBuilder.ts +++ b/packages/sanity/src/core/tasks/components/form/tasksFormBuilder/useTasksFormBuilder.ts @@ -1,5 +1,5 @@ import {type ObjectSchemaType, type Path} from '@sanity/types' -import {useCallback, useMemo, useRef, useState} from 'react' +import {useCallback, useRef, useState} from 'react' import { createPatchChannel, @@ -112,7 +112,7 @@ export function useTasksFormBuilder(options: TasksFormBuilderOptions): TasksForm const ready = editState.ready && connectionState === 'connected' - const patchChannel = useMemo(() => createPatchChannel(), []) + const [patchChannel] = useState(() => createPatchChannel()) if (formState === null || !ready) { return {loading: true} } diff --git a/packages/sanity/src/router/RouterProvider.tsx b/packages/sanity/src/router/RouterProvider.tsx index d0f8ada9523..d21166e7dfd 100644 --- a/packages/sanity/src/router/RouterProvider.tsx +++ b/packages/sanity/src/router/RouterProvider.tsx @@ -47,7 +47,7 @@ export interface RouterProviderProps { * import {useCallback, useMemo} from 'react' * * function Root() { - * const router = useMemo(() => route.create('/'), []) + * const [router] = useState(() => route.create('/')) * * const [state, setState] = useState({}) * diff --git a/packages/sanity/src/structure/components/pane/PaneLayout.tsx b/packages/sanity/src/structure/components/pane/PaneLayout.tsx index 31545e633ba..9e84e683d35 100644 --- a/packages/sanity/src/structure/components/pane/PaneLayout.tsx +++ b/packages/sanity/src/structure/components/pane/PaneLayout.tsx @@ -28,7 +28,7 @@ export function PaneLayout( Omit, 'as' | 'height' | 'ref' | 'wrap'>, ) { const {children, minWidth, onCollapse, onExpand, ...restProps} = props - const controller = useMemo(() => createPaneLayoutController(), []) + const [controller] = useState(() => createPaneLayoutController()) const [rootElement, setRootElement] = useState(null) const rootRect = useElementRect(rootElement) const width = rootRect?.width || 0 diff --git a/packages/sanity/src/structure/components/pane/__workshop__/ResizeStory.tsx b/packages/sanity/src/structure/components/pane/__workshop__/ResizeStory.tsx index cd360897a7d..0b95357becd 100644 --- a/packages/sanity/src/structure/components/pane/__workshop__/ResizeStory.tsx +++ b/packages/sanity/src/structure/components/pane/__workshop__/ResizeStory.tsx @@ -17,7 +17,7 @@ const PaneLayoutRoot = styled(Flex)` export default function ResizeStory() { const [rootElement, setRootElement] = useState(null) - const controller = useMemo(() => createPaneLayoutController(), []) + const [controller] = useState(() => createPaneLayoutController()) const collapsed = false const [state, setState] = useState({ expandedElement: null, diff --git a/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx b/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx index b3113c96cb6..930d27386e4 100644 --- a/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx +++ b/packages/sanity/src/structure/panes/document/documentPanel/documentViews/FormView.tsx @@ -61,7 +61,7 @@ export const FormView = forwardRef(function FormV // nodes about both remote and local patches. // - Used by the Portable Text input to modify selections. // - Used by `withDocument` to reset value. - const patchChannel = useMemo(() => createPatchChannel(), []) + const [patchChannel] = useState(() => createPatchChannel()) const isLocked = editState?.transactionSyncLock?.enabled const {t} = useTranslation(structureLocaleNamespace) diff --git a/packages/sanity/src/structure/panes/documentList/useDocumentList.ts b/packages/sanity/src/structure/panes/documentList/useDocumentList.ts index c902b8302b1..650c0474c1b 100644 --- a/packages/sanity/src/structure/panes/documentList/useDocumentList.ts +++ b/packages/sanity/src/structure/panes/documentList/useDocumentList.ts @@ -1,5 +1,5 @@ import {observableCallback} from 'observable-callback' -import {useMemo} from 'react' +import {useMemo, useState} from 'react' import {useObservable} from 'react-rx' import {concat, fromEvent, merge, of} from 'rxjs' import { @@ -75,8 +75,8 @@ export function useDocumentList(opts: UseDocumentListOpts): UseDocumentListHookV [searchFilter, paramsProp], ) - const [onRetry$, onRetry] = useMemo(() => observableCallback(), []) - const [onFetchFullList$, onLoadFullList] = useMemo(() => observableCallback(), []) + const [[onRetry$, onRetry]] = useState(() => observableCallback()) + const [[onFetchFullList$, onLoadFullList]] = useState(() => observableCallback()) const queryResults$ = useMemo(() => { const listenSearchQueryArgs = { diff --git a/packages/sanity/src/structure/structureResolvers/useResolvedPanes.ts b/packages/sanity/src/structure/structureResolvers/useResolvedPanes.ts index eb9acadf64d..824380f69f7 100644 --- a/packages/sanity/src/structure/structureResolvers/useResolvedPanes.ts +++ b/packages/sanity/src/structure/structureResolvers/useResolvedPanes.ts @@ -30,7 +30,7 @@ export interface Panes { } function useRouterPanesStream() { - const routerStateSubject = useMemo(() => new ReplaySubject(1), []) + const [routerStateSubject] = useState(() => new ReplaySubject(1)) const routerPanes$ = useMemo( () => routerStateSubject