fixes bug with nested repeaters losing their data

This commit is contained in:
James
2020-04-21 18:14:09 -04:00
parent cdc764cefe
commit 0961a3d499
8 changed files with 141 additions and 72 deletions

7
.vscode/launch.json vendored
View File

@@ -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}"
}
]
}

View 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;

View File

@@ -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

View File

@@ -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': {

View File

@@ -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}

View File

@@ -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>
);
};

View File

@@ -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>
);
};

View File

@@ -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;