diff --git a/src/client/components/forms/Form/index.js b/src/client/components/forms/Form/index.js index dd5006bbb9..6bd3ffbf85 100644 --- a/src/client/components/forms/Form/index.js +++ b/src/client/components/forms/Form/index.js @@ -86,23 +86,23 @@ const Form = (props) => { return reduceFieldsToValues(siblingFields); }, [fields]); - const countRows = useCallback((rowName) => { - const namePrefixToRemove = rowName.substring(0, rowName.lastIndexOf('.') + 1); + const getDataByPath = useCallback((path) => { + const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1); const rows = Object.keys(fields).reduce((matchedRows, key) => { - if (key.indexOf(`${rowName}.`) === 0) { + if (key.indexOf(`${path}.`) === 0) { return { ...matchedRows, - [key.replace(namePrefixToRemove, '')]: fields[key], + [key.replace(pathPrefixToRemove, '')]: fields[key], }; } return matchedRows; }, {}); - const unflattenedRows = unflatten(rows); - const rowCount = unflattenedRows[rowName.replace(namePrefixToRemove, '')]?.length || 0; - return rowCount; + const rowValues = reduceFieldsToValues(rows); + const unflattenedRows = unflatten(rowValues); + return unflattenedRows; }, [fields]); const validateForm = useCallback(() => { @@ -283,12 +283,12 @@ const Form = (props) => { getField, processing, submitted, - countRows, + getDataByPath, getData, + getSiblingData, validateForm, modified, setModified, - getSiblingData, }} > { const dataToInitialize = initialData || defaultValue; const [rows, dispatchRows] = useReducer(reducer, []); const { customComponentsPath } = useRenderedFields(); + const { getDataByPath } = useForm(); const path = pathFromProps || name; @@ -58,25 +60,32 @@ const Repeater = (props) => { }); const addRow = (rowIndex) => { + const data = getDataByPath(path)?.[name]; + dispatchRows({ - type: 'ADD', index: rowIndex, + type: 'ADD', index: rowIndex, data, }); setValue(value + 1); }; const removeRow = (rowIndex) => { + const data = getDataByPath(path)?.[name]; + dispatchRows({ type: 'REMOVE', index: rowIndex, + data, }); setValue(value - 1); }; const moveRow = (moveFromIndex, moveToIndex) => { + const data = getDataByPath(path)?.[name]; + dispatchRows({ - type: 'MOVE', index: moveFromIndex, moveToIndex, + type: 'MOVE', index: moveFromIndex, moveToIndex, data, }); }; @@ -90,7 +99,7 @@ const Repeater = (props) => { useEffect(() => { dispatchRows({ type: 'SET_ALL', - payload: dataToInitialize.reduce((acc, data) => ([ + rows: dataToInitialize.reduce((acc, data) => ([ ...acc, { key: uuidv4(), diff --git a/src/client/components/forms/field-types/Repeater/reducer.js b/src/client/components/forms/field-types/Repeater/reducer.js index eb8271281c..ea2acbc2d3 100644 --- a/src/client/components/forms/field-types/Repeater/reducer.js +++ b/src/client/components/forms/field-types/Repeater/reducer.js @@ -2,16 +2,14 @@ import { v4 as uuidv4 } from 'uuid'; const reducer = (currentState, action) => { const { - type, index, moveToIndex, payload, data, + type, index, moveToIndex, rows, data, } = action; const stateCopy = [...currentState]; - const movingRowState = stateCopy[index]; - switch (type) { case 'SET_ALL': - return payload; + return rows; case 'ADD': stateCopy.splice(index + 1, 0, { @@ -20,20 +18,40 @@ const reducer = (currentState, action) => { data, }); - return stateCopy; + data.splice(index + 1, 0, {}); + + return stateCopy.map((row, i) => { + return { + ...row, + data: { + ...(data[i] || {}), + }, + }; + }); case 'REMOVE': stateCopy.splice(index, 1); return stateCopy; case 'UPDATE_COLLAPSIBLE_STATUS': - stateCopy[index].open = !movingRowState.open; + stateCopy[index].open = !stateCopy[index].open; return stateCopy; - case 'MOVE': - stateCopy.splice(index, 1); - stateCopy.splice(moveToIndex, 0, movingRowState); - return stateCopy; + case 'MOVE': { + const stateCopyWithNewData = stateCopy.map((row, i) => { + return { + ...row, + data: { + ...(data[i] || {}), + }, + }; + }); + + const movingRowState = { ...stateCopyWithNewData[index] }; + stateCopyWithNewData.splice(index, 1); + stateCopyWithNewData.splice(moveToIndex, 0, movingRowState); + return stateCopyWithNewData; + } default: return currentState; diff --git a/src/client/components/forms/useFieldType/index.js b/src/client/components/forms/useFieldType/index.js index 324f568467..3a2e041a51 100644 --- a/src/client/components/forms/useFieldType/index.js +++ b/src/client/components/forms/useFieldType/index.js @@ -2,7 +2,6 @@ import { useContext, useCallback, useEffect, useState, } from 'react'; import FormContext from '../Form/Context'; -import { useLocale } from '../../utilities/Locale'; import useDebounce from '../../../hooks/useDebounce'; import './index.scss'; @@ -10,7 +9,6 @@ import './index.scss'; const useFieldType = (options) => { const { path, - required, initialData: data, defaultValue, validate, @@ -24,16 +22,11 @@ const useFieldType = (options) => { // If no initialData, use default value const initialData = data !== undefined ? data : defaultValue; - const locale = useLocale(); const formContext = useContext(FormContext); const { dispatchFields, submitted, processing, getField, setModified, modified, } = formContext; - // Maintain an internal initial value AND value to ensure that the form - // can successfully differentiate between updates sent from fields - // that are meant to be initial values vs. values that are deliberately changed - const [internalInitialValue, setInternalInitialValue] = useState(initialData); const [internalValue, setInternalValue] = useState(initialData); // Debounce internal values to update form state only every 60ms @@ -45,7 +38,6 @@ const useFieldType = (options) => { // Valid could be a string equal to an error message const valid = (field && typeof field.valid === 'boolean') ? field.valid : true; - const valueFromForm = field?.value; const showError = valid === false && submitted; // Method to send update field values from field component(s) @@ -79,38 +71,12 @@ const useFieldType = (options) => { setInternalValue(value); }, [setModified, modified]); - const setInitialValue = useCallback((value) => { - setInternalInitialValue(value); - }, []); - // Remove field from state on "unmount" // This is mostly used for repeater / flex content row modifications useEffect(() => { return () => dispatchFields({ path, type: 'REMOVE' }); }, [dispatchFields, path]); - // Whenever the value from form updates, - // update internal value as well - useEffect(() => { - if (valueFromForm !== undefined) { - setInternalInitialValue(valueFromForm); - } - }, [valueFromForm, setInternalInitialValue]); - - // When locale changes, and / or initialData changes, - // reset internal initial value - useEffect(() => { - if (initialData !== undefined) { - setInternalInitialValue(initialData); - } - }, [initialData, setInternalInitialValue, locale]); - - // When internal initial value changes, set internal value - // This is necessary to bypass changing the form to a modified:true state - useEffect(() => { - setInternalValue(internalInitialValue); - }, [setInternalValue, internalInitialValue]); - // The only time that the FORM value should be updated // is when the debounced value updates. So, when the debounced value updates, // send it up to the form @@ -132,7 +98,6 @@ const useFieldType = (options) => { formSubmitted: submitted, formProcessing: processing, setValue, - setInitialValue, }; };