diff --git a/components/forms.ts b/components/forms.ts index 39abf0bfb2..ba81e00716 100644 --- a/components/forms.ts +++ b/components/forms.ts @@ -6,7 +6,8 @@ export { useFormModified, } from '../dist/admin/components/forms/Form/context'; -export { default as useFieldType } from '../dist/admin/components/forms/useFieldType'; +export { default as useField } from '../dist/admin/components/forms/useField'; +export { default as useFieldType } from '../dist/admin/components/forms/useField'; export { default as Form } from '../dist/admin/components/forms/Form'; diff --git a/demo/collections/CustomComponents/components/fields/Select/Field/index.tsx b/demo/collections/CustomComponents/components/fields/Select/Field/index.tsx new file mode 100644 index 0000000000..e3c35481ef --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/Select/Field/index.tsx @@ -0,0 +1,55 @@ +import React, { useCallback, useState } from 'react'; +import SelectInput from '../../../../../../../src/admin/components/forms/field-types/Select'; +import { Props as SelectFieldType } from '../../../../../../../src/admin/components/forms/field-types/Select/types'; +import useField from '../../../../../../../src/admin/components/forms/useField'; + +const Select: React.FC = (props) => { + const { + path, + name, + label, + options + } = props; + + const { + value, + setValue + } = useField({ + path + }); + + const onChange = useCallback((incomingValue) => { + const sendToCRM = async () => { + try { + const req = await fetch('https://fake-crm.com', { + method: 'post', + body: JSON.stringify({ + someKey: incomingValue + }) + }); + + const res = await req.json(); + if (res.ok) { + console.log('Successfully synced to CRM.') + } + } catch (e) { + console.error(e); + } + } + + sendToCRM(); + setValue(incomingValue) + }, []) + + return ( + + ) +}; + +export default Select; diff --git a/demo/collections/CustomComponents/components/fields/Text/Field/index.tsx b/demo/collections/CustomComponents/components/fields/Text/Field/index.tsx new file mode 100644 index 0000000000..580486d2cf --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/Text/Field/index.tsx @@ -0,0 +1,35 @@ +import React, { useCallback, useState } from 'react'; +import TextInput from '../../../../../../../src/admin/components/forms/field-types/Text'; +import { Props as TextFieldType } from '../../../../../../../src/admin/components/forms/field-types/Text/types'; +import useField from '../../../../../../../src/admin/components/forms/useField'; + +const Text: React.FC = (props) => { + const { + path, + name, + label + } = props; + + const { + value, + setValue + } = useField({ + path + }); + + const onChange = useCallback((incomingValue) => { + const valueWithoutSpaces = incomingValue.replace(/\s/g, ''); + setValue(valueWithoutSpaces) + }, []) + + return ( + + ) +}; + +export default Text; diff --git a/demo/collections/CustomComponents/components/fields/UI/Field/index.tsx b/demo/collections/CustomComponents/components/fields/UI/Field/index.tsx new file mode 100644 index 0000000000..dd56f4d6f7 --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/UI/Field/index.tsx @@ -0,0 +1,50 @@ +import React, { useCallback } from 'react'; +import TextInput from '../../../../../../../src/admin/components/forms/field-types/Text'; +import { UIField as UIFieldType } from '../../../../../../../src/fields/config/types'; +import SelectInput from '../../../../../../../src/admin/components/forms/field-types/Select'; + +const UIField: React.FC = () => { + const [textValue, setTextValue] = React.useState(''); + const [selectValue, setSelectValue] = React.useState(''); + + const onTextChange = useCallback((incomingValue) => { + setTextValue(incomingValue); + }, []) + + const onSelectChange = useCallback((incomingValue) => { + setSelectValue(incomingValue); + }, []) + + return ( +
+ + +
+ ) +}; + +export default UIField; diff --git a/demo/collections/CustomComponents/components/fields/Upload/Field/index.tsx b/demo/collections/CustomComponents/components/fields/Upload/Field/index.tsx new file mode 100644 index 0000000000..dd5b0f4967 --- /dev/null +++ b/demo/collections/CustomComponents/components/fields/Upload/Field/index.tsx @@ -0,0 +1,38 @@ +import React, { useCallback, useState } from 'react'; +import Upload from '../../../../../../../src/admin/components/forms/field-types/Upload'; +import { Props as UploadFieldType } from '../../../../../../../src/admin/components/forms/field-types/Upload/types'; +import useField from '../../../../../../../src/admin/components/forms/useField'; + +const Text: React.FC = (props) => { + const { + path, + name, + label, + relationTo, + fieldTypes + } = props; + + const { + value, + setValue + } = useField({ + path + }); + + const onChange = useCallback((incomingValue) => { + setValue(incomingValue) + }, []) + + return ( + + ) +}; + +export default Text; diff --git a/demo/collections/CustomComponents/index.ts b/demo/collections/CustomComponents/index.ts index 2320a8bc1a..e09ef5ae1f 100644 --- a/demo/collections/CustomComponents/index.ts +++ b/demo/collections/CustomComponents/index.ts @@ -1,11 +1,15 @@ import { CollectionConfig } from '../../../src/collections/config/types'; import DescriptionField from './components/fields/Description/Field'; +import TextField from './components/fields/Text/Field'; +import SelectField from './components/fields/Select/Field'; +import UploadField from './components/fields/Upload/Field'; import DescriptionCell from './components/fields/Description/Cell'; import DescriptionFilter from './components/fields/Description/Filter'; import NestedArrayField from './components/fields/NestedArrayCustomField/Field'; import GroupField from './components/fields/Group/Field'; import NestedGroupField from './components/fields/NestedGroupCustomField/Field'; import NestedText1Field from './components/fields/NestedText1/Field'; +import UIField from './components/fields/UI/Field'; import ListView from './components/views/List'; import CustomDescriptionComponent from '../../customComponents/Description'; @@ -25,11 +29,69 @@ const CustomComponents: CollectionConfig = { unique: true, localized: true, }, + { + name: 'text', + label: 'Custom text field (removes whitespace)', + type: 'text', + required: true, + localized: true, + admin: { + components: { + Field: TextField, + }, + }, + }, + { + name: 'select', + label: 'Custom select field (sends value to crm)', + type: 'select', + localized: true, + options: [ + { + label: 'Option 1', + value: '1', + }, + { + label: 'Option 2', + value: '2', + }, + { + label: 'Option 3', + value: '3', + }, + ], + admin: { + components: { + Field: SelectField, + }, + }, + }, + { + name: 'ui', + label: 'UI', + type: 'ui', + admin: { + components: { + Field: UIField, + }, + }, + }, + { + name: 'upload', + label: 'Upload', + type: 'upload', + relationTo: 'media', + localized: true, + admin: { + components: { + Field: UploadField, + }, + }, + }, { name: 'description', label: 'Description', type: 'textarea', - required: true, localized: true, admin: { components: { diff --git a/docs/admin/components.mdx b/docs/admin/components.mdx index 95aadf323e..9c24fb07f4 100644 --- a/docs/admin/components.mdx +++ b/docs/admin/components.mdx @@ -90,13 +90,13 @@ All Payload fields support the ability to swap in your own React components. So, #### Sending and receiving values from the form -When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useFieldType` hook as follows: +When swapping out the `Field` component, you'll be responsible for sending and receiving the field's `value` from the form itself. To do so, import the `useField` hook as follows: ```js -import { useFieldType } from 'payload/components/forms'; +import { useField } from 'payload/components/forms'; const CustomTextField = ({ path }) => { - const { value, setValue } = useFieldType({ path }); + const { value, setValue } = useField({ path }); return ( = (props) => { const { label, path, readOnly } = props; - const { value, setValue } = useFieldType({ path }); + const { value, setValue } = useField({ path }); const classes = [ baseClass, diff --git a/src/admin/components/forms/field-types/Array/Array.tsx b/src/admin/components/forms/field-types/Array/Array.tsx index a51001e277..be2d940073 100644 --- a/src/admin/components/forms/field-types/Array/Array.tsx +++ b/src/admin/components/forms/field-types/Array/Array.tsx @@ -7,7 +7,7 @@ import DraggableSection from '../../DraggableSection'; import reducer from '../rowReducer'; import { useForm } from '../../Form/context'; import buildStateFromSchema from '../../Form/buildStateFromSchema'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Error from '../../Error'; import { array } from '../../../../../fields/validations'; import Banner from '../../../elements/Banner'; @@ -66,7 +66,7 @@ const ArrayFieldType: React.FC = (props) => { errorMessage, value, setValue, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, disableFormData, diff --git a/src/admin/components/forms/field-types/Blocks/Blocks.tsx b/src/admin/components/forms/field-types/Blocks/Blocks.tsx index 7cc49cd83a..d6a18f20ce 100644 --- a/src/admin/components/forms/field-types/Blocks/Blocks.tsx +++ b/src/admin/components/forms/field-types/Blocks/Blocks.tsx @@ -12,7 +12,7 @@ import { useForm } from '../../Form/context'; import buildStateFromSchema from '../../Form/buildStateFromSchema'; import DraggableSection from '../../DraggableSection'; import Error from '../../Error'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Popup from '../../../elements/Popup'; import BlockSelector from './BlockSelector'; import { blocks as blocksValidator } from '../../../../../fields/validations'; @@ -76,7 +76,7 @@ const Blocks: React.FC = (props) => { errorMessage, value, setValue, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, disableFormData, @@ -110,7 +110,7 @@ const Blocks: React.FC = (props) => { if (preferencesKey) { const preferences: DocumentPreferences = await getPreference(preferencesKey); - const preferencesToSet = preferences || { fields: { } }; + const preferencesToSet = preferences || { fields: {} }; let newCollapsedState = preferencesToSet?.fields?.[path]?.collapsed .filter((filterID) => (rows.find((row) => row.id === filterID))) || []; diff --git a/src/admin/components/forms/field-types/Checkbox/index.tsx b/src/admin/components/forms/field-types/Checkbox/index.tsx index d49342318c..4bf44687b5 100644 --- a/src/admin/components/forms/field-types/Checkbox/index.tsx +++ b/src/admin/components/forms/field-types/Checkbox/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import withCondition from '../../withCondition'; import Error from '../../Error'; import { checkbox } from '../../../../../fields/validations'; @@ -41,7 +41,7 @@ const Checkbox: React.FC = (props) => { showError, errorMessage, setValue, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, disableFormData, diff --git a/src/admin/components/forms/field-types/Code/Code.tsx b/src/admin/components/forms/field-types/Code/Code.tsx index 5c4f4b2acd..bf6c0db10c 100644 --- a/src/admin/components/forms/field-types/Code/Code.tsx +++ b/src/admin/components/forms/field-types/Code/Code.tsx @@ -3,7 +3,7 @@ import Editor from 'react-simple-code-editor'; import { highlight, languages } from 'prismjs/components/prism-core'; import 'prismjs/components/prism-clike'; import 'prismjs/components/prism-javascript'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import withCondition from '../../withCondition'; import Label from '../../Label'; import Error from '../../Error'; @@ -52,7 +52,7 @@ const Code: React.FC = (props) => { showError, setValue, errorMessage, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, enableDebouncedValue: true, diff --git a/src/admin/components/forms/field-types/ConfirmPassword/index.tsx b/src/admin/components/forms/field-types/ConfirmPassword/index.tsx index ede7f5b335..3428f2fb2c 100644 --- a/src/admin/components/forms/field-types/ConfirmPassword/index.tsx +++ b/src/admin/components/forms/field-types/ConfirmPassword/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Label from '../../Label'; import Error from '../../Error'; import { useWatchForm } from '../../Form/context'; @@ -23,7 +23,7 @@ const ConfirmPassword: React.FC = () => { showError, setValue, errorMessage, - } = useFieldType({ + } = useField({ path: 'confirm-password', disableFormData: true, validate, diff --git a/src/admin/components/forms/field-types/DateTime/index.tsx b/src/admin/components/forms/field-types/DateTime/index.tsx index cebb44ebdd..1e17e22bb2 100644 --- a/src/admin/components/forms/field-types/DateTime/index.tsx +++ b/src/admin/components/forms/field-types/DateTime/index.tsx @@ -2,7 +2,7 @@ import React, { useCallback } from 'react'; import DatePicker from '../../../elements/DatePicker'; import withCondition from '../../withCondition'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Label from '../../Label'; import Error from '../../Error'; import FieldDescription from '../../FieldDescription'; @@ -43,7 +43,7 @@ const DateTime: React.FC = (props) => { showError, errorMessage, setValue, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, condition, diff --git a/src/admin/components/forms/field-types/Email/index.tsx b/src/admin/components/forms/field-types/Email/index.tsx index e857da0980..91979c3b6e 100644 --- a/src/admin/components/forms/field-types/Email/index.tsx +++ b/src/admin/components/forms/field-types/Email/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; import withCondition from '../../withCondition'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Label from '../../Label'; import Error from '../../Error'; import FieldDescription from '../../FieldDescription'; @@ -34,7 +34,7 @@ const Email: React.FC = (props) => { return validationResult; }, [validate, required]); - const fieldType = useFieldType({ + const fieldType = useField({ path, validate: memoizedValidate, enableDebouncedValue: true, diff --git a/src/admin/components/forms/field-types/HiddenInput/index.tsx b/src/admin/components/forms/field-types/HiddenInput/index.tsx index 2ae0af77ef..318cd7a250 100644 --- a/src/admin/components/forms/field-types/HiddenInput/index.tsx +++ b/src/admin/components/forms/field-types/HiddenInput/index.tsx @@ -1,5 +1,5 @@ import React, { useEffect } from 'react'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import withCondition from '../../withCondition'; import { Props } from './types'; @@ -13,7 +13,7 @@ const HiddenInput: React.FC = (props) => { const path = pathFromProps || name; - const { value, setValue } = useFieldType({ + const { value, setValue } = useField({ path, }); diff --git a/src/admin/components/forms/field-types/Number/index.tsx b/src/admin/components/forms/field-types/Number/index.tsx index f0af81f922..cb8b4c01ca 100644 --- a/src/admin/components/forms/field-types/Number/index.tsx +++ b/src/admin/components/forms/field-types/Number/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Label from '../../Label'; import Error from '../../Error'; import FieldDescription from '../../FieldDescription'; @@ -41,7 +41,7 @@ const NumberField: React.FC = (props) => { showError, setValue, errorMessage, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, enableDebouncedValue: true, diff --git a/src/admin/components/forms/field-types/Password/index.tsx b/src/admin/components/forms/field-types/Password/index.tsx index e26a585d86..5bafb357d4 100644 --- a/src/admin/components/forms/field-types/Password/index.tsx +++ b/src/admin/components/forms/field-types/Password/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Label from '../../Label'; import Error from '../../Error'; import withCondition from '../../withCondition'; @@ -33,7 +33,7 @@ const Password: React.FC = (props) => { formProcessing, setValue, errorMessage, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, enableDebouncedValue: true, diff --git a/src/admin/components/forms/field-types/Point/index.tsx b/src/admin/components/forms/field-types/Point/index.tsx index 62f0cc1759..2bdfd89406 100644 --- a/src/admin/components/forms/field-types/Point/index.tsx +++ b/src/admin/components/forms/field-types/Point/index.tsx @@ -1,5 +1,5 @@ import React, { useCallback } from 'react'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Label from '../../Label'; import Error from '../../Error'; import FieldDescription from '../../FieldDescription'; @@ -41,7 +41,7 @@ const PointField: React.FC = (props) => { showError, setValue, errorMessage, - } = useFieldType<[number, number]>({ + } = useField<[number, number]>({ path, validate: memoizedValidate, enableDebouncedValue: true, diff --git a/src/admin/components/forms/field-types/RadioGroup/index.tsx b/src/admin/components/forms/field-types/RadioGroup/index.tsx index e6aa893cd1..4e84db9d5d 100644 --- a/src/admin/components/forms/field-types/RadioGroup/index.tsx +++ b/src/admin/components/forms/field-types/RadioGroup/index.tsx @@ -1,6 +1,6 @@ import React, { useCallback } from 'react'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import withCondition from '../../withCondition'; import Error from '../../Error'; import Label from '../../Label'; @@ -44,7 +44,7 @@ const RadioGroup: React.FC = (props) => { showError, errorMessage, setValue, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, condition, diff --git a/src/admin/components/forms/field-types/Relationship/index.tsx b/src/admin/components/forms/field-types/Relationship/index.tsx index cfa8d13847..17ea530efc 100644 --- a/src/admin/components/forms/field-types/Relationship/index.tsx +++ b/src/admin/components/forms/field-types/Relationship/index.tsx @@ -5,7 +5,7 @@ import { useConfig } from '@payloadcms/config-provider'; import withCondition from '../../withCondition'; import ReactSelect from '../../../elements/ReactSelect'; import { Value } from '../../../elements/ReactSelect/types'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import Label from '../../Label'; import Error from '../../Error'; import FieldDescription from '../../FieldDescription'; @@ -70,7 +70,7 @@ const Relationship: React.FC = (props) => { showError, errorMessage, setValue, - } = useFieldType({ + } = useField({ path: path || name, validate: memoizedValidate, condition, diff --git a/src/admin/components/forms/field-types/RichText/RichText.tsx b/src/admin/components/forms/field-types/RichText/RichText.tsx index de1a8b8989..0d01ad5001 100644 --- a/src/admin/components/forms/field-types/RichText/RichText.tsx +++ b/src/admin/components/forms/field-types/RichText/RichText.tsx @@ -4,7 +4,7 @@ import { createEditor, Transforms, Node, Element as SlateElement, Text, BaseEdit import { ReactEditor, Editable, withReact, Slate } from 'slate-react'; import { HistoryEditor, withHistory } from 'slate-history'; import { richText } from '../../../../../fields/validations'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import withCondition from '../../withCondition'; import Label from '../../Label'; import Error from '../../Error'; @@ -29,7 +29,7 @@ const defaultElements: RichTextElement[] = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', const defaultLeaves: RichTextLeaf[] = ['bold', 'italic', 'underline', 'strikethrough', 'code']; const baseClass = 'rich-text'; -type CustomText = { text: string; [x: string]: unknown } +type CustomText = { text: string;[x: string]: unknown } type CustomElement = { type: string; children: CustomText[] } @@ -115,7 +115,7 @@ const RichText: React.FC = (props) => { return validationResult; }, [validate, required]); - const fieldType = useFieldType({ + const fieldType = useField({ path, validate: memoizedValidate, stringify: true, @@ -194,7 +194,7 @@ const RichText: React.FC = (props) => { }} >
- { !hideGutter && () } + {!hideGutter && ()} = (props) => { description, condition, } = {}, + value: valueFromProps, + onChange: onChangeFromProps } = props; const path = pathFromProps || name; @@ -52,16 +54,42 @@ const Select: React.FC = (props) => { }, [validate, required, options]); const { - value, + value: valueFromContext, showError, setValue, errorMessage, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, condition, }); + const onChange = useCallback((selectedOption) => { + if (!readOnly) { + let newValue; + if (hasMany) { + if (Array.isArray(selectedOption)) { + newValue = selectedOption.map((option) => option.value); + } else { + newValue = []; + } + } else { + newValue = selectedOption.value; + } + + if (typeof onChangeFromProps === 'function') { + onChangeFromProps(newValue); + } else { + setValue(newValue); + } + } + }, [ + readOnly, + hasMany, + onChangeFromProps, + setValue + ]) + const classes = [ 'field-type', baseClass, @@ -71,6 +99,8 @@ const Select: React.FC = (props) => { let valueToRender; + const value = valueFromProps || valueFromContext || ''; + if (hasMany && Array.isArray(value)) { valueToRender = value.map((val) => options.find((option) => option.value === val)); } else { @@ -95,17 +125,7 @@ const Select: React.FC = (props) => { required={required} /> { - if (hasMany) { - if (Array.isArray(selectedOption)) { - setValue(selectedOption.map((option) => option.value)); - } else { - setValue([]); - } - } else { - setValue(selectedOption.value); - } - } : undefined} + onChange={onChange} value={valueToRender} showError={showError} isDisabled={readOnly} diff --git a/src/admin/components/forms/field-types/Text/index.tsx b/src/admin/components/forms/field-types/Text/index.tsx index 1854ca7d9b..aa6f044203 100644 --- a/src/admin/components/forms/field-types/Text/index.tsx +++ b/src/admin/components/forms/field-types/Text/index.tsx @@ -1,5 +1,5 @@ -import React from 'react'; -import useFieldType from '../../useFieldType'; +import React, { useCallback, useEffect } from 'react'; +import useField from '../../useField'; import withCondition from '../../withCondition'; import Label from '../../Label'; import Error from '../../Error'; @@ -24,11 +24,13 @@ const Text: React.FC = (props) => { description, condition, } = {}, + value: valueFromProps, + onChange: onChangeFromProps, } = props; const path = pathFromProps || name; - const fieldType = useFieldType({ + const fieldType = useField({ path, validate, enableDebouncedValue: true, @@ -36,12 +38,24 @@ const Text: React.FC = (props) => { }); const { - value, + value: valueFromContext, showError, setValue, errorMessage, } = fieldType; + const onChange = useCallback((e) => { + const { value: incomingValue } = e.target; + if (typeof onChangeFromProps === 'function') { + onChangeFromProps(incomingValue); + } else { + setValue(e); + } + }, [ + onChangeFromProps, + setValue, + ]); + const classes = [ 'field-type', 'text', @@ -49,6 +63,8 @@ const Text: React.FC = (props) => { readOnly && 'read-only', ].filter(Boolean).join(' '); + const value = valueFromProps || valueFromContext || ''; + return (
= (props) => { required={required} /> = (props) => { showError, setValue, errorMessage, - } = useFieldType({ + } = useField({ path, validate: memoizedValidate, enableDebouncedValue: true, diff --git a/src/admin/components/forms/field-types/Upload/index.tsx b/src/admin/components/forms/field-types/Upload/index.tsx index d687ef5551..bb76e35c13 100644 --- a/src/admin/components/forms/field-types/Upload/index.tsx +++ b/src/admin/components/forms/field-types/Upload/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useEffect, useCallback } from 'react'; import { useModal } from '@faceless-ui/modal'; import { useConfig } from '@payloadcms/config-provider'; -import useFieldType from '../../useFieldType'; +import useField from '../../useField'; import withCondition from '../../withCondition'; import Button from '../../../elements/Button'; import Label from '../../Label'; @@ -38,6 +38,8 @@ const Upload: React.FC = (props) => { validate = upload, relationTo, fieldTypes, + value: valueFromProps, + onChange: onChangeFromProps, } = props; const collection = collections.find((coll) => coll.slug === relationTo); @@ -51,19 +53,21 @@ const Upload: React.FC = (props) => { return validationResult; }, [validate, required]); - const fieldType = useFieldType({ + const fieldType = useField({ path, validate: memoizedValidate, condition, }); const { - value, + value: valueFromContext, showError, setValue, errorMessage, } = fieldType; + const value = valueFromProps || valueFromContext || ''; + const classes = [ 'field-type', baseClass, @@ -81,14 +85,28 @@ const Upload: React.FC = (props) => { setInternalValue(json); } else { setInternalValue(undefined); - setValue(null); setMissingFile(true); } }; fetchFile(); } - }, [value, setInternalValue, relationTo, api, serverURL, setValue]); + }, [ + value, + relationTo, + api, + serverURL, + setValue + ]); + + useEffect(() => { + const { id: incomingID } = internalValue || {}; + if (typeof onChangeFromProps === 'function') { + onChangeFromProps(incomingID) + } else { + setValue(incomingID); + } + }, [internalValue]); return (
= (props) => { fieldTypes, setValue: (val) => { setMissingFile(false); - setValue(val.id); setInternalValue(val); }, }} @@ -157,7 +174,6 @@ const Upload: React.FC = (props) => { slug: selectExistingModalSlug, setValue: (val) => { setMissingFile(false); - setValue(val.id); setInternalValue(val); }, addModalSlug, diff --git a/src/admin/components/forms/useFieldType/index.tsx b/src/admin/components/forms/useField/index.tsx similarity index 80% rename from src/admin/components/forms/useFieldType/index.tsx rename to src/admin/components/forms/useField/index.tsx index 9de075b9f3..5cee2de4a2 100644 --- a/src/admin/components/forms/useFieldType/index.tsx +++ b/src/admin/components/forms/useField/index.tsx @@ -5,7 +5,7 @@ import { useFormProcessing, useFormSubmitted, useFormModified, useForm } from '. import useDebounce from '../../../hooks/useDebounce'; import { Options, FieldType } from './types'; -const useFieldType = (options: Options): FieldType => { +const useField = (options: Options): FieldType => { const { path, validate, @@ -22,8 +22,10 @@ const useFieldType = (options: Options): FieldType => { const modified = useFormModified(); const { - dispatchFields, getField, setModified, - } = formContext; + dispatchFields, + getField, + setModified, + } = formContext || {}; const [internalValue, setInternalValue] = useState(undefined); @@ -64,21 +66,38 @@ const useFieldType = (options: Options): FieldType => { fieldToDispatch.valid = validationResult; } - dispatchFields(fieldToDispatch); - }, [path, dispatchFields, validate, disableFormData, ignoreWhileFlattening, initialValue, stringify, condition]); + if (typeof dispatchFields === 'function') { + dispatchFields(fieldToDispatch); + } + }, [ + path, + dispatchFields, + validate, + disableFormData, + ignoreWhileFlattening, + initialValue, + stringify, + condition + ]); - // Method to return from `useFieldType`, used to + // Method to return from `useField`, used to // update internal field values from field component(s) // as fast as they arrive. NOTE - this method is NOT debounced const setValue = useCallback((e, modifyForm = true) => { const val = (e && e.target) ? e.target.value : e; if ((!ignoreWhileFlattening && !modified) && modifyForm) { - setModified(true); + if (typeof setModified === 'function') { + setModified(true); + } } setInternalValue(val); - }, [setModified, modified, ignoreWhileFlattening]); + }, [ + setModified, + modified, + ignoreWhileFlattening + ]); useEffect(() => { setInternalValue(initialValue); @@ -94,7 +113,11 @@ const useFieldType = (options: Options): FieldType => { if (field?.value !== valueToSend && valueToSend !== undefined) { sendField(valueToSend); } - }, [valueToSend, sendField, field]); + }, [ + valueToSend, + sendField, + field + ]); return { ...options, @@ -107,4 +130,4 @@ const useFieldType = (options: Options): FieldType => { }; }; -export default useFieldType; +export default useField; diff --git a/src/admin/components/forms/useFieldType/types.ts b/src/admin/components/forms/useField/types.ts similarity index 100% rename from src/admin/components/forms/useFieldType/types.ts rename to src/admin/components/forms/useField/types.ts diff --git a/src/admin/components/views/collections/Edit/Auth/APIKey.tsx b/src/admin/components/views/collections/Edit/Auth/APIKey.tsx index bcbfad2bca..9f2bb6cbf2 100644 --- a/src/admin/components/views/collections/Edit/Auth/APIKey.tsx +++ b/src/admin/components/views/collections/Edit/Auth/APIKey.tsx @@ -1,6 +1,6 @@ import React, { useMemo, useState, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; -import useFieldType from '../../../../forms/useFieldType'; +import useField from '../../../../forms/useField'; import Label from '../../../../forms/Label'; import CopyToClipboard from '../../../../elements/CopyToClipboard'; import { text } from '../../../../../../fields/validations'; @@ -31,7 +31,7 @@ const APIKey: React.FC = () => {
), [apiKeyValue]); - const fieldType = useFieldType({ + const fieldType = useField({ path: 'apiKey', validate, }); diff --git a/src/admin/components/views/collections/Edit/Upload/index.tsx b/src/admin/components/views/collections/Edit/Upload/index.tsx index ba28fc50c2..ca8f5ddf44 100644 --- a/src/admin/components/views/collections/Edit/Upload/index.tsx +++ b/src/admin/components/views/collections/Edit/Upload/index.tsx @@ -1,7 +1,7 @@ import React, { useState, useRef, useEffect, useCallback, } from 'react'; -import useFieldType from '../../../../forms/useFieldType'; +import useField from '../../../../forms/useField'; import Button from '../../../../elements/Button'; import FileDetails from '../../../../elements/FileDetails'; import Error from '../../../../forms/Error'; @@ -45,7 +45,7 @@ const Upload: React.FC = (props) => { setValue, showError, errorMessage, - } = useFieldType<{name: string}>({ + } = useField<{ name: string }>({ path: 'file', validate, }); diff --git a/src/fields/config/types.ts b/src/fields/config/types.ts index 47e9adb864..10e9713f81 100644 --- a/src/fields/config/types.ts +++ b/src/fields/config/types.ts @@ -99,6 +99,8 @@ export type TextField = FieldBase & { placeholder?: string autoComplete?: string } + value?: string + onChange?: (value: string) => void } export type EmailField = FieldBase & { @@ -167,9 +169,11 @@ export type UIField = { } export type UploadField = FieldBase & { - type: 'upload'; - relationTo: string; - maxDepth?: number; + type: 'upload' + relationTo: string + maxDepth?: number + value?: string + onChange?: (value: string) => void } type CodeAdmin = Admin & { @@ -184,9 +188,11 @@ export type CodeField = Omit & { } export type SelectField = FieldBase & { - type: 'select'; - options: Option[]; - hasMany?: boolean; + type: 'select' + options: Option[] + hasMany?: boolean + value?: string + onChange?: (value: string) => void } export type RelationshipField = FieldBase & {