From 0961a3d499cb85896951a8b1ef58aba9db85a77b Mon Sep 17 00:00:00 2001 From: James Date: Tue, 21 Apr 2020 18:14:09 -0400 Subject: [PATCH] fixes bug with nested repeaters losing their data --- .vscode/launch.json | 7 ++ .../forms/Form/RowModified/index.js | 27 ++++ src/client/components/forms/Form/index.js | 18 +++ src/client/components/forms/Form/reducer.js | 4 +- .../components/forms/RenderFields/index.js | 2 +- .../forms/field-types/Flexible/index.js | 33 +++-- .../forms/field-types/Repeater/index.js | 116 ++++++++++-------- .../components/forms/useFieldType/index.js | 6 - 8 files changed, 141 insertions(+), 72 deletions(-) create mode 100644 src/client/components/forms/Form/RowModified/index.js diff --git a/.vscode/launch.json b/.vscode/launch.json index 9278c061fc..e78a4f01d9 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -22,6 +22,13 @@ "request": "launch", "name": "Launch Program", "program": "${workspaceFolder}/demo/server.js" + }, + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against Localhost", + "url": "http://localhost:3000/admin", + "webRoot": "${workspaceFolder}" } ] } diff --git a/src/client/components/forms/Form/RowModified/index.js b/src/client/components/forms/Form/RowModified/index.js new file mode 100644 index 0000000000..65714ad547 --- /dev/null +++ b/src/client/components/forms/Form/RowModified/index.js @@ -0,0 +1,27 @@ +import React, { + createContext, useContext, +} from 'react'; +import PropTypes from 'prop-types'; + +const Context = createContext({}); + +export const RowModifiedProvider = ({ children, lastModified }) => { + return ( + + {children} + + ); +}; + +export const useRowModified = () => useContext(Context); + +RowModifiedProvider.defaultProps = { + lastModified: null, +}; + +RowModifiedProvider.propTypes = { + children: PropTypes.node.isRequired, + lastModified: PropTypes.number, +}; + +export default Context; diff --git a/src/client/components/forms/Form/index.js b/src/client/components/forms/Form/index.js index 1c9d8a432a..7ecf5628aa 100644 --- a/src/client/components/forms/Form/index.js +++ b/src/client/components/forms/Form/index.js @@ -144,6 +144,24 @@ const Form = (props) => { fields, processing, submitted, + countRows: (rowName) => { + const namePrefixToRemove = rowName.substring(0, rowName.lastIndexOf('.') + 1); + + const rows = Object.keys(fields).reduce((matchedRows, key) => { + if (key.indexOf(`${rowName}.`) === 0) { + return { + ...matchedRows, + [key.replace(namePrefixToRemove, '')]: fields[key], + }; + } + + return matchedRows; + }, {}); + + const unflattenedRows = unflatten(rows); + const rowCount = unflattenedRows[rowName.replace(namePrefixToRemove, '')]?.length || 0; + return rowCount; + }, }} > field.validate(value, field) : undefined} defaultValue={initialData[field.name] || defaultValue} diff --git a/src/client/components/forms/field-types/Flexible/index.js b/src/client/components/forms/field-types/Flexible/index.js index 87b2bf055a..7e8d25df3f 100644 --- a/src/client/components/forms/field-types/Flexible/index.js +++ b/src/client/components/forms/field-types/Flexible/index.js @@ -1,17 +1,18 @@ import React, { - useContext, useEffect, useReducer, useState, Fragment, + useContext, useEffect, useReducer, useState, } from 'react'; import PropTypes from 'prop-types'; import { DragDropContext, Droppable } from 'react-beautiful-dnd'; import { useModal } from '@trbl/react-modal'; +import { RowModifiedProvider, useRowModified } from '../../Form/RowModified'; import withCondition from '../../withCondition'; import Button from '../../../controls/Button'; import FormContext from '../../Form/Context'; import Section from '../../../layout/Section'; import AddRowModal from './AddRowModal'; import collapsibleReducer from './reducer'; -import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle +import DraggableSection from '../../DraggableSection'; import './index.scss'; @@ -27,15 +28,16 @@ const Flexible = (props) => { fieldTypes, } = props; + const parentRowsModified = useRowModified(); const { toggle: toggleModal, closeAll: closeAllModals } = useModal(); const [rowIndexBeingAdded, setRowIndexBeingAdded] = useState(null); - const [hasModifiedRows, setHasModifiedRows] = useState(false); + const [lastModified, setLastModified] = useState(null); const [rowCount, setRowCount] = useState(0); const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []); const formContext = useContext(FormContext); const modalSlug = `flexible-${name}`; - const { fields: fieldState, dispatchFields } = formContext; + const { fields: fieldState, dispatchFields, countRows } = formContext; const addRow = (rowIndex, blockType) => { const blockToAdd = blocks.find(block => block.slug === blockType); @@ -49,7 +51,7 @@ const Flexible = (props) => { }); setRowCount(rowCount + 1); - setHasModifiedRows(true); + setLastModified(Date.now()); }; const removeRow = (rowIndex) => { @@ -63,7 +65,7 @@ const Flexible = (props) => { }); setRowCount(rowCount - 1); - setHasModifiedRows(true); + setLastModified(Date.now()); }; const moveRow = (moveFromIndex, moveToIndex) => { @@ -75,7 +77,7 @@ const Flexible = (props) => { type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex, }); - setHasModifiedRows(true); + setLastModified(Date.now()); }; const openAddRowModal = (rowIndex) => { @@ -90,9 +92,16 @@ const Flexible = (props) => { moveRow(sourceIndex, destinationIndex); }; + const updateRowCountOnParentRowModified = () => { + const countedRows = countRows(name); + setRowCount(countedRows); + }; + + useEffect(updateRowCountOnParentRowModified, [parentRowsModified]); + useEffect(() => { setRowCount(defaultValue.length); - setHasModifiedRows(false); + setLastModified(null); dispatchCollapsibleStates({ type: 'SET_ALL_COLLAPSIBLES', @@ -101,7 +110,7 @@ const Flexible = (props) => { }, [defaultValue]); return ( - +
{ {rowCount !== 0 && Array.from(Array(rowCount).keys()).map((_, rowIndex) => { let blockType = fieldState[`${name}.${rowIndex}.blockType`]?.value; - if (!hasModifiedRows && !blockType) { + if (!lastModified && !blockType) { blockType = defaultValue?.[rowIndex]?.blockType; } @@ -144,7 +153,7 @@ const Flexible = (props) => { }, ]} singularLabel={blockType} - defaultValue={hasModifiedRows ? undefined : defaultValue[rowIndex]} + defaultValue={lastModified ? undefined : defaultValue[rowIndex]} dispatchCollapsibleStates={dispatchCollapsibleStates} collapsibleStates={collapsibleStates} blockType="flexible" @@ -178,7 +187,7 @@ const Flexible = (props) => { slug={modalSlug} blocks={blocks} /> - + ); }; diff --git a/src/client/components/forms/field-types/Repeater/index.js b/src/client/components/forms/field-types/Repeater/index.js index c427b42234..bde957ac88 100644 --- a/src/client/components/forms/field-types/Repeater/index.js +++ b/src/client/components/forms/field-types/Repeater/index.js @@ -4,11 +4,12 @@ import React, { import PropTypes from 'prop-types'; import { DragDropContext, Droppable } from 'react-beautiful-dnd'; +import { RowModifiedProvider, useRowModified } from '../../Form/RowModified'; import withCondition from '../../withCondition'; import Button from '../../../controls/Button'; import FormContext from '../../Form/Context'; import Section from '../../../layout/Section'; -import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle +import DraggableSection from '../../DraggableSection'; import collapsibleReducer from './reducer'; import './index.scss'; @@ -16,11 +17,12 @@ import './index.scss'; const baseClass = 'field-type repeater'; const Repeater = (props) => { + const parentRowsModified = useRowModified(); const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []); const formContext = useContext(FormContext); const [rowCount, setRowCount] = useState(0); - const [hasModifiedRows, setHasModifiedRows] = useState(false); - const { fields: fieldState, dispatchFields } = formContext; + const [lastModified, setLastModified] = useState(null); + const { fields: fieldState, dispatchFields, countRows } = formContext; const { label, @@ -41,7 +43,7 @@ const Repeater = (props) => { }); setRowCount(rowCount + 1); - setHasModifiedRows(true); + setLastModified(Date.now()); }; const removeRow = (rowIndex) => { @@ -55,7 +57,7 @@ const Repeater = (props) => { }); setRowCount(rowCount - 1); - setHasModifiedRows(true); + setLastModified(Date.now()); }; const moveRow = (moveFromIndex, moveToIndex) => { @@ -67,11 +69,12 @@ const Repeater = (props) => { type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex, }); - setHasModifiedRows(true); + setLastModified(Date.now()); }; useEffect(() => { setRowCount(defaultValue.length); + setLastModified(null); dispatchCollapsibleStates({ type: 'SET_ALL_COLLAPSIBLES', @@ -79,6 +82,13 @@ const Repeater = (props) => { }); }, [defaultValue]); + const updateRowCountOnParentRowModified = () => { + const countedRows = countRows(name); + setRowCount(countedRows); + }; + + useEffect(updateRowCountOnParentRowModified, [parentRowsModified]); + const onDragEnd = (result) => { if (!result.destination) return; const sourceIndex = result.source.index; @@ -87,54 +97,56 @@ const Repeater = (props) => { }; return ( - -
-
- <> - - {provided => ( -
+ +
+
+ <> + + {provided => ( +
+ {rowCount !== 0 + && Array.from(Array(rowCount).keys()).map((_, rowIndex) => { + return ( + addRow(rowIndex)} + removeRow={() => removeRow(rowIndex)} + rowIndex={rowIndex} + fieldState={fieldState} + fieldSchema={fields} + defaultValue={lastModified ? undefined : defaultValue[rowIndex]} + dispatchCollapsibleStates={dispatchCollapsibleStates} + collapsibleStates={collapsibleStates} + /> + ); + }) + } + {provided.placeholder} +
+ )} +
+ +
+
- )} - + {`Add ${singularLabel}`} + +
+ +
-
- -
- -
- -
-
+ + +
); }; diff --git a/src/client/components/forms/useFieldType/index.js b/src/client/components/forms/useFieldType/index.js index 28cd2380f9..6e967ce877 100644 --- a/src/client/components/forms/useFieldType/index.js +++ b/src/client/components/forms/useFieldType/index.js @@ -1,6 +1,5 @@ import { useContext, useCallback, useEffect } from 'react'; import FormContext from '../Form/Context'; -import { useLocale } from '../../utilities/Locale'; import './index.scss'; @@ -13,7 +12,6 @@ const useFieldType = (options) => { validate, } = options; - const locale = useLocale(); const formContext = useContext(FormContext); const { dispatchFields, submitted, processing } = formContext; const mountValue = formContext.fields[name]?.value || null; @@ -44,10 +42,6 @@ const useFieldType = (options) => { if (defaultValue != null) sendField(defaultValue); }, [defaultValue, sendField]); - useEffect(() => { - sendField(null); - }, [locale, sendField]); - const valid = formContext.fields[name] ? formContext.fields[name].valid : true; const showError = valid === false && formContext.submitted;