-
There is not a generic solution for handling dirty state checking and reverts throughout our codebase.
-
Validation is a mess, either it's duplicated as inline validation in the "Input" component and in the "Form" component. Other places it exist in the parent component and passed down through props to the "Input", this means our inputs require the parent component to know and do a ton of validation making them no longer easily portable.
- Generalizes and abstracts form dirty state, and reverts
- Allows to define validation once for an input while keeping form inputs portable and easily composeable.
- A standardized interface for validation functions and how errors are presented both inline and at the form level.
import _ from 'lodash';
import React from 'react';
import PropTypes from 'prop-types';
import { Input, Button } from 'optimizely-oui'
import SectionModule from '../../section_module/';
@Form({
// Populates the form HoC with initial state for the entity being edited
// accessible via `props.formState.clean` and `props.form.state.editing`
initialStateGetter: SectionModule.getters.currentlyEditingExperiment,
})
class MyForm extends React.Component {
constructor(props) {
super(props);
// global validation functino
// form {FormInterface} is passed to wrapped component as prop
const { form } = this.props;
form.addValidationFn({
keypath: 'name',
getErrors(val) {
// these should be composable
let hasError
if (val.length === 0) {
return {
hasError: true,
details: {
message: 'Name is required',
}
}
}
if (val.length > 63) {
return {
hasError: true,
details: {
message: 'Name must be less than 64 characters',
}
}
}
return {
hasError: false,
details: {},
}
},
})
}
handleSave = () => {
const { form } = this.props;
// this call will populate errors and pass them through props to the child components
// Question: should this return something?
form.validateAll();
if (!form.isFormValid()) {
// you can also use this display global errors
return;
}
const data = form.getValue().toJS();
// do save operation with data
}
render() {
const {
form,
formState,
} = this.props;
const nameField = form.field('name');
return (
<form>
<p>
is dirty:
<strong>
{ formState.isDirty ? 'true': 'false' }
</strong>
</p>
<Input
value={ nameField.getValue() }
onChange={ (e) => nameField.setValue(e.target.value) }
errors={ nameField.getErrors() }
/>
<div>
<Button
isDisabled={ !formState.isDirty }
onClick={ form.revert }>
Revert
</Button>
<Button
onClick={ this.handleSave }>
Save
</Button>
</div>
</form>
)
}
}
+------------------------+
| Form HoC |
+-------+--------+-------+
| |
+ +
`form` api `formState`
+--------------------------+
| |
| Wrapped components |
| `props.form` |
| `props.formState` |
+------------+-------------+
|
|
|
v
FormInput
- Receives a getter for initial form state
- Passes
form
andformState
down as props - exposes functionality such as
revert
,getValue()
,setValue(val)
- Any component with the following interface
<FormInput
form={{
setValue,
getValue,
getErrors,
addValidationFn,
}}
/>
// or for convenience
<FormInput
form={ props.form.field('name') }
/>
Passes as a props.form
to the wrapped component of the Form
HoC.
returns
all of the form data
returns
data for that particular keypath, ex: form.getValue('variations')
Reverts the editing state to the original "initialState"
Iterates and runs all validation functions, useful for right before submission. Validation results is accessible via
form.isFormValid()
args
keypath
(optional) if supplied returns data for keypath only