simplifies useFieldType and implements a way to retrieve data by path in useForm

This commit is contained in:
James
2020-06-19 09:54:55 -04:00
parent 5d20700cf4
commit 577f3b6164
4 changed files with 49 additions and 57 deletions

View File

@@ -86,23 +86,23 @@ const Form = (props) => {
return reduceFieldsToValues(siblingFields);
}, [fields]);
const countRows = useCallback((rowName) => {
const namePrefixToRemove = rowName.substring(0, rowName.lastIndexOf('.') + 1);
const getDataByPath = useCallback((path) => {
const pathPrefixToRemove = path.substring(0, path.lastIndexOf('.') + 1);
const rows = Object.keys(fields).reduce((matchedRows, key) => {
if (key.indexOf(`${rowName}.`) === 0) {
if (key.indexOf(`${path}.`) === 0) {
return {
...matchedRows,
[key.replace(namePrefixToRemove, '')]: fields[key],
[key.replace(pathPrefixToRemove, '')]: fields[key],
};
}
return matchedRows;
}, {});
const unflattenedRows = unflatten(rows);
const rowCount = unflattenedRows[rowName.replace(namePrefixToRemove, '')]?.length || 0;
return rowCount;
const rowValues = reduceFieldsToValues(rows);
const unflattenedRows = unflatten(rowValues);
return unflattenedRows;
}, [fields]);
const validateForm = useCallback(() => {
@@ -283,12 +283,12 @@ const Form = (props) => {
getField,
processing,
submitted,
countRows,
getDataByPath,
getData,
getSiblingData,
validateForm,
modified,
setModified,
getSiblingData,
}}
>
<HiddenInput

View File

@@ -8,6 +8,7 @@ import Button from '../../../elements/Button';
import DraggableSection from '../../DraggableSection';
import reducer from './reducer';
import { useRenderedFields } from '../../RenderFields';
import useForm from '../../Form/useForm';
import useFieldType from '../../useFieldType';
import Error from '../../Error';
import { repeater } from '../../../../../validation/validations';
@@ -35,6 +36,7 @@ const Repeater = (props) => {
const dataToInitialize = initialData || defaultValue;
const [rows, dispatchRows] = useReducer(reducer, []);
const { customComponentsPath } = useRenderedFields();
const { getDataByPath } = useForm();
const path = pathFromProps || name;
@@ -58,25 +60,32 @@ const Repeater = (props) => {
});
const addRow = (rowIndex) => {
const data = getDataByPath(path)?.[name];
dispatchRows({
type: 'ADD', index: rowIndex,
type: 'ADD', index: rowIndex, data,
});
setValue(value + 1);
};
const removeRow = (rowIndex) => {
const data = getDataByPath(path)?.[name];
dispatchRows({
type: 'REMOVE',
index: rowIndex,
data,
});
setValue(value - 1);
};
const moveRow = (moveFromIndex, moveToIndex) => {
const data = getDataByPath(path)?.[name];
dispatchRows({
type: 'MOVE', index: moveFromIndex, moveToIndex,
type: 'MOVE', index: moveFromIndex, moveToIndex, data,
});
};
@@ -90,7 +99,7 @@ const Repeater = (props) => {
useEffect(() => {
dispatchRows({
type: 'SET_ALL',
payload: dataToInitialize.reduce((acc, data) => ([
rows: dataToInitialize.reduce((acc, data) => ([
...acc,
{
key: uuidv4(),

View File

@@ -2,16 +2,14 @@ import { v4 as uuidv4 } from 'uuid';
const reducer = (currentState, action) => {
const {
type, index, moveToIndex, payload, data,
type, index, moveToIndex, rows, data,
} = action;
const stateCopy = [...currentState];
const movingRowState = stateCopy[index];
switch (type) {
case 'SET_ALL':
return payload;
return rows;
case 'ADD':
stateCopy.splice(index + 1, 0, {
@@ -20,20 +18,40 @@ const reducer = (currentState, action) => {
data,
});
return stateCopy;
data.splice(index + 1, 0, {});
return stateCopy.map((row, i) => {
return {
...row,
data: {
...(data[i] || {}),
},
};
});
case 'REMOVE':
stateCopy.splice(index, 1);
return stateCopy;
case 'UPDATE_COLLAPSIBLE_STATUS':
stateCopy[index].open = !movingRowState.open;
stateCopy[index].open = !stateCopy[index].open;
return stateCopy;
case 'MOVE':
stateCopy.splice(index, 1);
stateCopy.splice(moveToIndex, 0, movingRowState);
return stateCopy;
case 'MOVE': {
const stateCopyWithNewData = stateCopy.map((row, i) => {
return {
...row,
data: {
...(data[i] || {}),
},
};
});
const movingRowState = { ...stateCopyWithNewData[index] };
stateCopyWithNewData.splice(index, 1);
stateCopyWithNewData.splice(moveToIndex, 0, movingRowState);
return stateCopyWithNewData;
}
default:
return currentState;

View File

@@ -2,7 +2,6 @@ import {
useContext, useCallback, useEffect, useState,
} from 'react';
import FormContext from '../Form/Context';
import { useLocale } from '../../utilities/Locale';
import useDebounce from '../../../hooks/useDebounce';
import './index.scss';
@@ -10,7 +9,6 @@ import './index.scss';
const useFieldType = (options) => {
const {
path,
required,
initialData: data,
defaultValue,
validate,
@@ -24,16 +22,11 @@ const useFieldType = (options) => {
// If no initialData, use default value
const initialData = data !== undefined ? data : defaultValue;
const locale = useLocale();
const formContext = useContext(FormContext);
const {
dispatchFields, submitted, processing, getField, setModified, modified,
} = formContext;
// Maintain an internal initial value AND value to ensure that the form
// can successfully differentiate between updates sent from fields
// that are meant to be initial values vs. values that are deliberately changed
const [internalInitialValue, setInternalInitialValue] = useState(initialData);
const [internalValue, setInternalValue] = useState(initialData);
// Debounce internal values to update form state only every 60ms
@@ -45,7 +38,6 @@ const useFieldType = (options) => {
// Valid could be a string equal to an error message
const valid = (field && typeof field.valid === 'boolean') ? field.valid : true;
const valueFromForm = field?.value;
const showError = valid === false && submitted;
// Method to send update field values from field component(s)
@@ -79,38 +71,12 @@ const useFieldType = (options) => {
setInternalValue(value);
}, [setModified, modified]);
const setInitialValue = useCallback((value) => {
setInternalInitialValue(value);
}, []);
// Remove field from state on "unmount"
// This is mostly used for repeater / flex content row modifications
useEffect(() => {
return () => dispatchFields({ path, type: 'REMOVE' });
}, [dispatchFields, path]);
// Whenever the value from form updates,
// update internal value as well
useEffect(() => {
if (valueFromForm !== undefined) {
setInternalInitialValue(valueFromForm);
}
}, [valueFromForm, setInternalInitialValue]);
// When locale changes, and / or initialData changes,
// reset internal initial value
useEffect(() => {
if (initialData !== undefined) {
setInternalInitialValue(initialData);
}
}, [initialData, setInternalInitialValue, locale]);
// When internal initial value changes, set internal value
// This is necessary to bypass changing the form to a modified:true state
useEffect(() => {
setInternalValue(internalInitialValue);
}, [setInternalValue, internalInitialValue]);
// The only time that the FORM value should be updated
// is when the debounced value updates. So, when the debounced value updates,
// send it up to the form
@@ -132,7 +98,6 @@ const useFieldType = (options) => {
formSubmitted: submitted,
formProcessing: processing,
setValue,
setInitialValue,
};
};