unstyled but working repeater

This commit is contained in:
Jarrod Flesch
2020-03-13 16:03:26 -04:00
parent 2c05e403d5
commit 2bfc691659
6 changed files with 202 additions and 30 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,3 @@
.field-repeater {
// background: red;
}

View File

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