diff --git a/packages/ui/src/fields/JSON/index.tsx b/packages/ui/src/fields/JSON/index.tsx index b50bf2ffa7..87cf7f32c6 100644 --- a/packages/ui/src/fields/JSON/index.tsx +++ b/packages/ui/src/fields/JSON/index.tsx @@ -10,10 +10,10 @@ import { useField } from '../../forms/useField/index.js' import { withCondition } from '../../forms/withCondition/index.js' import { FieldDescription } from '../FieldDescription/index.js' import { FieldError } from '../FieldError/index.js' -import './index.scss' import { FieldLabel } from '../FieldLabel/index.js' import { mergeFieldStyles } from '../mergeFieldStyles.js' import { fieldBaseClass } from '../shared/index.js' +import './index.scss' const baseClass = 'json-field' @@ -31,10 +31,9 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => { readOnly, validate, } = props - - const [stringValue, setStringValue] = useState() const [jsonError, setJsonError] = useState() - const [hasLoadedValue, setHasLoadedValue] = useState(false) + const inputChangeFromRef = React.useRef<'system' | 'user'>('system') + const [editorKey, setEditorKey] = useState('') const memoizedValidate = useCallback( (value, options) => { @@ -56,6 +55,12 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => { validate: memoizedValidate, }) + const [initialStringValue, setInitialStringValue] = useState(() => + (value || initialValue) !== undefined + ? JSON.stringify(value ?? initialValue, null, 2) + : undefined, + ) + const handleMount = useCallback( (editor, monaco) => { if (!jsonSchema) { @@ -88,7 +93,7 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => { if (readOnly) { return } - setStringValue(val) + inputChangeFromRef.current = 'user' try { setValue(val ? JSON.parse(val) : null) @@ -98,20 +103,21 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => { setJsonError(e) } }, - [readOnly, setValue, setStringValue], + [readOnly, setValue], ) useEffect(() => { - if (hasLoadedValue || value === undefined) { - return + if (inputChangeFromRef.current === 'system') { + setInitialStringValue( + (value || initialValue) !== undefined + ? JSON.stringify(value ?? initialValue, null, 2) + : undefined, + ) + setEditorKey(new Date().toString()) } - setStringValue( - value || initialValue ? JSON.stringify(value ? value : initialValue, null, 2) : '', - ) - - setHasLoadedValue(true) - }, [initialValue, value, hasLoadedValue]) + inputChangeFromRef.current = 'system' + }, [initialValue, value]) const styles = useMemo(() => mergeFieldStyles(field), [field]) @@ -142,12 +148,16 @@ const JSONFieldComponent: JSONFieldClientComponent = (props) => { {BeforeInput} {AfterInput} diff --git a/test/fields/collections/JSON/AfterField.tsx b/test/fields/collections/JSON/AfterField.tsx new file mode 100644 index 0000000000..be7bfd8db0 --- /dev/null +++ b/test/fields/collections/JSON/AfterField.tsx @@ -0,0 +1,38 @@ +'use client' + +import { useField } from '@payloadcms/ui' + +export function AfterField() { + const { setValue } = useField({ path: 'customJSON' }) + + return ( + + ) +} diff --git a/test/fields/collections/JSON/e2e.spec.ts b/test/fields/collections/JSON/e2e.spec.ts index 8cee112ae6..feafa0e053 100644 --- a/test/fields/collections/JSON/e2e.spec.ts +++ b/test/fields/collections/JSON/e2e.spec.ts @@ -103,4 +103,24 @@ describe('JSON', () => { '"foo.with.periods": "bar"', ) }) + + test('should update', async () => { + const createdDoc = await payload.create({ + collection: 'json-fields', + data: { + customJSON: { + default: 'value', + }, + }, + }) + + await page.goto(url.edit(createdDoc.id)) + const jsonField = page.locator('.json-field #field-customJSON') + await expect(jsonField).toContainText('"default": "value"') + + const originalHeight = (await page.locator('#field-customJSON').boundingBox())?.height || 0 + await page.locator('#set-custom-json').click() + const newHeight = (await page.locator('#field-customJSON').boundingBox())?.height || 0 + expect(newHeight).toBeGreaterThan(originalHeight) + }) }) diff --git a/test/fields/collections/JSON/index.tsx b/test/fields/collections/JSON/index.tsx index 8a05ee77e3..5952f4f4c4 100644 --- a/test/fields/collections/JSON/index.tsx +++ b/test/fields/collections/JSON/index.tsx @@ -67,6 +67,16 @@ const JSON: CollectionConfig = { }, ], }, + { + name: 'customJSON', + type: 'json', + admin: { + components: { + afterInput: ['./collections/JSON/AfterField#AfterField'], + }, + }, + label: 'Custom Json', + }, ], versions: { maxPerDoc: 1, diff --git a/test/fields/payload-types.ts b/test/fields/payload-types.ts index 61d47b8f75..5023784986 100644 --- a/test/fields/payload-types.ts +++ b/test/fields/payload-types.ts @@ -1474,6 +1474,15 @@ export interface JsonField { | boolean | null; }; + customJSON?: + | { + [k: string]: unknown; + } + | unknown[] + | string + | number + | boolean + | null; updatedAt: string; createdAt: string; } @@ -3165,6 +3174,7 @@ export interface JsonFieldsSelect { | { jsonWithinGroup?: T; }; + customJSON?: T; updatedAt?: T; createdAt?: T; }