Video of the talk 👉 https://www.youtube.com/watch?v=kIi9OV338c4&list=PLOEJ0eNOcZeosJfuT0dRcRWvNz4pru7SD&index=7&t=0s
Code examples 👉 https://github.com/RStankov/talks-code/tree/master/2019.05.11%20-%20Forms%20with%20React%202
How not to hate your lifewhen dealing with formsRadoslav Stankov 11/05/2019
View Slide
Radoslav Stankov@rstankovhttp://rstankov.comhttp://github.com/rstankov
Who is developer worst enemy? !
" The designer Who is developer worst enemy? !
" The designer# The QA Who is developer worst enemy? !
" The designer# The QA$ The sys admin Who is developer worst enemy? !
" The designer# The QA$ The sys admin% The project owner Who is developer worst enemy? !
" The designer# The QA$ The sys admin% The project owner& Santa Who is developer worst enemy? !
" The designer# The QA$ The sys admin% The project owner& Santa' Forms Who is developer worst enemy? !
" The designer# The QA$ The sys admin% The project owner& Santa( Form Who is developer worst enemy? !
Form Basics
function SubmitTalkForm() {const [state, setState] = useState({});const onSubmit = event => {event.preventDefault();console.log(state);};return (Title:id="title"type="text"name="title"value={state.title || ''}onChange={({ target }) =>setState({ ...state, title: target.value })}/>);}
Input changefunction SubmitTalkForm() {const [state, setState] = useState({});const onSubmit = event => {event.preventDefault();console.log(state);};return (Title:id="title"type="text"name="title"value={state.title || ''}onChange={({ target }) =>setState({ ...state, title: target.value })}/>);}
Input change onChangesetStatefunction SubmitTalkForm() {const [state, setState] = useState({});const onSubmit = event => {event.preventDefault();console.log(state);};return (Title:id="title"type="text"name="title"value={state.title || ''}onChange={({ target }) =>setState({ ...state, title: target.value })}/>);}
Input change onChangesetState renderNew valuefunction SubmitTalkForm() {const [state, setState] = useState({});const onSubmit = event => {event.preventDefault();console.log(state);};return (Title:id="title"type="text"name="title"value={state.title || ''}onChange={({ target }) =>setState({ ...state, title: target.value })}/>);}
FieldInput ControlLabel
FieldInput ControlLabel ? ErrorDescription
function SubmitTalkForm() {const [state, setState] = useState({});const onSubmit = event => { /* */ };return (id="title"name="title"value={state.title || ''}onChange={({ target }) => setState({ ...state, title: target.value })}/>);}function Field({ label, name, ...inputProps }) {return ({label || capitalize(name)}:);}
Form
FormSome Component
FormSome ComponentSome Other Component
FormSome ComponentSome Other Component Field
FormSome ComponentSome Other Component FieldContext
FormSome ComponentSome Other Component FieldContextContext
const FormContext = React.createContext();function SubmitTalkForm() {const context = useState({});const onSubmit = event => { /* */ };return ();}function Field({ label, name, ...inputProps }) {const [state, setState] = useContext(FormContext);return ({label || capitalize(name)}: type="text"id={name}name={name}
return ();}function Field({ label, name, ...inputProps }) {const [state, setState] = useContext(FormContext);return ({label || capitalize(name)}: type="text"id={name}name={name}value={state[name] || ''}onChange={({ target }) => setState({ ...state, [name]: target.value })}{...inputProps}/>);}
function SubmitTalkForm() {const onSubmit = values => {console.log(value);};return ();}
const FormContext = React.createContext([{}, function() {}]);function Form({ onSubmit, initialValues, ...props }) {const context = useState(initialValues);function handleSubmit(e) {e.preventDefault();onSubmit(context[0]);}return ();}Form.Field = Field;Form.Submit = () => ;
function Field({ label, name, control, ...inputProps }) {const [state, setState] = useContext(FormContext);return ({label || capitalize(name)}:type="text"id={name}name={name}value={state[name] || ''}onChange={({ target }) => setState({ ...state, [name]: target.value }) }{...inputProps}/>);}
function Field({ label, name, control, ...inputProps }) {const [state, setState] = useContext(FormContext);return ({label || capitalize(name)}:type={control || 'text'}id={name}name={name}value={state[name] || ''}onChange={({ target }) => setState({ ...state, [name]: target.value }) }{...inputProps}/>);}
function Field({ label, name, control, ...inputProps }) {const [state, setState] = useContext(FormContext);let Control = props => ;if (control === 'textarea') {Control = props => ;}return ({label || capitalize(name)}:id={name}name={name}value={state[name] || ''}onChange={({ target }) => setState({ ...state, [name]: target.value })}{...inputProps}/>);}
const LENGTH_OPTIONS = [{ value: 15, label: '15 minutes' },{ value: 30, label: '30 minutes' },{ value: 45, label: '45 minutes' },];
function Field({ label, name, control, ...inputProps }) {const [state, setState] = useContext(FormContext);let Control = props => ;if (control === 'textarea') {Control = props => ;}if (control === 'select') {Control = ({ options, ...props }) => ({options.map(({ label, value }, i) => ({label || value}))});}return ({label || capitalize(name)}:id={name}name={name}
const CONTROLS = {undefined: props => ,text: props => ,email: props => ,textarea: props => ,select: ({ options, ...props }) => ({options.map(({ label, value }, i) => ({label || value}))}),}; function Field({ label, name, control, ...inputProps }) {const [state, setState] = useContext(FormContext);const Control = CONTROLS[name];return ({label || capitalize(name)}:id={name}name={name}
const CONTROLS = {/* */radioGroup: ({ value options, name, onChange, ...props }) => ({options.map((option, value }, i) => (type="radio"name={name}value={option.value}checked={option.value === value}onChange={onChange}{...props}/>{option.label || option.value}))}),};
function Field({ label, name, control, ...inputProps }) {const [state, setState] = useContext(FormContext);const Control = typeof control === 'function' ? control : CONTROLS[control];return ({label || capitalize(name)}:id={name}name={name}value={state[name] || ''}onChange={({ target }) => setState({ ...state, [name]: target.value })}{...inputProps}/>);}
Init
Init Loading
Init LoadingSuccess
Init LoadingSuccessErrors
Submit ServerSuccessErrors
remoteCall Server{ result: 'ok' }{ errors: {…} }
remoteCall Server{ result: 'ok' }{ errors: {field1: 'error1', field2: 'error2' } }
{ errors: {field1: 'error1', field2: 'error2' } }
• simple form interface• extensible fields• standardized form layout ) So far...
• validations - client / server side• protection again double submit• nested fields• handle submit• ... more ! What is missing?
Libraries
* https://github.com/tannerlinsley/react-form+ https://github.com/prometheusresearch/react-forms, https://github.com/codecks-io/react-reform- https://github.com/erikras/redux-form. https://github.com/Semantic-Org/Semantic-UI-React/ https://github.com/jaredpalmer/formik0 https://github.com/final-form/final-form 1 Libraries
* https://github.com/tannerlinsley/react-form+ https://github.com/prometheusresearch/react-forms, https://github.com/codecks-io/react-reform- https://github.com/erikras/redux-form. https://github.com/Semantic-Org/Semantic-UI-React/ https://github.com/jaredpalmer/formik0 https://github.com/final-form/final-formLibraries
Final Form Formik
2
https://github.com/final-form/3
✅ Zero dependencies *✅ Framework agnostic✅ Opt-in subscriptions - only update on the state you need!✅ 5 7.7k gzipped 5 - with React components
import * as FinalForm from 'react-final-form';const SubmitTalkForm = () => (onSubmit={onSubmit}validate={validate}render={({ handleSubmit, pristine, invalid }) => (Title{({ input, meta }) => (Email{meta.touched && meta.error && {meta.error}})}name="description"render={({ input, meta }) => (
validate={validate}render={({ handleSubmit, pristine, invalid }) => (Title{({ input, meta }) => (Email{meta.touched && meta.error && {meta.error}})}name="description"render={({ input, meta }) => (Description{meta.touched && meta.error && {meta.error}})}/>
)}name="description"render={({ input, meta }) => (Description{meta.touched && meta.error && {meta.error}})}/>LengthLevelname=""component={CONTROLS.radioGroup}options={LEVEL_OPTIONS}/>
/>LengthLevelname=""component={CONTROLS.radioGroup}options={LEVEL_OPTIONS}/>SpeakersSubmit)}/>);
/>LengthLevelname=""component={CONTROLS.radioGroup}options={LEVEL_OPTIONS}/>SpeakersSubmit)}/>);6
import * as FinalForm from 'react-final-form';const SubmitTalkForm = () => (onSubmit={onSubmit}validate={validate}render={({ handleSubmit, pristine, invalid }) => (Title{({ input, meta }) => (Email{meta.touched && meta.error && {meta.error}})}name="description"render={({ input, meta }) => (Description{meta.touched && meta.error && {meta.error}})}/>LengthLevelname=""component={CONTROLS.radioGroup}options={LEVEL_OPTIONS}/>SpeakersSubmit)}/>);
function Form({ initialValues, onSubmit, children }) {return ({({ handleSubmit }) => {children}});}
export default function Field({ control, ...props }) {control = typeof control === 'function' ? control : CONTROLS[control];return ;}function FieldRow({control: Control,label,meta,input,fields,...inputProps}) {const error = meta.error || meta.submitError;const name = input.name;return ({label || capitalize(name)}:{error && {error}});}
function FieldRow({control: Control,label,meta,input,fields,id,...inputProps}) {const error = meta.error || meta.submitError;const name = input.name;id = React.useMemo(() => id || uniqueId(`form-${name}-`), [id, name]);return ({label || capitalize(name)}:{error && {error}});}
Form.Submit = () => ({form => (type="submit"disabled={form.submitting}value={`Submit${form.submitting ? '...' : ''}`}/>{form.submitSucceeded && '7 Submitted'}{form.submitting && '8 Saving…'}{form.submitFailed && '9 Oh-oh! There has been an error…'})});
{form => console.log(form)}{"dirty": false,"dirtyFields": {},"dirtySinceLastSubmit": false,"errors": {},"hasSubmitErrors": false,"hasValidationErrors": false,"initialValues": {},"invalid": false,"modified": {"title": false,"email": false,"description": false,"length": false,"level": false,},"pristine": true,"submitting": false,"submitFailed": false,"submitSucceeded": false,"touched": {"title": false,"email": false,"description": false,"length": false,"level": false,},"valid": true,"validating": false,"values": {},"visited": {"title": false,"email": false,"description": false,"length": false,"level": false,},"form": {"mutators": {}},"mutators": {}}
{form => console.log(form)}{"dirty": false,"dirtyFields": {},"dirtySinceLastSubmit": false,"errors": {},"hasSubmitErrors": false,"hasValidationErrors": false,"initialValues": {},"invalid": false,"modified": {"title": false,"email": false,"description": false,"length": false,"level": false,},"pristine": true,"submitting": false,"submitFailed": false,"submitSucceeded": false,"touched": {"title": false,"email": false,"description": false,"length": false,"level": false,},"valid": true,"validating": false,"values": {},"visited": {"title": false,"email": false,"description": false,"length": false,"level": false,},"form": {"mutators": {}},"mutators": {}}2
https://github.com/final-form/final-form-arrays[3]
export default function Field({ control, ...props }) {control = typeof control === 'function' ? control : CONTROLS[control];const Field = control.isArray ? FieldArray : FinalForm.Field;return ;}function FieldRow({ control: Control, label, meta, input, fields, id, ...props}) {const error = meta.error || meta.submitError;const name = input ? input.name : fields.name;id = React.useMemo(() => id || uniqueId(`form-${name}-`), [id, name]);return ({label || capitalize(name)}:{error && {error}}{fields ? () : ()});}
function SpeakersInput({ fields }) {return ({fields.map((name, index) => ({index + 1}name={`${name}.firstName`}placeholder="First Name"/>name={`${name}.lastName`}placeholder="Last Name"/> { e.preventDefault();fields.remove(index); }}>remove))} { e.preventDefault(); fields.push(undefined); }}>add speaker);}SpeakersInput.isArray = true;
Form.Mutation = ({ children, mutation, input, update, initialValues, onSubmit }) => {return ({(mutate) => {const submit = async (values) => {const response = await mutate({variables: buildVariables(values, input);});const errors = extractErrors(response);if (errors) {return errors;}if (onSubmit) {onSubmit(extractObject(response));}};return ({children});}});};
https://github.com/RStankov/talks-code
⚛ basic of form handling in React0 build an extensible Form/Form.Field interface3 integrated with good battle tested form library ; Recap
⚛ basic of form handling in React0 build an extensible Form/Form.Field interface3 integrated with good battle tested form library< ...had some fun = ; Recap
Thanks >