From facd3fbb4d5494a149157e499cf3c0dc3c5ad49c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rge=20N=C3=A6ss?= Date: Mon, 6 Jan 2025 15:32:54 +0100 Subject: [PATCH] fix(structure): set patchRef in an insertion effect instead of regular useEffect (#8194) --- dev/test-studio/schema/debug/patchOnMount.tsx | 30 +++++++++++++++++++ dev/test-studio/schema/index.ts | 2 ++ dev/test-studio/structure/constants.ts | 1 + .../panes/document/DocumentPaneProvider.tsx | 9 ++++-- 4 files changed, 39 insertions(+), 3 deletions(-) create mode 100644 dev/test-studio/schema/debug/patchOnMount.tsx diff --git a/dev/test-studio/schema/debug/patchOnMount.tsx b/dev/test-studio/schema/debug/patchOnMount.tsx new file mode 100644 index 00000000000..1595b5317bb --- /dev/null +++ b/dev/test-studio/schema/debug/patchOnMount.tsx @@ -0,0 +1,30 @@ +import {defineType} from '@sanity/types' +import {useEffect, useRef} from 'react' +import {set} from 'sanity' + +export const patchOnMountDebug = defineType({ + type: 'document', + name: 'patchOnMountDebug', + fields: [ + { + type: 'string', + name: 'title', + }, + ], + components: { + // eslint-disable-next-line func-name-matching + input: function PatchOnMountInput(props) { + const {onChange} = props + const mounted = useRef(false) + + useEffect(() => { + if (!mounted.current) { + onChange(set(`${Math.random()}`, ['title'])) + mounted.current = true + } + }, [onChange]) + + return props.renderDefault(props) + }, + }, +}) diff --git a/dev/test-studio/schema/index.ts b/dev/test-studio/schema/index.ts index b579539513f..ebc53f54904 100644 --- a/dev/test-studio/schema/index.ts +++ b/dev/test-studio/schema/index.ts @@ -53,6 +53,7 @@ import localeString from './debug/localeString' import manyFieldsTest from './debug/manyFieldsTest' import notitle from './debug/notitle' import {objectsDebug} from './debug/objectsDebug' +import {patchOnMountDebug} from './debug/patchOnMount' import poppers from './debug/poppers' import presence, {objectWithNestedArray} from './debug/presence' import previewImageUrlTest from './debug/previewImageUrlTest' @@ -238,6 +239,7 @@ export const schemaTypes = [ recursiveObjectTest, recursiveObject, recursivePopover, + patchOnMountDebug, simpleArrayOfObjects, simpleReferences, reservedFieldNames, diff --git a/dev/test-studio/structure/constants.ts b/dev/test-studio/structure/constants.ts index ffcdea6447a..75b4b5ba8e5 100644 --- a/dev/test-studio/structure/constants.ts +++ b/dev/test-studio/structure/constants.ts @@ -55,6 +55,7 @@ export const DEBUG_INPUT_TYPES = [ 'fieldsetsTest', 'fieldValidationInferReproDoc', 'focusTest', + 'patchOnMountDebug', 'formInputDebug', 'initialValuesTest', 'inspectorsTest', diff --git a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx index d35ec275dad..29ed057106d 100644 --- a/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx +++ b/packages/sanity/src/structure/panes/document/DocumentPaneProvider.tsx @@ -10,7 +10,7 @@ import { import {useToast} from '@sanity/ui' import {fromString as pathFromString, pathFor, resolveKeyedPath} from '@sanity/util/paths' import {omit, throttle} from 'lodash' -import {memo, useCallback, useEffect, useMemo, useRef, useState} from 'react' +import {memo, useCallback, useEffect, useInsertionEffect, useMemo, useRef, useState} from 'react' import deepEquals from 'react-fast-compare' import { type DocumentFieldAction, @@ -297,10 +297,13 @@ export const DocumentPaneProvider = memo((props: DocumentPaneProviderProps) => { const patchRef = useRef<(event: PatchEvent) => void>(() => { throw new Error( - 'Attempted to patch the Sanity document during initial render. Input components should only call `onChange()` in an effect or a callback.', + 'Attempted to patch the Sanity document during initial render or in an `useInsertionEffect`. Input components should only call `onChange()` in a useEffect or an event handler.', ) }) - useEffect(() => { + useInsertionEffect(() => { + // note: this needs to happen in an insertion effect to make sure we're ready to receive patches from child components when they run their effects initially + // in case they do e.g. `useEffect(() => props.onChange(set("foo")), [])` + // Note: although we discourage patch-on-mount, we still support it. patchRef.current = (event: PatchEvent) => { // when creating a new draft if (!editState.draft && !editState.published) {