unstyled but working repeater
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import React, { useState, useReducer } from 'react';
|
||||
import React, { useState, useReducer, useCallback } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
import PropTypes from 'prop-types';
|
||||
import { unflatten } from 'flat';
|
||||
import flatten, { unflatten } from 'flat';
|
||||
import FormContext from './Context';
|
||||
import { useLocale } from '../../utilities/Locale';
|
||||
import { useStatusList } from '../../modules/Status';
|
||||
@@ -12,26 +12,103 @@ import './index.scss';
|
||||
|
||||
const baseClass = 'form';
|
||||
|
||||
const initialFieldState = {};
|
||||
const reduceToFieldNames = fields => fields.reduce((acc, field) => {
|
||||
if (field.name) acc.push(field.name);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
const reindexRows = ({
|
||||
fieldName, fields, rowFieldNamesAsArray, totalRows, index: adjustmentIndex, type,
|
||||
}) => {
|
||||
return Array.from(Array(totalRows).keys()).reduce((reindexedRows, _, rowIndex) => {
|
||||
const currentRow = rowFieldNamesAsArray.reduce((fieldAcc, rowFieldName) => {
|
||||
let newIndex;
|
||||
switch (type) {
|
||||
case 'addAfter':
|
||||
newIndex = rowIndex <= adjustmentIndex ? rowIndex : rowIndex + 1;
|
||||
if (rowIndex === adjustmentIndex) {
|
||||
return {
|
||||
...fieldAcc,
|
||||
[`${fieldName}.${newIndex}.${rowFieldName}`]: fields[`${fieldName}.${rowIndex}.${rowFieldName}`],
|
||||
};
|
||||
}
|
||||
return {
|
||||
...fieldAcc,
|
||||
[`${fieldName}.${newIndex}.${rowFieldName}`]: fields[`${fieldName}.${rowIndex}.${rowFieldName}`],
|
||||
};
|
||||
|
||||
case 'remove':
|
||||
if (rowIndex === adjustmentIndex) return { ...fieldAcc };
|
||||
|
||||
newIndex = rowIndex < adjustmentIndex ? rowIndex : rowIndex - 1;
|
||||
return {
|
||||
...fieldAcc,
|
||||
[`${fieldName}.${newIndex}.${rowFieldName}`]: fields[`${fieldName}.${rowIndex}.${rowFieldName}`],
|
||||
};
|
||||
|
||||
default:
|
||||
return { ...fieldAcc };
|
||||
}
|
||||
}, {});
|
||||
|
||||
return { ...reindexedRows, ...currentRow };
|
||||
}, {});
|
||||
};
|
||||
|
||||
const initialFieldState = {};
|
||||
function fieldReducer(state, action) {
|
||||
return {
|
||||
...state,
|
||||
[action.name]: {
|
||||
value: action.value,
|
||||
valid: action.valid,
|
||||
},
|
||||
};
|
||||
switch (action.type) {
|
||||
case 'replace':
|
||||
return {
|
||||
...action.value,
|
||||
};
|
||||
|
||||
default:
|
||||
return {
|
||||
...state,
|
||||
[action.name]: {
|
||||
value: action.value,
|
||||
valid: action.valid,
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const Form = (props) => {
|
||||
const [fields, setField] = useReducer(fieldReducer, initialFieldState);
|
||||
const [fields, dispatchFields] = useReducer(fieldReducer, initialFieldState);
|
||||
const [submitted, setSubmitted] = useState(false);
|
||||
const [processing, setProcessing] = useState(false);
|
||||
const history = useHistory();
|
||||
const locale = useLocale();
|
||||
const { addStatus } = useStatusList();
|
||||
|
||||
function adjustRows({
|
||||
index, fieldName, fields: fieldsForInsert, totalRows, type,
|
||||
}) {
|
||||
const rowFieldNamesAsArray = reduceToFieldNames(fieldsForInsert);
|
||||
const reindexedRows = reindexRows({
|
||||
fieldName,
|
||||
fields,
|
||||
rowFieldNamesAsArray,
|
||||
totalRows,
|
||||
index,
|
||||
type,
|
||||
});
|
||||
|
||||
const stateWithoutFields = { ...fields };
|
||||
Array.from(Array(totalRows).keys()).forEach((rowIndex) => {
|
||||
rowFieldNamesAsArray.forEach((rowFieldName) => { delete stateWithoutFields[`${fieldName}.${rowIndex}.${rowFieldName}`]; });
|
||||
});
|
||||
|
||||
dispatchFields({
|
||||
type: 'replace',
|
||||
value: {
|
||||
...stateWithoutFields,
|
||||
...reindexedRows,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
onSubmit,
|
||||
ajax,
|
||||
@@ -143,10 +220,11 @@ const Form = (props) => {
|
||||
className={classes}
|
||||
>
|
||||
<FormContext.Provider value={{
|
||||
setField,
|
||||
dispatchFields,
|
||||
fields,
|
||||
processing,
|
||||
submitted,
|
||||
adjustRows,
|
||||
}}
|
||||
>
|
||||
<HiddenInput
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
RenderFieldsNotes
|
||||
|
||||
slides.0.meta.title
|
||||
slides.1.heroInfo.title
|
||||
|
||||
fields: [
|
||||
{
|
||||
name: slides,
|
||||
type: repeater,
|
||||
fields: [
|
||||
{
|
||||
type: group,
|
||||
name: meta,
|
||||
fields: [
|
||||
{
|
||||
name: title,
|
||||
type: text,
|
||||
component: require.resolve('/aslifjawelifjaew)
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
type: group,
|
||||
name: heroInfo,
|
||||
fields: [
|
||||
{
|
||||
name: title,
|
||||
type: text,
|
||||
component: require.resolve('/aslifjawelifjaew)
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useCallback } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import fieldTypes from '../field-types';
|
||||
|
||||
@@ -10,7 +10,7 @@ const RenderFields = ({ fields, initialData }) => {
|
||||
<>
|
||||
{fields.map((field, i) => {
|
||||
const { defaultValue } = field;
|
||||
const FieldComponent = field.component || fieldTypes[field.type];
|
||||
const FieldComponent = fieldTypes[field.type];
|
||||
|
||||
if (FieldComponent) {
|
||||
return (
|
||||
|
||||
@@ -1,30 +1,86 @@
|
||||
import React from 'react';
|
||||
import React, { useState, useContext, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
import FormContext from '../../Form/Context';
|
||||
import Section from '../../../layout/Section';
|
||||
import RenderFields from '../../RenderFields';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
const Repeater = (props) => {
|
||||
const formContext = useContext(FormContext);
|
||||
const { adjustRows } = formContext;
|
||||
|
||||
const {
|
||||
label, fields, name, defaultValue,
|
||||
label,
|
||||
name,
|
||||
defaultValue: defaultOrSavedValue,
|
||||
fields,
|
||||
} = props;
|
||||
|
||||
let rows = defaultValue.length > 0 ? defaultValue : [{}];
|
||||
const [internalRowCount, setInternalRowCount] = useState(1);
|
||||
useEffect(() => { setInternalRowCount(defaultOrSavedValue.length); }, [defaultOrSavedValue]);
|
||||
|
||||
function addNewRow({ rowIndex }) {
|
||||
setInternalRowCount(count => count + 1);
|
||||
adjustRows({
|
||||
index: rowIndex + 1,
|
||||
fieldName: name,
|
||||
totalRows: internalRowCount,
|
||||
fields,
|
||||
type: 'addAfter',
|
||||
});
|
||||
}
|
||||
|
||||
function removeRow({ rowIndex }) {
|
||||
setInternalRowCount(count => count - 1);
|
||||
adjustRows({
|
||||
index: rowIndex,
|
||||
fieldName: name,
|
||||
totalRows: internalRowCount,
|
||||
fields,
|
||||
type: 'remove',
|
||||
});
|
||||
}
|
||||
|
||||
const initialRows = defaultOrSavedValue.length > 0 ? defaultOrSavedValue : [{}];
|
||||
const iterableInternalRowCount = Array.from(Array(internalRowCount).keys());
|
||||
|
||||
return (
|
||||
<div className="field-repeater">
|
||||
<Section heading={label}>
|
||||
{rows.map((row, i) => {
|
||||
|
||||
{iterableInternalRowCount.map((_, rowIndex) => {
|
||||
return (
|
||||
<RenderFields
|
||||
key={i}
|
||||
fields={fields.map((subField) => ({
|
||||
...subField,
|
||||
name: `${name}.${i}.${subField.name}`,
|
||||
defaultValue: row[subField.name] || null,
|
||||
}))}
|
||||
/>
|
||||
)
|
||||
<React.Fragment key={rowIndex}>
|
||||
<h2>{`Repeater Item ${rowIndex}`}</h2>
|
||||
|
||||
<RenderFields
|
||||
fields={fields.map((field) => {
|
||||
return ({
|
||||
...field,
|
||||
name: `${name}.${rowIndex}.${field.name}`,
|
||||
defaultValue: initialRows[rowIndex] ? initialRows[rowIndex][field.name] : null,
|
||||
});
|
||||
})}
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={() => addNewRow({ rowIndex })}
|
||||
type="button"
|
||||
>
|
||||
{`Add after ${rowIndex}`}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => removeRow({ rowIndex })}
|
||||
type="button"
|
||||
>
|
||||
{`Remove ${rowIndex}`}
|
||||
</button>
|
||||
</React.Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
</Section>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.field-repeater {
|
||||
// background: red;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import './index.scss';
|
||||
|
||||
const useFieldType = (options) => {
|
||||
const formContext = useContext(FormContext);
|
||||
const { setField, submitted, processing } = formContext;
|
||||
const { dispatchFields, submitted, processing } = formContext;
|
||||
|
||||
const {
|
||||
name,
|
||||
@@ -16,14 +16,14 @@ const useFieldType = (options) => {
|
||||
} = options;
|
||||
|
||||
const sendField = useCallback((valueToSend) => {
|
||||
setField({
|
||||
dispatchFields({
|
||||
name,
|
||||
value: valueToSend,
|
||||
valid: required && validate
|
||||
? validate(valueToSend || '')
|
||||
: true,
|
||||
});
|
||||
}, [name, required, setField, validate]);
|
||||
}, [name, required, dispatchFields, validate]);
|
||||
|
||||
useEffect(() => {
|
||||
sendField(defaultValue);
|
||||
|
||||
Reference in New Issue
Block a user