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",
|
"request": "launch",
|
||||||
"name": "Launch Program",
|
"name": "Launch Program",
|
||||||
"program": "${workspaceFolder}/demo/server.js"
|
"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,
|
fields,
|
||||||
processing,
|
processing,
|
||||||
submitted,
|
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
|
<HiddenInput
|
||||||
|
|||||||
@@ -80,10 +80,12 @@ function fieldReducer(state, action) {
|
|||||||
// Add new object containing subfield names to unflattenedRows array
|
// Add new object containing subfield names to unflattenedRows array
|
||||||
unflattenedRows.splice(rowIndex + 1, 0, subFields);
|
unflattenedRows.splice(rowIndex + 1, 0, subFields);
|
||||||
|
|
||||||
return {
|
const newState = {
|
||||||
...remainingFlattenedState,
|
...remainingFlattenedState,
|
||||||
...(flatten({ [name]: unflattenedRows }, { filters: flattenFilters })),
|
...(flatten({ [name]: unflattenedRows }, { filters: flattenFilters })),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return newState;
|
||||||
}
|
}
|
||||||
|
|
||||||
case 'MOVE_ROW': {
|
case 'MOVE_ROW': {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ const RenderFields = ({
|
|||||||
return (
|
return (
|
||||||
<FieldComponent
|
<FieldComponent
|
||||||
fieldTypes={fieldTypes}
|
fieldTypes={fieldTypes}
|
||||||
key={i}
|
key={field.name}
|
||||||
{...field}
|
{...field}
|
||||||
validate={field.validate ? value => field.validate(value, field) : undefined}
|
validate={field.validate ? value => field.validate(value, field) : undefined}
|
||||||
defaultValue={initialData[field.name] || defaultValue}
|
defaultValue={initialData[field.name] || defaultValue}
|
||||||
|
|||||||
@@ -1,17 +1,18 @@
|
|||||||
import React, {
|
import React, {
|
||||||
useContext, useEffect, useReducer, useState, Fragment,
|
useContext, useEffect, useReducer, useState,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
import { useModal } from '@trbl/react-modal';
|
import { useModal } from '@trbl/react-modal';
|
||||||
|
|
||||||
|
import { RowModifiedProvider, useRowModified } from '../../Form/RowModified';
|
||||||
import withCondition from '../../withCondition';
|
import withCondition from '../../withCondition';
|
||||||
import Button from '../../../controls/Button';
|
import Button from '../../../controls/Button';
|
||||||
import FormContext from '../../Form/Context';
|
import FormContext from '../../Form/Context';
|
||||||
import Section from '../../../layout/Section';
|
import Section from '../../../layout/Section';
|
||||||
import AddRowModal from './AddRowModal';
|
import AddRowModal from './AddRowModal';
|
||||||
import collapsibleReducer from './reducer';
|
import collapsibleReducer from './reducer';
|
||||||
import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle
|
import DraggableSection from '../../DraggableSection';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -27,15 +28,16 @@ const Flexible = (props) => {
|
|||||||
fieldTypes,
|
fieldTypes,
|
||||||
} = props;
|
} = props;
|
||||||
|
|
||||||
|
const parentRowsModified = useRowModified();
|
||||||
const { toggle: toggleModal, closeAll: closeAllModals } = useModal();
|
const { toggle: toggleModal, closeAll: closeAllModals } = useModal();
|
||||||
const [rowIndexBeingAdded, setRowIndexBeingAdded] = useState(null);
|
const [rowIndexBeingAdded, setRowIndexBeingAdded] = useState(null);
|
||||||
const [hasModifiedRows, setHasModifiedRows] = useState(false);
|
const [lastModified, setLastModified] = useState(null);
|
||||||
const [rowCount, setRowCount] = useState(0);
|
const [rowCount, setRowCount] = useState(0);
|
||||||
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
|
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
|
||||||
const formContext = useContext(FormContext);
|
const formContext = useContext(FormContext);
|
||||||
const modalSlug = `flexible-${name}`;
|
const modalSlug = `flexible-${name}`;
|
||||||
|
|
||||||
const { fields: fieldState, dispatchFields } = formContext;
|
const { fields: fieldState, dispatchFields, countRows } = formContext;
|
||||||
|
|
||||||
const addRow = (rowIndex, blockType) => {
|
const addRow = (rowIndex, blockType) => {
|
||||||
const blockToAdd = blocks.find(block => block.slug === blockType);
|
const blockToAdd = blocks.find(block => block.slug === blockType);
|
||||||
@@ -49,7 +51,7 @@ const Flexible = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setRowCount(rowCount + 1);
|
setRowCount(rowCount + 1);
|
||||||
setHasModifiedRows(true);
|
setLastModified(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeRow = (rowIndex) => {
|
const removeRow = (rowIndex) => {
|
||||||
@@ -63,7 +65,7 @@ const Flexible = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setRowCount(rowCount - 1);
|
setRowCount(rowCount - 1);
|
||||||
setHasModifiedRows(true);
|
setLastModified(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveRow = (moveFromIndex, moveToIndex) => {
|
const moveRow = (moveFromIndex, moveToIndex) => {
|
||||||
@@ -75,7 +77,7 @@ const Flexible = (props) => {
|
|||||||
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
|
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
setHasModifiedRows(true);
|
setLastModified(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
const openAddRowModal = (rowIndex) => {
|
const openAddRowModal = (rowIndex) => {
|
||||||
@@ -90,9 +92,16 @@ const Flexible = (props) => {
|
|||||||
moveRow(sourceIndex, destinationIndex);
|
moveRow(sourceIndex, destinationIndex);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateRowCountOnParentRowModified = () => {
|
||||||
|
const countedRows = countRows(name);
|
||||||
|
setRowCount(countedRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(updateRowCountOnParentRowModified, [parentRowsModified]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRowCount(defaultValue.length);
|
setRowCount(defaultValue.length);
|
||||||
setHasModifiedRows(false);
|
setLastModified(null);
|
||||||
|
|
||||||
dispatchCollapsibleStates({
|
dispatchCollapsibleStates({
|
||||||
type: 'SET_ALL_COLLAPSIBLES',
|
type: 'SET_ALL_COLLAPSIBLES',
|
||||||
@@ -101,7 +110,7 @@ const Flexible = (props) => {
|
|||||||
}, [defaultValue]);
|
}, [defaultValue]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<RowModifiedProvider lastModified={lastModified}>
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<div className={baseClass}>
|
<div className={baseClass}>
|
||||||
<Section
|
<Section
|
||||||
@@ -117,7 +126,7 @@ const Flexible = (props) => {
|
|||||||
{rowCount !== 0 && Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
{rowCount !== 0 && Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
||||||
let blockType = fieldState[`${name}.${rowIndex}.blockType`]?.value;
|
let blockType = fieldState[`${name}.${rowIndex}.blockType`]?.value;
|
||||||
|
|
||||||
if (!hasModifiedRows && !blockType) {
|
if (!lastModified && !blockType) {
|
||||||
blockType = defaultValue?.[rowIndex]?.blockType;
|
blockType = defaultValue?.[rowIndex]?.blockType;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +153,7 @@ const Flexible = (props) => {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
singularLabel={blockType}
|
singularLabel={blockType}
|
||||||
defaultValue={hasModifiedRows ? undefined : defaultValue[rowIndex]}
|
defaultValue={lastModified ? undefined : defaultValue[rowIndex]}
|
||||||
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
dispatchCollapsibleStates={dispatchCollapsibleStates}
|
||||||
collapsibleStates={collapsibleStates}
|
collapsibleStates={collapsibleStates}
|
||||||
blockType="flexible"
|
blockType="flexible"
|
||||||
@@ -178,7 +187,7 @@ const Flexible = (props) => {
|
|||||||
slug={modalSlug}
|
slug={modalSlug}
|
||||||
blocks={blocks}
|
blocks={blocks}
|
||||||
/>
|
/>
|
||||||
</Fragment>
|
</RowModifiedProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -4,11 +4,12 @@ import React, {
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||||
|
|
||||||
|
import { RowModifiedProvider, useRowModified } from '../../Form/RowModified';
|
||||||
import withCondition from '../../withCondition';
|
import withCondition from '../../withCondition';
|
||||||
import Button from '../../../controls/Button';
|
import Button from '../../../controls/Button';
|
||||||
import FormContext from '../../Form/Context';
|
import FormContext from '../../Form/Context';
|
||||||
import Section from '../../../layout/Section';
|
import Section from '../../../layout/Section';
|
||||||
import DraggableSection from '../../DraggableSection'; // eslint-disable-line import/no-cycle
|
import DraggableSection from '../../DraggableSection';
|
||||||
import collapsibleReducer from './reducer';
|
import collapsibleReducer from './reducer';
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
@@ -16,11 +17,12 @@ import './index.scss';
|
|||||||
const baseClass = 'field-type repeater';
|
const baseClass = 'field-type repeater';
|
||||||
|
|
||||||
const Repeater = (props) => {
|
const Repeater = (props) => {
|
||||||
|
const parentRowsModified = useRowModified();
|
||||||
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
|
const [collapsibleStates, dispatchCollapsibleStates] = useReducer(collapsibleReducer, []);
|
||||||
const formContext = useContext(FormContext);
|
const formContext = useContext(FormContext);
|
||||||
const [rowCount, setRowCount] = useState(0);
|
const [rowCount, setRowCount] = useState(0);
|
||||||
const [hasModifiedRows, setHasModifiedRows] = useState(false);
|
const [lastModified, setLastModified] = useState(null);
|
||||||
const { fields: fieldState, dispatchFields } = formContext;
|
const { fields: fieldState, dispatchFields, countRows } = formContext;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
label,
|
label,
|
||||||
@@ -41,7 +43,7 @@ const Repeater = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setRowCount(rowCount + 1);
|
setRowCount(rowCount + 1);
|
||||||
setHasModifiedRows(true);
|
setLastModified(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
const removeRow = (rowIndex) => {
|
const removeRow = (rowIndex) => {
|
||||||
@@ -55,7 +57,7 @@ const Repeater = (props) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
setRowCount(rowCount - 1);
|
setRowCount(rowCount - 1);
|
||||||
setHasModifiedRows(true);
|
setLastModified(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
const moveRow = (moveFromIndex, moveToIndex) => {
|
const moveRow = (moveFromIndex, moveToIndex) => {
|
||||||
@@ -67,11 +69,12 @@ const Repeater = (props) => {
|
|||||||
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
|
type: 'MOVE_COLLAPSIBLE', collapsibleIndex: moveFromIndex, moveToIndex,
|
||||||
});
|
});
|
||||||
|
|
||||||
setHasModifiedRows(true);
|
setLastModified(Date.now());
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setRowCount(defaultValue.length);
|
setRowCount(defaultValue.length);
|
||||||
|
setLastModified(null);
|
||||||
|
|
||||||
dispatchCollapsibleStates({
|
dispatchCollapsibleStates({
|
||||||
type: 'SET_ALL_COLLAPSIBLES',
|
type: 'SET_ALL_COLLAPSIBLES',
|
||||||
@@ -79,6 +82,13 @@ const Repeater = (props) => {
|
|||||||
});
|
});
|
||||||
}, [defaultValue]);
|
}, [defaultValue]);
|
||||||
|
|
||||||
|
const updateRowCountOnParentRowModified = () => {
|
||||||
|
const countedRows = countRows(name);
|
||||||
|
setRowCount(countedRows);
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(updateRowCountOnParentRowModified, [parentRowsModified]);
|
||||||
|
|
||||||
const onDragEnd = (result) => {
|
const onDragEnd = (result) => {
|
||||||
if (!result.destination) return;
|
if (!result.destination) return;
|
||||||
const sourceIndex = result.source.index;
|
const sourceIndex = result.source.index;
|
||||||
@@ -87,54 +97,56 @@ const Repeater = (props) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContext onDragEnd={onDragEnd}>
|
<RowModifiedProvider lastModified={lastModified}>
|
||||||
<div className={baseClass}>
|
<DragDropContext onDragEnd={onDragEnd}>
|
||||||
<Section heading={label}>
|
<div className={baseClass}>
|
||||||
<>
|
<Section heading={label}>
|
||||||
<Droppable droppableId="repeater-drop">
|
<>
|
||||||
{provided => (
|
<Droppable droppableId="repeater-drop">
|
||||||
<div
|
{provided => (
|
||||||
ref={provided.innerRef}
|
<div
|
||||||
{...provided.droppableProps}
|
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
|
{`Add ${singularLabel}`}
|
||||||
&& Array.from(Array(rowCount).keys()).map((_, rowIndex) => {
|
</Button>
|
||||||
return (
|
</div>
|
||||||
<DraggableSection
|
</>
|
||||||
fieldTypes={fieldTypes}
|
</Section>
|
||||||
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>
|
|
||||||
|
|
||||||
<div className={`${baseClass}__add-button-wrap`}>
|
</div>
|
||||||
<Button
|
</DragDropContext>
|
||||||
onClick={() => addRow(rowCount)}
|
</RowModifiedProvider>
|
||||||
type="secondary"
|
|
||||||
>
|
|
||||||
{`Add ${singularLabel}`}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
</Section>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</DragDropContext>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { useContext, useCallback, useEffect } from 'react';
|
import { useContext, useCallback, useEffect } from 'react';
|
||||||
import FormContext from '../Form/Context';
|
import FormContext from '../Form/Context';
|
||||||
import { useLocale } from '../../utilities/Locale';
|
|
||||||
|
|
||||||
import './index.scss';
|
import './index.scss';
|
||||||
|
|
||||||
@@ -13,7 +12,6 @@ const useFieldType = (options) => {
|
|||||||
validate,
|
validate,
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const locale = useLocale();
|
|
||||||
const formContext = useContext(FormContext);
|
const formContext = useContext(FormContext);
|
||||||
const { dispatchFields, submitted, processing } = formContext;
|
const { dispatchFields, submitted, processing } = formContext;
|
||||||
const mountValue = formContext.fields[name]?.value || null;
|
const mountValue = formContext.fields[name]?.value || null;
|
||||||
@@ -44,10 +42,6 @@ const useFieldType = (options) => {
|
|||||||
if (defaultValue != null) sendField(defaultValue);
|
if (defaultValue != null) sendField(defaultValue);
|
||||||
}, [defaultValue, sendField]);
|
}, [defaultValue, sendField]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
sendField(null);
|
|
||||||
}, [locale, sendField]);
|
|
||||||
|
|
||||||
const valid = formContext.fields[name] ? formContext.fields[name].valid : true;
|
const valid = formContext.fields[name] ? formContext.fields[name].valid : true;
|
||||||
const showError = valid === false && formContext.submitted;
|
const showError = valid === false && formContext.submitted;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user