Upgrade to Pro — share decks privately, control downloads, hide ads and more …

How not to hate your life when dealing with forms

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →

How not to hate your life when dealing with forms

Avatar for Radoslav Stankov

Radoslav Stankov

May 11, 2019

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

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

    
 Who is developer worst enemy? ! 

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

    % The project owner 
 Who is developer worst enemy? ! 

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

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

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

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

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

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

  6. 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> ); }
  7. 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> ); }
  8. 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> ); }
  9. 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> ); }
  10. 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> ); }
  11. 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> ); }
  12. 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> ); }
  13. 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}
  14. 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}
  15. 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}
  16. 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> ); }
  17. function SubmitTalkForm() { const onSubmit = values => { console.log(value);

    }; return ( <Form onSubmit={onSubmit}> <Form.Field name="title" /> <Form.Submit /> </Form> ); }
  18. 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" />;
  19. 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> ); }
  20. 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> ); }
  21. 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> ); }
  22. 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> ); }
  23. 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> ); }
  24. 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>
  25. 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}
  26. 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}
  27. 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}
  28. 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}
  29. 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> ), };
  30. <Form> <h1>
 <Form.Field /> <Form.Field /> <Form.Field /> <Form.Field />


    <Form.Field />
 <Form.Field />
 <Form.Submit /> </Form>
  31. <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>
  32. 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> ); }
  33. 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> ); }
  34. remoteCall Server { result: 'ok' } { 
 errors: {

    field1: 'error1',
 field2: 'error2'
 }
 }
  35. • validations - client / server side • protection again

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

  36. 2

  37. 2

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

    - only update on the state you need! ✅ 5 7.7k gzipped 5 - with React components
  39. 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 }) => (
  40. 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> )} />
  41. </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>
  42. /> <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> )} /> );
  43. /> <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
  44. <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>
  45. <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> )} /> );
  46. <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>
  47. function Form({ initialValues, onSubmit, children }) { return ( <FinalForm.Form

    onSubmit={onSubmit} initialValues={initialValues || {}}> {({ handleSubmit }) => <form onSubmit={handleSubmit}>{children}</form>} </FinalForm.Form> ); }
  48. <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>
  49. 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> ); }
  50. 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> ); }
  51. 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> ); }
  52. <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>
  53. 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> );
  54. 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> );
  55. 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> );
  56. <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": {} }
  57. <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
  58. 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> ); }
  59. 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> ); }
  60. 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;
  61. <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>
  62. <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>
  63. 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> ); };
  64. ⚛ basic of form handling in React 0 build an

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

  65. ⚛ 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