react-formstate-fp
Version:
An API to build, query, update, and bind formstate in React. Develop forms DRYly in React.
214 lines (155 loc) • 6.88 kB
JavaScript
import { getRootModelKey } from './schemaAndLookup.js';
import {
isValid, isInvalid, isChanged, isBlurred, isSubmitting, isSubmitted,
isWaiting, isAsynclyValidated, getAsyncEndTime, getAsyncError, setSubmitting, setSubmitted
} from './fieldAndScopeStatus.js';
//
// Reading form status
//
export function isFormWaiting(formstate) {
return Object.keys(formstate.statuses).some(id => {
const s = formstate.statuses[id];
return Boolean(s.async.started && !s.async.finished);
});
}
function _getFormAsyncErrorStatuses(formstate) {
return Object.keys(formstate.statuses).filter(id => formstate.statuses[id].async.error);
}
export function isFormAsyncError(formstate) {
return _getFormAsyncErrorStatuses(formstate).length > 0;
}
export function getFormAsyncErrorModelKeys(formstate) {
return _getFormAsyncErrorStatuses(formstate).map(id => getRootModelKey(formstate, id));
}
export function wasAsyncErrorDuringSubmit(formstate, modelKey) {
const asyncEnd = getAsyncEndTime(formstate, modelKey);
const submitStart = getFormSubmissionStartTime(formstate);
const submitEnd = getFormSubmissionEndTime(formstate);
return Boolean(submitStart && submitStart < asyncEnd && (!submitEnd || asyncEnd <= submitEnd));
}
export function isInputDisabled(formstate) {
return formstate.formStatus.inputDisabled;
}
export function isFormSubmitting(formstate) {
return Boolean(formstate.formStatus.submit.started && !formstate.formStatus.submit.finished);
}
export function isFormSubmittedAndUnchanged(formstate) {
return isSubmitted({...formstate, nestedScopeId: null}, '');
}
export function getFormSubmissionStartTime(formstate) {
return formstate.formStatus.submit.started;
}
export function getFormSubmissionEndTime(formstate) {
return formstate.formStatus.submit.finished;
}
export function getFormSubmissionValidity(formstate) {
return formstate.formStatus.submit.valid;
}
export function getFormSubmissionAsyncErrorModelKeys(formstate) {
return formstate.formStatus.submit.asyncErrorModelKeys;
}
export function getFormSubmissionError(formstate) {
return formstate.formStatus.submit.submissionError;
}
export function getFormSubmissionHistory(formstate) {
return formstate.formStatus.submitHistory;
}
export function wasSuccessfulSubmit(formstate) {
return Boolean(!isFormSubmitting(formstate) && getFormSubmissionValidity(formstate) && !getFormSubmissionError(formstate));
}
export function getFormCustomProperty(formstate, name) {
return formstate.formStatus.custom[name];
}
export function isModelValid(formstate) {
formstate = {...formstate, nestedScopeId: null};
return Object.keys(formstate.lookup.idsByRootModelKey).every(rootModelKey => isValid(formstate, rootModelKey));
}
function _true() {return true;}
export function isModelInvalid(formstate) {
return isPrimedModelInvalid(formstate, _true);
}
export function isPrimedModelInvalid(formstate, calculatePrimed) {
formstate = {...formstate, nestedScopeId: null};
return Object.keys(formstate.statuses).some(id => {
const rootModelKey = getRootModelKey(formstate, id);
const primed = (calculatePrimed || primeOnChange)(formstate, rootModelKey);
return isInvalid(formstate, rootModelKey) && primed;
});
}
// These don't try to be all things to all people because the user can provide their own logic.
export function primeOnChange(formstate, modelKey) {
return primeOnSubmit(formstate, modelKey) || isChanged(formstate, modelKey);
}
export function primeOnBlur(formstate, modelKey) {
return primeOnSubmit(formstate, modelKey) || isBlurred(formstate, modelKey);
}
export function primeOnChangeThenBlur(formstate, modelKey) {
return primeOnSubmit(formstate, modelKey) || (isChanged(formstate, modelKey) && isBlurred(formstate, modelKey));
}
export function primeOnSubmit(formstate, modelKey) {
if (isSubmitted(formstate, modelKey)) {return true;}
if (isWaiting(formstate, modelKey) || isAsynclyValidated(formstate, modelKey) || getAsyncError(formstate, modelKey)) {return true;}
return isSubmitting(formstate, modelKey) && !isFormWaiting(formstate); // Wait until async finishes to show all new results at same time.
}
//
// Modifying form status
//
export function setFormSubmitting(formstate) {
const formStatus = {...formstate.formStatus};
formStatus.submit = { started: Date.now() };
formstate = {...formstate, formStatus};
// markUnsubmittedStatusesSubmitting adds flexibility for priming.
// It is relevant even if there is no asynchronous validation.
return markUnsubmittedStatusesSubmitting(formstate);
}
export function markUnsubmittedStatusesSubmitting(formstate) {
Object.keys(formstate.lookup.idsByRootModelKey).forEach((rootModelKey) => {
formstate = setSubmitting(formstate, rootModelKey);
});
return formstate;
}
export function setFormSubmitted(formstate) {
const submit = {...formstate.formStatus.submit, finished: Date.now()};
if (isModelInvalid(formstate)) {submit.valid = false;}
else if (isModelValid(formstate)) {submit.valid = true;}
else {submit.valid = null;}
// If the user customizes the submit, and decides not to run async validation if the model is syncly invalid,
// then there could be async errors that didn't technically happen during the submit. To be precise, it's arguably
// better to filter those out when capturing submit history.
const asyncErrorModelKeys = getFormAsyncErrorModelKeys(formstate).filter(modelKey => wasAsyncErrorDuringSubmit(formstate, modelKey));
if (asyncErrorModelKeys.length > 0) {
submit.asyncErrorModelKeys = asyncErrorModelKeys;
}
formstate = {...formstate, formStatus: {...formstate.formStatus, submit}};
formstate.formStatus.submitHistory = [formstate, ...formstate.formStatus.submitHistory];
return markAllStatusesSubmitted(formstate);
}
export function markAllStatusesSubmitted(formstate) {
Object.keys(formstate.lookup.idsByRootModelKey).forEach((rootModelKey) => {
formstate = setSubmitted(formstate, rootModelKey);
});
return formstate;
}
export function setFormSubmissionError(formstate, error) {
return {...formstate,
formStatus: {...formstate.formStatus,
submit: {...formstate.formStatus.submit,
submissionError: error || new Error('Unknown error.')
}
}
};
}
function _setInputDisabled(formstate, inputDisabled) {
return {...formstate, formStatus: {...formstate.formStatus, inputDisabled}};
}
export function setInputDisabled(formstate) {
return _setInputDisabled(formstate, true);
}
export function setInputEnabled(formstate) {
return _setInputDisabled(formstate, false);
}
export function setFormCustomProperty(formstate, name, value) {
formstate = {...formstate, formStatus: {...formstate.formStatus, custom: {...formstate.formStatus.custom}}};
formstate.formStatus.custom[name] = value;
return formstate;
}