Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

How not to hate your life when dealing with forms

How not to hate your life when dealing with forms

Radoslav Stankov

May 11, 2019
Tweet

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