How not to hate your life when dealing with forms

How not to hate your life when dealing with forms

7a0e72a6f55811246bb5d9a946fd2e49?s=128

Radoslav Stankov

May 11, 2019
Tweet

Transcript

  1. How not to hate your life when dealing with forms

    Radoslav Stankov 11/05/2019
  2. Radoslav Stankov @rstankov http://rstankov.com http://github.com/rstankov

  3. None
  4. None
  5. 
 Who is developer worst enemy? ! 


  6. " The designer 
 Who is developer worst enemy? !

  7. " The designer # The QA 
 Who is developer

    worst enemy? ! 

  8. " The designer # The QA $ The sys admin

    
 Who is developer worst enemy? ! 

  9. " The designer # The QA $ The sys admin

    % The project owner 
 Who is developer worst enemy? ! 

  10. " The designer # The QA $ The sys admin

    % The project owner & Santa 
 Who is developer worst enemy? ! 

  11. " The designer # The QA $ The sys admin

    % The project owner & Santa ' Forms 
 Who is developer worst enemy? ! 

  12. " The designer # The QA $ The sys admin

    % The project owner & Santa ( Form 
 Who is developer worst enemy? ! 

  13. None
  14. None
  15. Form Basics

  16. None
  17. None
  18. None
  19. <form> <h1>
 <label> <input /> </label>
 <input type="submit" /> </form>

  20. function SubmitTalkForm() { const [state, setState] = useState({}); const onSubmit

    = event => { event.preventDefault(); console.log(state); }; return ( <form onSubmit={onSubmit}> <div> <label htmlFor="title">Title:</label> <input id="title" type="text" name="title" value={state.title || ''} onChange={({ target }) => setState({ ...state, title: target.value }) } /> </div> <input type="submit" value="Submit" /> </form> ); }
  21. function SubmitTalkForm() { const [state, setState] = useState({}); const onSubmit

    = event => { event.preventDefault(); console.log(state); }; return ( <form onSubmit={onSubmit}> <div> <label htmlFor="title">Title:</label> <input id="title" type="text" name="title" value={state.title || ''} onChange={({ target }) => setState({ ...state, title: target.value }) } /> </div> <input type="submit" value="Submit" /> </form> ); }
  22. Input change function SubmitTalkForm() { const [state, setState] = useState({});

    const onSubmit = event => { event.preventDefault(); console.log(state); }; return ( <form onSubmit={onSubmit}> <div> <label htmlFor="title">Title:</label> <input id="title" type="text" name="title" value={state.title || ''} onChange={({ target }) => setState({ ...state, title: target.value }) } /> </div> <input type="submit" value="Submit" /> </form> ); }
  23. Input change 
 onChange setState function SubmitTalkForm() { const [state,

    setState] = useState({}); const onSubmit = event => { event.preventDefault(); console.log(state); }; return ( <form onSubmit={onSubmit}> <div> <label htmlFor="title">Title:</label> <input id="title" type="text" name="title" value={state.title || ''} onChange={({ target }) => setState({ ...state, title: target.value }) } /> </div> <input type="submit" value="Submit" /> </form> ); }
  24. Input change 
 onChange setState 
 render New value function

    SubmitTalkForm() { const [state, setState] = useState({}); const onSubmit = event => { event.preventDefault(); console.log(state); }; return ( <form onSubmit={onSubmit}> <div> <label htmlFor="title">Title:</label> <input id="title" type="text" name="title" value={state.title || ''} onChange={({ target }) => setState({ ...state, title: target.value }) } /> </div> <input type="submit" value="Submit" /> </form> ); }
  25. None
  26. Field Input Control Label

  27. Field Input Control Label ? Error Description

  28. <form> <h1>
 <label> <input /> </label>
 <input type="submit" /> </form>

  29. <form> <h1>
 <Field />
 <input type="submit" /> </form>

  30. function SubmitTalkForm() { const [state, setState] = useState({}); const onSubmit

    = event => { /* */ }; return ( <form onSubmit={onSubmit}> <Field id="title" name="title" value={state.title || ''} onChange={({ target }) => setState({ ...state, title: target.value })} /> <input type="submit" value="Submit" /> </form> ); } function Field({ label, name, ...inputProps }) { return ( <div> <label htmlFor={inputProps.id}>{label || capitalize(name)}:</label> <input type="text" name={name} {...inputProps} /> </div> ); }
  31. function SubmitTalkForm() { const [state, setState] = useState({}); const onSubmit

    = event => { /* */ }; return ( <form onSubmit={onSubmit}> <Field id="title" name="title" value={state.title || ''} onChange={({ target }) => setState({ ...state, title: target.value })} /> <input type="submit" value="Submit" /> </form> ); } function Field({ label, name, ...inputProps }) { return ( <div> <label htmlFor={inputProps.id}>{label || capitalize(name)}:</label> <input type="text" name={name} {...inputProps} /> </div> ); }
  32. 
 Form

  33. 
 Form Some Component

  34. 
 Form Some Component Some Other Component

  35. 
 Form Some Component Some Other Component 
 Field

  36. 
 Form Some Component Some Other Component 
 Field Context

  37. 
 Form Some Component Some Other Component 
 Field Context

    Context
  38. const FormContext = React.createContext(); function SubmitTalkForm() { const context =

    useState({}); const onSubmit = event => { /* */ }; return ( <FormContext.Provider value={context}> <form onSubmit={onSubmit}> <Field name="title" /> <input type="submit" value="submit" /> </form> </FormContext.Provider> ); } function Field({ label, name, ...inputProps }) { const [state, setState] = useContext(FormContext); return ( <div> <label htmlFor={name}>{label || capitalize(name)}: </label> <input type="text" id={name} name={name}
  39. const FormContext = React.createContext(); function SubmitTalkForm() { const context =

    useState({}); const onSubmit = event => { /* */ }; return ( <FormContext.Provider value={context}> <form onSubmit={onSubmit}> <Field name="title" /> <input type="submit" value="submit" /> </form> </FormContext.Provider> ); } function Field({ label, name, ...inputProps }) { const [state, setState] = useContext(FormContext); return ( <div> <label htmlFor={name}>{label || capitalize(name)}: </label> <input type="text" id={name} name={name}
  40. const FormContext = React.createContext(); function SubmitTalkForm() { const context =

    useState({}); const onSubmit = event => { /* */ }; return ( <FormContext.Provider value={context}> <form onSubmit={onSubmit}> <Field name="title" /> <input type="submit" value="submit" /> </form> </FormContext.Provider> ); } function Field({ label, name, ...inputProps }) { const [state, setState] = useContext(FormContext); return ( <div> <label htmlFor={name}>{label || capitalize(name)}: </label> <input type="text" id={name} name={name}
  41. return ( <FormContext.Provider value={context}> <form onSubmit={onSubmit}> <Field name="title" /> <input

    type="submit" value="submit" /> </form> </FormContext.Provider> ); } function Field({ label, name, ...inputProps }) { const [state, setState] = useContext(FormContext); return ( <div> <label htmlFor={name}>{label || capitalize(name)}: </label> <input type="text" id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value })} {...inputProps} /> </div> ); }
  42. <form> <h1>
 <Field />
 <input type="submit" /> </form>

  43. <Form> <h1>
 <Form.Field />
 <Form.Submit /> </Form>

  44. function SubmitTalkForm() { const onSubmit = values => { console.log(value);

    }; return ( <Form onSubmit={onSubmit}> <Form.Field name="title" /> <Form.Submit /> </Form> ); }
  45. const FormContext = React.createContext([{}, function() {}]); function Form({ onSubmit, initialValues,

    ...props }) { const context = useState(initialValues); function handleSubmit(e) { e.preventDefault(); onSubmit(context[0]); } return ( <FormContext.Provider value={context}> <form onSubmit={handleSubmit} {...props} /> </FormContext.Provider> ); } Form.Field = Field; Form.Submit = () => <input type="submit" value="submit" />;
  46. <Form> <h1>
 <Form.Field />
 <Form.Submit /> </Form>

  47. <Form> <h1>
 <Form.Field /> <Form.Field />
 <Form.Submit /> </Form>

  48. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Submit />

    </Form>
  49. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <input type="text" id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value }) } {...inputProps} /> </div> ); }
  50. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <input type={control || 'text'} id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value }) } {...inputProps} /> </div> ); }
  51. <Form> <h1>
 <Form.Field /> <Form.Field />
 <Form.Submit /> </Form>

  52. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field />
 <Form.Submit />

    </Form>
  53. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Submit /> </Form>
  54. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <input type={control || 'text'} id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value }) } {...inputProps} /> </div> ); }
  55. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); let Control = props => <input type={control || 'text'} {...props} />; if (control === 'textarea') { Control = props => <textarea {...props} />; } return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value })} {...inputProps} /> </div> ); }
  56. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); let Control = props => <input type={control || 'text'} {...props} />; if (control === 'textarea') { Control = props => <textarea {...props} />; } return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value })} {...inputProps} /> </div> ); }
  57. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field />
 <Form.Submit />

    </Form>
  58. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field /> <Form.Field />


    <Form.Submit /> </Form>
  59. const LENGTH_OPTIONS = [ { value: 15, label: '15 minutes'

    }, { value: 30, label: '30 minutes' }, { value: 45, label: '45 minutes' }, ]; <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description" control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Submit /> </Form>
  60. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); let Control = props => <input type={control || 'text'} {...props} />; if (control === 'textarea') { Control = props => <textarea {...props} />; } if (control === 'select') { Control = ({ options, ...props }) => ( <select {...props}> {options.map(({ label, value }, i) => ( <option key={i} value={value}> {label || value} </option> ))} </select> ); } return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name}
  61. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); let Control = props => <input type={control || 'text'} {...props} />; if (control === 'textarea') { Control = props => <textarea {...props} />; } if (control === 'select') { Control = ({ options, ...props }) => ( <select {...props}> {options.map(({ label, value }, i) => ( <option key={i} value={value}> {label || value} </option> ))} </select> ); } return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name}
  62. const CONTROLS = { undefined: props => <input type="text" {...props}

    />, text: props => <input type="text" {...props} />, email: props => <input type="email" {...props} />, textarea: props => <textarea {...props} />, select: ({ options, ...props }) => ( <select className="form-control" {...props}> {options.map(({ label, value }, i) => ( <option key={i} value={value}> {label || value} </option> ))} </select> ), };
 
 function Field({ label, name, control, ...inputProps }) { const [state, setState] = useContext(FormContext); const Control = CONTROLS[name]; return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name}
  63. const CONTROLS = { undefined: props => <input type="text" {...props}

    />, text: props => <input type="text" {...props} />, email: props => <input type="email" {...props} />, textarea: props => <textarea {...props} />, select: ({ options, ...props }) => ( <select className="form-control" {...props}> {options.map(({ label, value }, i) => ( <option key={i} value={value}> {label || value} </option> ))} </select> ), };
 
 function Field({ label, name, control, ...inputProps }) { const [state, setState] = useContext(FormContext); const Control = CONTROLS[name]; return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name}
  64. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field /> <Form.Field />


    <Form.Submit /> </Form>
  65. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field /> <Form.Field />


    <Form.Field />
 <Form.Submit /> </Form>
  66. const CONTROLS = { /* */ radioGroup: ({ value options,

    name, onChange, ...props }) => ( <ul> {options.map((option, value }, i) => ( <li key={i}> <label> <input type="radio" name={name} value={option.value} checked={option.value === value} onChange={onChange} {...props} /> {option.label || option.value} </label> </li> ))} </ul> ), };
  67. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field /> <Form.Field />


    <Form.Field />
 <Form.Submit /> </Form>
  68. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field /> <Form.Field />


    <Form.Field />
 <Form.Field />
 <Form.Submit /> </Form>
  69. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form>
  70. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); const Control = typeof control === 'function' ? control : CONTROLS[control]; return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value })} {...inputProps} /> </div> ); }
  71. function Field({ label, name, control, ...inputProps }) { const [state,

    setState] = useContext(FormContext); const Control = typeof control === 'function' ? control : CONTROLS[control]; return ( <div> <label htmlFor={name}>{label || capitalize(name)}:</label> <Control id={name} name={name} value={state[name] || ''} onChange={({ target }) => setState({ ...state, [name]: target.value })} {...inputProps} /> </div> ); }
  72. None
  73. None
  74. None
  75. Init

  76. Init Loading

  77. Init Loading Success

  78. Init Loading Success Errors

  79. Submit Server Success Errors

  80. remoteCall Server { result: 'ok' } { errors: {…} }

  81. remoteCall Server { result: 'ok' } { 
 errors: {

    field1: 'error1',
 field2: 'error2'
 }
 }
  82. { 
 errors: { field1: 'error1',
 field2: 'error2'
 }
 }

  83. { 
 errors: { field1: 'error1',
 field2: 'error2'
 }
 }

  84. None
  85. • simple form interface • extensible fields • standardized form

    layout 
 ) So far... 

  86. • validations - client / server side • protection again

    double submit • nested fields • handle submit • ... more 
 ! What is missing? 

  87. Libraries

  88. * 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/formik 0 https://github.com/final-form/final-form 
 1 Libraries 

  89. None
  90. None
  91. None
  92. None
  93. * 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/formik 0 https://github.com/final-form/final-form Libraries
  94. Final Form Formik

  95. None
  96. None
  97. 2

  98. 2

  99. https://github.com/final-form/ 3

  100. ✅ Zero dependencies * ✅ Framework agnostic ✅ Opt-in subscriptions

    - only update on the state you need! ✅ 5 7.7k gzipped 5 - with React components
  101. import * as FinalForm from 'react-final-form'; const SubmitTalkForm = ()

    => ( <FinalForm.Form onSubmit={onSubmit} validate={validate} render={({ handleSubmit, pristine, invalid }) => ( <form onSubmit={handleSubmit}> <div> <label>Title</label> <FinalForm.Field name="title" component="input" /> </div> <FinalForm.Field name="email"> {({ input, meta }) => ( <div> <label>Email</label> <input type="email" {...input} /> {meta.touched && meta.error && <span>{meta.error}</span>} </div> )} </FinalForm.Field> <FinalForm.Field name="description" render={({ input, meta }) => (
  102. validate={validate} render={({ handleSubmit, pristine, invalid }) => ( <form onSubmit={handleSubmit}>

    <div> <label>Title</label> <FinalForm.Field name="title" component="input" /> </div> <FinalForm.Field name="email"> {({ input, meta }) => ( <div> <label>Email</label> <input type="email" {...input} /> {meta.touched && meta.error && <span>{meta.error}</span>} </div> )} </FinalForm.Field> <FinalForm.Field name="description" render={({ input, meta }) => ( <div> <label>Description</label> <textarea {...input} /> {meta.touched && meta.error && <span>{meta.error}</span>} </div> )} />
  103. </div> )} </FinalForm.Field> <FinalForm.Field name="description" render={({ input, meta }) =>

    ( <div> <label>Description</label> <textarea {...input} /> {meta.touched && meta.error && <span>{meta.error}</span>} </div> )} /> <div> <label>Length</label> <FinalForm.Field name="" component={CONTROLS.select} options={LENGTH_O </div> <div> <label>Level</label> <FinalForm.Field name="" component={CONTROLS.radioGroup} options={LEVEL_OPTIONS} /> </div>
  104. /> <div> <label>Length</label> <FinalForm.Field name="" component={CONTROLS.select} options={LENGTH_O </div> <div> <label>Level</label>

    <FinalForm.Field name="" component={CONTROLS.radioGroup} options={LEVEL_OPTIONS} /> </div> <div> <label>Speakers</label> <FinalForm.Field name="speakers" component={SpeakersInput} /> </div> <button type="submit" disabled={pristine || invalid}> Submit </button> </form> )} /> );
  105. /> <div> <label>Length</label> <FinalForm.Field name="" component={CONTROLS.select} options={LENGTH_O </div> <div> <label>Level</label>

    <FinalForm.Field name="" component={CONTROLS.radioGroup} options={LEVEL_OPTIONS} /> </div> <div> <label>Speakers</label> <FinalForm.Field name="speakers" component={SpeakersInput} /> </div> <button type="submit" disabled={pristine || invalid}> Submit </button> </form> )} /> ); 6
  106. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form>
  107. None
  108. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form> import * as FinalForm from 'react-final-form'; const SubmitTalkForm = () => ( <FinalForm.Form onSubmit={onSubmit} validate={validate} render={({ handleSubmit, pristine, invalid }) => ( <form onSubmit={handleSubmit}> <div> <label>Title</label> <FinalForm.Field name="title" component="input" /> </div> <FinalForm.Field name="email"> {({ input, meta }) => ( <div> <label>Email</label> <input type="email" {...input} /> {meta.touched && meta.error && <span>{meta.error}</span>} </div> )} </FinalForm.Field> <FinalForm.Field name="description" render={({ input, meta }) => ( <div> <label>Description</label> <textarea {...input} /> {meta.touched && meta.error && <span>{meta.error}</span>} </div> )} /> <div> <label>Length</label> <FinalForm.Field name="" component={CONTROLS.select} options={LENGTH_OPTIONS} </div> <div> <label>Level</label> <FinalForm.Field name="" component={CONTROLS.radioGroup} options={LEVEL_OPTIONS} /> </div> <div> <label>Speakers</label> <FinalForm.Field name="speakers" component={SpeakersInput} /> </div> <button type="submit" disabled={pristine || invalid}> Submit </button> </form> )} /> );
  109. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form>
  110. function Form({ initialValues, onSubmit, children }) { return ( <FinalForm.Form

    onSubmit={onSubmit} initialValues={initialValues || {}}> {({ handleSubmit }) => <form onSubmit={handleSubmit}>{children}</form>} </FinalForm.Form> ); }
  111. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form>
  112. export default function Field({ control, ...props }) { control =

    typeof control === 'function' ? control : CONTROLS[control]; return <FinalForm.Field control={control} component={FieldRow} {...props} />; } function FieldRow({ control: Control, label, meta, input, fields, ...inputProps }) { const error = meta.error || meta.submitError; const name = input.name; return ( <div> <div> <label htmlFor={name}>{label || capitalize(name)}:</label> {error && <span>{error}</span>} </div> <Control id={name} {...input} {...inputProps} /> </div> ); }
  113. 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 ( <div> <div> <label htmlFor={id}>{label || capitalize(name)}:</label> {error && <span>{error}</span>} </div> <Control id={id} {...input} {...inputProps} /> </div> ); }
  114. 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 ( <div> <div> <label htmlFor={id}>{label || capitalize(name)}:</label> {error && <span>{error}</span>} </div> <Control id={id} {...input} {...inputProps} /> </div> ); }
  115. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form>
  116. Form.Submit = () => ( <FormSpy> {form => ( <React.Fragment>

    <input 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…'} </React.Fragment> )} </FormSpy> );
  117. Form.Submit = () => ( <FormSpy> {form => ( <React.Fragment>

    <input 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…'} </React.Fragment> )} </FormSpy> );
  118. Form.Submit = () => ( <FormSpy> {form => ( <React.Fragment>

    <input 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…'} </React.Fragment> )} </FormSpy> );
  119. <FormSpy> {form => console.log(form)} </FormSpy> { "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": {} }
  120. <FormSpy> {form => console.log(form)} </FormSpy> { "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
  121. None
  122. None
  123. https://github.com/final-form/final-form-arrays [3]

  124. export default function Field({ control, ...props }) { control =

    typeof control === 'function' ? control : CONTROLS[control]; const Field = control.isArray ? FieldArray : FinalForm.Field; return <Field control={control} component={FieldRow} {...props} />; } 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 ( <div> <div> <label htmlFor={id}>{label || capitalize(name)}:</label> {error && <span>{error}</span>} </div> {fields ? ( <Control fields={fields} id={id} {...props} /> ) : ( <Control id={id} {...input} {...props} /> )} </div> ); }
  125. export default function Field({ control, ...props }) { control =

    typeof control === 'function' ? control : CONTROLS[control]; const Field = control.isArray ? FieldArray : FinalForm.Field; return <Field control={control} component={FieldRow} {...props} />; } 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 ( <div> <div> <label htmlFor={id}>{label || capitalize(name)}:</label> {error && <span>{error}</span>} </div> {fields ? ( <Control fields={fields} id={id} {...props} /> ) : ( <Control id={id} {...input} {...props} /> )} </div> ); }
  126. function SpeakersInput({ fields }) { return ( <React.Fragment> {fields.map((name, index)

    => ( <div key={name}> {index + 1} <Form.Input name={`${name}.firstName`} placeholder="First Name" /> <Form.Input name={`${name}.lastName`} placeholder="Last Name" /> <button onClick={e => { e.preventDefault();fields.remove(index); }}> remove </button> </div> ))} <button onClick={e => { e.preventDefault(); fields.push(undefined); }}> add speaker </button> </React.Fragment> ); } SpeakersInput.isArray = true;
  127. None
  128. <Form> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field name="description"

    control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form>
  129. <Form.Mutation onSubmit={onComplete}> <Form.Field name="title" /> <Form.Field name="email" control="email" /> <Form.Field

    name="description" control="textarea" /> <Form.Field name="length" control="select" options={LENGTH_OPTIONS} /> <Form.Field name="level" control="radioGroup" options={LEVEL_OPTIONS} /> <Form.Field name="speakers" control={SpeakersInput} /> <Form.Submit /> </Form.Mutation>
  130. Form.Mutation = ({ children, mutation, input, update, initialValues, onSubmit })

    => { return ( <ApolloMutation mutation={mutation} update={update}> {(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 ( <Form initialValues={initialValues} onSubmit={submit}> {children} </Form> ); }} </ApolloMutation> ); };
  131. https://github.com/RStankov/talks-code

  132. None
  133. None
  134. ⚛ basic of form handling in React 0 build an

    extensible Form/Form.Field interface 3 integrated with good battle tested form library 
 ; Recap 

  135. ⚛ basic of form handling in React 0 build an

    extensible Form/Form.Field interface 3 integrated with good battle tested form library < ...had some fun = 
 ; Recap 

  136. None
  137. None
  138. None
  139. Thanks >

  140. None