fixes bug with nested repeaters losing their data
This commit is contained in:
7
.vscode/launch.json
vendored
7
.vscode/launch.json
vendored
@@ -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}"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
27
src/client/components/forms/Form/RowModified/index.js
Normal file
27
src/client/components/forms/Form/RowModified/index.js
Normal file
@@ -0,0 +1,27 @@
|
||||
import React, {
|
||||
createContext, useContext,
|
||||
} from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
const Context = createContext({});
|
||||
|
||||
export const RowModifiedProvider = ({ children, lastModified }) => {
|
||||
return (
|
||||
<Context.Provider value={lastModified}>
|
||||
{children}
|
||||
</Context.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
export const useRowModified = () => useContext(Context);
|
||||
|
||||
RowModifiedProvider.defaultProps = {
|
||||
lastModified: null,
|
||||
};
|
||||
|
||||
RowModifiedProvider.propTypes = {
|
||||
children: PropTypes.node.isRequired,
|
||||
lastModified: PropTypes.number,
|
||||
};
|
||||
|
||||
export default Context;
|
||||
@@ -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;
|
||||
},
|
||||
}}
|
||||
>
|
||||
<HiddenInput
|
||||
|
||||
@@ -80,10 +80,12 @@ function fieldReducer(state, action) {
|
||||
// Add new object containing subfield names to unflattenedRows array
|
||||
unflattenedRows.splice(rowIndex + 1, 0, subFields);
|
||||
|
||||
return {
|
||||
const newState = {
|
||||
...remainingFlattenedState,
|
||||
...(flatten({ [name]: unflattenedRows }, { filters: flattenFilters })),
|
||||
};
|
||||
|
||||
return newState;
|
||||
}
|
||||
|
||||
case 'MOVE_ROW': {
|
||||
|
||||
@@ -17,7 +17,7 @@ const RenderFields = ({
|
||||
return (
|
||||
<FieldComponent
|
||||
fieldTypes={fieldTypes}
|
||||
key={i}
|
||||
key={field.name}
|
||||
{...field}
|
||||
validate={field.validate ? value => field.validate(value, field) : undefined}
|
||||
defaultValue={initialData[field.name] || defaultValue}
|
||||
|
||||
@@ -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 (
|
||||
<Fragment>
|
||||
<RowModifiedProvider lastModified={lastModified}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div className={baseClass}>
|
||||
<Section
|
||||
@@ -117,7 +126,7 @@ const Flexible = (props) => {
|
||||
{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}
|
||||
/>
|
||||
</Fragment>
|
||||
</RowModifiedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div className={baseClass}>
|
||||
<Section heading={label}>
|
||||
<>
|
||||
<Droppable droppableId="repeater-drop">
|
||||
{provided => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
<RowModifiedProvider lastModified={lastModified}>
|
||||
<DragDropContext onDragEnd={onDragEnd}>
|
||||
<div className={baseClass}>
|
||||
<Section heading={label}>
|
||||
<>
|
||||
<Droppable droppableId="repeater-drop">
|
||||
{provided => (
|
||||
<div
|
||||
ref={provided.innerRef}
|
||||
{...provided.droppableProps}
|
||||
>
|
||||
{rowCount !== 0
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
return (
|
||||
<DraggableSection
|
||||
fieldTypes={fieldTypes}
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
singularLabel={singularLabel}
|
||||
addRow={() => addRow(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fieldState={fieldState}
|
||||
fieldSchema={fields}
|
||||
defaultValue={lastModified ? undefined : defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
|
||||
<div className={`${baseClass}__add-button-wrap`}>
|
||||
<Button
|
||||
onClick={() => addRow(rowCount)}
|
||||
type="secondary"
|
||||
>
|
||||
{rowCount !== 0
|
||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||
return (
|
||||
<DraggableSection
|
||||
fieldTypes={fieldTypes}
|
||||
key={rowIndex}
|
||||
parentName={name}
|
||||
singularLabel={singularLabel}
|
||||
addRow={() => addRow(rowIndex)}
|
||||
removeRow={() => removeRow(rowIndex)}
|
||||
rowIndex={rowIndex}
|
||||
fieldState={fieldState}
|
||||
fieldSchema={fields}
|
||||
defaultValue={hasModifiedRows ? undefined : defaultValue[rowIndex]}
|
||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||
collapsibleStates={collapsibleStates}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
)}
|
||||
</Droppable>
|
||||
{`Add ${singularLabel}`}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Section>
|
||||
|
||||
<div className={`${baseClass}__add-button-wrap`}>
|
||||
<Button
|
||||
onClick={() => addRow(rowCount)}
|
||||
type="secondary"
|
||||
>
|
||||
{`Add ${singularLabel}`}
|
||||
</Button>
|
||||
</div>
|
||||
</>
|
||||
</Section>
|
||||
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</div>
|
||||
</DragDropContext>
|
||||
</RowModifiedProvider>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user