$30 off During Our Annual Pro Sale. View Details »

Forms with React

Forms with React

Tutorial on how to handle forms with React, presented at React.Sofia

Radoslav Stankov

March 07, 2017
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. Forms with React Radoslav Stankov 07/03/2017

  2. Radoslav Stankov @rstankov http://rstankov.com http://github.com/rstankov

  3. None
  4. None
  5. ! 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

    Libraries
  6. None
  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. None
  14. None
  15. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  16. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  17. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  18. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  19. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  20. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  21. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  22. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  23. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div>
 <input type="submit" /> </form>
  24. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label>

    <input /> </div>
 <input type="submit" /> </form>
  25. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label>

    <input /> </div>
 <input type="submit" /> </form>
  26. export default class SubmissionForm extends React.Component { render() { return

    ( <form method="post" action="/cfp/submission"> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input type="text" id="speakerName" name="speakerName" defaultValue="" /> </div> <div> <label htmlFor="speakerEmail">Email: </label> <input type="email" id="speakerEmail" name="speakerEmail" defaultValue="" /> </div> <input type="submit" value="Submit" /> </form> ); } }
  27. export default class SubmissionForm extends React.Component { render() { return

    ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input type="text" id="speakerName" name="speakerName" defaultValue="" /> </div> <div> <label htmlFor="speakerEmail">Email: </label> <input type="email" id="speakerEmail" name="speakerEmail" defaultValue="" /> </div> <input type="submit" value="Submit" /> </form> ); } handleSubmit = (e) => { e.preventDefault(); const allValues = Array.from(e.target.elements).reduce((data, input) => { console.log(input); data[input.name] = input.value; return data; }, {}); remoteCall({ speakerName: allValues.speakerName, speakerEmail: allValues.speakerEmail, }); }; }
  28. export default class SubmissionForm extends React.Component { render() { return

    ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input ref={(ref) => this.speakerName = ref} type="text" id="speakerName" name="speakerName" defaultV </div> <div> <label htmlFor="speakerEmail">Email: </label> <input ref={(ref) => this.speakerEmail = ref} type="email" id="speakerEmail" name="speakerEmail" defa </div> <input type="submit" value="Submit" /> </form> ); } handleSubmit = (e) => { e.preventDefault(); remoteCall({ speakerName: this.speakerName.value, speakerEmail: this.speakerEmail.value, }); }; }
  29. export default class SubmissionForm extends React.Component { render() { return

    ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input ref={(ref) => this.speakerName = ref} type="text" id="speakerName" name="speakerName" defaultV </div> <div> <label htmlFor="speakerEmail">Email: </label> <input ref={(ref) => this.speakerEmail = ref} type="email" id="speakerEmail" name="speakerEmail" defa </div> <input type="submit" value="Submit" /> </form> ); } handleSubmit = (e) => { e.preventDefault(); remoteCall({ speakerName: this.speakerName.value, speakerEmail: this.speakerEmail.value, }); }; }
  30. export default class SubmissionForm extends React.Component { render() { return

    ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input ref={(ref) => this.speakerName = ref} type="text" id="speakerName" name="speakerName" defaultV </div> <div> <label htmlFor="speakerEmail">Email: </label> <input ref={(ref) => this.speakerEmail = ref} type="email" id="speakerEmail" name="speakerEmail" defa </div> <input type="submit" value="Submit" /> </form> ); } handleSubmit = (e) => { e.preventDefault(); remoteCall({ speakerName: this.speakerName.value, speakerEmail: this.speakerEmail.value, }); }; } &
  31. Uncontrolled Components • fast • simple • integration with external

    libraries • no much control • gets messy quite easy
  32. Controlled Components • you have more control • easier to

    reason about • needed for complex form interactions • recommend by react documentation
  33. class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } }
  34. class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } } Input change
  35. class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } } Input change 
 handleChange setState
  36. class ExampleForm extends React.Component { state = { value: ''

    } handleChange = (event) => { this.setState({value: event.target.value}); } handleSubmit = (event) => { e.preventDefault(); remoteCall(this.target.value)/ }; render() { return ( <form onSubmit={this.handleSubmit}> <input type="text" value={this.state.value} onChange={this.handleChange} /> <input type="submit" value="Submit" /> </form> ); } } Input change 
 handleChange setState 
 render New value
  37. <form> <h2>
 <div>
 <label> <input /> </div> <div>
 <label> <input

    /> </div>
 <input type="submit" /> </form>
  38. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', }, }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input value={this.state.fields.speakerName} onChange={this.handleChange} type="text" id="speakerName </div> <div> <label htmlFor="speakerEmail">Email: </label> <input value={this.state.fields.speakerEmail} onChange={this.handleChange} type="email" id="speakerEm </div> <input type="submit" value="Submit" /> </form> ); } handleChange = (e) => { const name = e.target.name; const value = e.target.value; this.setState({ fields: {
 ...this.state.fields, [name]: value } }); }; handleSubmit = (e) => { e.preventDefault(); remoteCall(this.state.fields); }; }
  39. None
  40. None
  41. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', }, isSumitting: false, }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input value={this.state.fields.speakerName} onChange={this.handleChange} type="text" id="speakerName </div> <div> <label htmlFor="speakerEmail">Email: </label> <input value={this.state.fields.speakerEmail} onChange={this.handleChange} type="email" id="speakerEm </div> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true }); await remoteCall(this.state.fields); }; }
  42. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', }, isSumitting: false, }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input value={this.state.fields.speakerName} onChange={this.handleChange} type="text" id="speakerName </div> <div> <label htmlFor="speakerEmail">Email: </label> <input value={this.state.fields.speakerEmail} onChange={this.handleChange} type="email" id="speakerEm </div> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true }); await remoteCall(this.state.fields); }; }
  43. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div> <h2> <div>
 <label /> <input /> </div>
 <input type="submit" /> </form>
  44. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div> <h2> <div>
 <label /> <input /> </div>
 <input type="submit" /> </form>
  45. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div> <h2> <div>
 <label /> <input /> </div>
 <input type="submit" /> </form>
  46. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div> <h2> <div>
 <label /> <input /> </div>
 <input type="submit" /> </form>
  47. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div> <h2> <div>
 <label /> <input /> </div>
 <input type="submit" /> </form>
  48. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div> <h2> <div>
 <label /> <input /> </div>
 <input type="submit" /> </form>
  49. <form> <h2>
 <div>
 <label /> <input /> </div> <div>
 <label

    /> <input /> </div> <h2> <div>
 <label /> <input /> </div>
 <input type="submit" /> </form>
  50. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', }, isSumitting: false, }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input value={this.state.fields.speakerName} onChange={this.handleChange} type="text" id="speakerName </div> <div> <label htmlFor="speakerEmail">Email: </label> <input value={this.state.fields.speakerEmail} onChange={this.handleChange} type="email" id="speakerEm </div> <h2>Talk</h2> <div> <label htmlFor="talkTitle">Title: </label> <input type="text" id="talkTitle" name="talkTitle" value={this.state.fields.talkTitle} onChange={this </div> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = (e) => { /* … */ }; }
  51. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', }, isSumitting: false, }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input value={this.state.fields.speakerName} onChange={this.handleChange} type="text" id="speakerName </div> <div> <label htmlFor="speakerEmail">Email: </label> <input value={this.state.fields.speakerEmail} onChange={this.handleChange} type="email" id="speakerEm </div> <h2>Talk</h2> <div> <label htmlFor="talkTitle">Title: </label> <input type="text" id="talkTitle" name="talkTitle" value={this.state.fields.talkTitle} onChange={this </div> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = (e) => { /* … */ }; }
  52. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', }, isSumitting: false, }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <div> <label htmlFor="speakerName">Name: </label> <input value={this.state.fields.speakerName} onChange={this.handleChange} type="text" id="speakerName </div> <div> <label htmlFor="speakerEmail">Email: </label> <input value={this.state.fields.speakerEmail} onChange={this.handleChange} type="email" id="speakerEm </div> <h2>Talk</h2> <div> <label htmlFor="talkTitle">Title: </label> <input type="text" id="talkTitle" name="talkTitle" value={this.state.fields.talkTitle} onChange={this </div> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = (e) => { /* … */ }; }
  53. let Field = ({ name, type, label, ...props }) =>

    { return ( <div> <label htmlFor={name}>{label}: </label> <input type={type || 'text'} id={name} name={name} {...props} /> </div> ); };
  54. export default class SubmissionForm extends React.Component { state = {

    /* … */ }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" value={this.state.fields.speakerName} onChange={thi <Field name="speakerEmail" label="Email" type="email" value={this.state.fields.speakerEmai <h2>Talk</h2> <Field name="talkTitle" label="Title" value={this.state.fields.talkTitle} onChange={this.h <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = (e) => { /* … */ }; }
  55. export default class SubmissionForm extends React.Component { state = {

    /* … */ }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" value={this.state.fields.speakerName} onChange={thi <Field name="speakerEmail" label="Email" type="email" value={this.state.fields.speakerEmai <h2>Talk</h2> <Field name="talkTitle" label="Title" value={this.state.fields.talkTitle} onChange={this.h <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = (e) => { /* … */ }; }
  56. <form> <h2>
 <Field />
 <Field />
 <h2> <Field />
 <submit

    /> </form>
  57. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    />
 <submit /> </form>
  58. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    />
 <submit /> </form>
  59. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    />
 <submit /> </form> '
  60. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    />
 <submit /> </form> (
  61. let Field = ({ name, input, label, ...props }) =>

    { if (input === 'textarea') { return ( <div> <label htmlFor={name}>{label}: </label> <textarea id={name} name={name} {...props} /> </div> ); } return ( <div> <label htmlFor={name}>{label}: </label> <input type={input || 'text'} id={name} name={name} {...props} /> </div> ); };
  62. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', talkDescription: '', }, isSumitting: false, }; render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" value={this.state.fields.speakerName} onChange={thi <Field name="speakerEmail" label="Email" input="email" value={this.state.fields.speakerEma <h2>Talk</h2> <Field name="talkTitle" label="Title" value={this.state.fields.talkTitle} onChange={this.h <Field name="talkDescription" label="Description" input="textarea" value={this.state.field <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form> ); } handleChange = (e) => { /* … */ }; handleSubmit = (e) => { /* … */ }; }
  63. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    />
 <submit /> </form>
  64. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    /> <Field />
 <submit /> </form>
  65. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    /> <Field />
 <submit /> </form>
  66. const LENGTH_OPTIONS = [ {value: 15, label: '15 minutes'}, {value:

    30, label: '30 minutes'}, {value: 45, label: '45 minutes'}, ];
  67. <Field name="talkLength" label="Length" value={this.state.fields.talkLength} onChange={this.handleChange} input="select" options={LENGTH_OPTIONS} />

  68. let Select = ({ options, ...props }) => ( <select

    {...props}> {options.map(({ label, value }, i) => ( <option key={i} value={value}>{label || value}</option> ))} </select> );
  69. let Field = ({ name, input, label, ...props }) =>

    { if (input === 'textarea') { return ( <div> <label htmlFor={name}>{label}: </label> <textarea id={name} name={name} {...props} /> </div> ); } if (input === 'select') { return ( <div> <label htmlFor={name}>{label}: </label> <Select id={name} name={name} {...props} /> </div> ); } return ( <div> <label htmlFor={name}>{label}: </label> <input type={input || 'text'} id={name} name={name} {...props} /> </div> ); };
  70. let Input = ({ ...props }) => ( <input {...props}

    /> );
  71. const INPUTS = { 'textarea': Textarea, 'select': Select, 'text': Input,

    }; let Field = ({ name, input = 'text', label, ...props }) => { const Component = INPUTS[input] || Input; const inputProps = Component === Input ? { type: input, ...props } : props; return ( <div> <label htmlFor={name}>{label}: </label> <Component id={name} name={name} {...inputProps} /> </div> ); };
  72. const INPUTS = { 'textarea': Textarea, 'select': Select, 'text': Input,

    }; let Field = ({ name, input = 'text', label, ...props }) => { const Component = typeof input === 'function' ? input : INPUTS[input] || Input; const inputProps = Component === Input ? { type: input, ...props } : props; return ( <div> <label htmlFor={name}>{label}: </label> <Component id={name} name={name} {...inputProps} /> </div> ); };
  73. const INPUTS = { 'textarea': Textarea, 'select': Select, 'text': Input,

    }; let Field = ({ name, input = 'text', label, ...props }) => { const Component = typeof input === 'function' ? input : INPUTS[input] || Input; const inputProps = Component === Input ? { type: input, ...props } : props; return ( <div> <label htmlFor={name}>{label}: </label> <Component id={name} name={name} {...inputProps} /> </div> ); };
  74. <Field input={MyAwesomeCustomInput} />

  75. None
  76. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    /> <Field />
 <submit /> </form>
  77. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    /> <Field />
 <Field />
 <submit /> </form>
  78. <form> <h2>
 <Field />
 <Field />
 <h2> <Field /> <Field

    /> <Field />
 <Field />
 <submit /> </form>
  79. let RadioGroup = ({ value: fValue, options, name, id, ...props

    }) => ( <ul> {options.map(({ label, value }, i) => ( <li key={i}> <label> <input type="radio" name={name} value={value} checked={value === fValue} {...props} /> {label || value} </label> </li> ))} </ul> );
  80. const INPUTS = { 'textarea': Textarea, 'select': Select, 'text': Input,

    'radioGroup': RadioGroup, };
  81. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" value={this.state.fields.speakerName} onChange={this.hand <Field name="speakerEmail"

    label="Email" input="email" value={this.state.fields.speakerEmail} on <h2>Talk</h2> <Field name="talkTitle" label="Title" value={this.state.fields.talkTitle} onChange={this.handleI <Field name="talkDescription" label="Description" input="textarea" value={this.state.fields.talk <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} value={this.stat <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} value={th <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form>
  82. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" value={this.state.fields.speakerName} onChange={this.hand <Field name="speakerEmail"

    label="Email" input="email" value={this.state.fields.speakerEmail} on <h2>Talk</h2> <Field name="talkTitle" label="Title" value={this.state.fields.talkTitle} onChange={this.handleI <Field name="talkDescription" label="Description" input="textarea" value={this.state.fields.talk <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} value={this.stat <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} value={th <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form>
  83. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" value={this.state.fields.speakerName} onChange={this.hand <Field name="speakerEmail"

    label="Email" input="email" value={this.state.fields.speakerEmail} on <h2>Talk</h2> <Field name="talkTitle" label="Title" value={this.state.fields.talkTitle} onChange={this.handleI <Field name="talkDescription" label="Description" input="textarea" value={this.state.fields.talk <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} value={this.stat <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} value={th <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form>
  84. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email"

    input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form>
  85. 
 Component

  86. 
 Component Child Component

  87. 
 Component Child Component Child Child Component

  88. 
 Component Child Component Child Child Component 
 Child Child

    … Child Component
  89. 
 Component Child Component Child Child Component 
 Child Child

    … Child Component Context
  90. 
 Component Child Component Child Child Component 
 Child Child

    … Child Component Context Context
  91. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', talkDescription: '', talkLength: '15', }, isSumitting: false, }; static childContextTypes = { formState: React.PropTypes.object.isRequired, formHandleChange: React.PropTypes.func.isRequired, }; getChildContext() { return { formState: this.state, formHandleChange: this.handleChange, }; } render() { /* … */ }; handleChange = (e) => { /* … */ }; handleSubmit = (e) => { /* … */ }; }
  92. let Field = ({ name, input, label, ...props }, {

    formState: { fields }, formHandleChange }) => { const value = fields[name]; const Component = typeof input === 'function' ? input : INPUTS[input] || Input; const inputProps = Component === Input ? { type: input, ...props } : props; return ( <div> <label htmlFor={name}>{label}: </label> <Component id={name} name={name} value={value} onChange={formHandleChange} {...inputProps} /> </div> ); }; Field.contextTypes = { formState: React.PropTypes.object.isRequired, formHandleChange: React.PropTypes.func.isRequired, };
  93. let Field = ({ name, input, label, ...props }, {

    formState: { fields }, formHandleChange }) => { const value = fields[name]; const Component = typeof input === 'function' ? input : INPUTS[input] || Input; const inputProps = Component === Input ? { type: input, ...props } : props; return ( <div> <label htmlFor={name}>{label}: </label> <Component id={name} name={name} value={value} onChange={formHandleChange} {...inputProps} /> </div> ); }; Field.contextTypes = { formState: React.PropTypes.object.isRequired, formHandleChange: React.PropTypes.func.isRequired, };
  94. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email"

    input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form>
  95. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email"

    input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <input type="submit" value="Submit" disabled={this.state.isSubmitting} /> </form>
  96. let SubmitButton = ({ children }, { formState: { isSubmitting

    }}) => ( <input type="submit" value={isSubmitting ? 'Submitting...' : children} disabled={isSubmitting} /> ); SubmitButton.contextTypes = { formState: React.PropTypes.object.isRequired, };
  97. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email"

    input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <SubmitButton>Submit</SubmitButton> </form>
  98. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', talkDescription: '', talkLength: '15', }, isSumitting: false, }; static childContextTypes = { formState: React.PropTypes.object.isRequired, formHandleChange: React.PropTypes.func.isRequired, }; getChildContext() { return { formState: this.state, formHandleChange: this.handleChange, }; } render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email" input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <SubmitButton>Submit</SubmitButton> </form>
  99. <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email"

    input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <SubmitButton>Submit</SubmitButton> </form> ); } handleChange = (e) => { const name = e.target.name; const value = e.target.value; const fields = { ...this.state.fields, [name]: value }; this.setState({ fields }); }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true }); await remoteCall(this.state.fields); }; }
  100. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', talkDescription: '', talkLength: '15', }, isSumitting: false, }; static childContextTypes = { formState: React.PropTypes.object.isRequired, formHandleChange: React.PropTypes.func.isRequired, }; getChildContext() { return { formState: this.state, formHandleChange: this.handleChange, }; } render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email" input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <SubmitButton>Submit</SubmitButton> </form> ); } handleChange = (e) => { const name = e.target.name; const value = e.target.value; const fields = { ...this.state.fields, [name]: value }; this.setState({ fields }); }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true }); await remoteCall(this.state.fields); }; }
  101. export default class SubmissionForm extends React.Component { state = {

    fields: { speakerName: '', speakerEmail: '', talkTitle: '', talkDescription: '', talkLength: '15', }, isSumitting: false, }; static childContextTypes = { formState: React.PropTypes.object.isRequired, formHandleChange: React.PropTypes.func.isRequired, }; getChildContext() { return { formState: this.state, formHandleChange: this.handleChange, }; } render() { return ( <form onSubmit={this.handleSubmit}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email" input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <SubmitButton>Submit</SubmitButton> </form> ); } handleChange = (e) => { const name = e.target.name; const value = e.target.value; const fields = { ...this.state.fields, [name]: value }; this.setState({ fields }); }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true }); await remoteCall(this.state.fields); }; }
  102. class Form extends React.Component { state = { fields: this.props.fields,

    isSumitting: false, }; static childContextTypes = { formState: React.PropTypes.object.isRequired, formHandleChange: React.PropTypes.func.isRequired, }; getChildContext() { return { formState: this.state, formHandleChange: this.handleChange, }; } render() { return ( <form onSubmit={this.handleSubmit}> {this.props.children} </form> ); } handleInputChange = (e) => { const name = e.target.name; const value = e.target.value; const fields = { ...this.state.fields, [name]: value }; this.setState({ fields }); };
  103. return { formState: this.state, formHandleChange: this.handleChange, }; } render() {

    return ( <form onSubmit={this.handleSubmit}> {this.props.children} </form> ); } handleInputChange = (e) => { const name = e.target.name; const value = e.target.value; const fields = { ...this.state.fields, [name]: value }; this.setState({ fields }); }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true }); await this.props.onSubmit(this.state.fields); }; }
  104. const FIELDS = { speakerName: '', speakerEmail: '', talkTitle: '',

    talkDescription: '', talkLength: '15', }; export default class SubmissionForm extends React.Component { render() { return ( <Form fields={FIELDS} onSubmit={remoteCall}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email" input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" input="textarea" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <SubmitButton>Submit</SubmitButton> </Form> ); } }
  105. const FIELDS = { speakerName: '', speakerEmail: '', talkTitle: '',

    talkDescription: '', talkLength: '15', }; let SubmissionForm = () => ( <Form fields={FIELDS} onSubmit={remoteCall}> <h2>Speaker</h2> <Field name="speakerName" label="Name" /> <Field name="speakerEmail" label="Email" input="email" /> <h2>Talk</h2> <Field name="talkTitle" label="Title" /> <Field name="talkDescription" label="Description" input="textarea" /> <Field name="talkLength" label="Length" input="select" options={LENGTH_OPTIONS} /> <Field name="notifyVia" label="Notify me via" input="radioGroup" options={VIA_OPTIONS} /> <SubmitButton>Submit</SubmitButton> </Form> );
  106. None
  107. None
  108. None
  109. Submit

  110. Submit Server

  111. Submit Server Success

  112. Submit Server Success Errors

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

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

    field1: [ 'error1', 'error2 field2: [ 'error1']
 }
 }
  115. { 
 errors: { field1: [ 'error1', 'error2' ],
 field2:

    [ 'error1']
 }
 }
  116. class Form extends React.Component { state = { fields: this.props.fields,

    isSumitting: false, }; static childContextTypes = { /* … */ }; getChildContext() = { /* … */ } render() = { /* … */ } handleInputChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } await this.props.onSubmit(this.state.fields); }; }
  117. class Form extends React.Component { state = { fields: this.props.fields,

    errors: {}, isSumitting: false, }; static childContextTypes = { /* … */ }; getChildContext() = { /* … */ } render() = { /* … */ } handleInputChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true, errors: {} }); const { errors } = await this.props.onSubmit(this.state.fields); this.setState({ isSubmitting: false, errors: errors || {} }); }; }
  118. class Form extends React.Component { state = { fields: this.props.fields,

    errors: {}, isSumitting: false, }; static childContextTypes = { /* … */ }; getChildContext() = { /* … */ } render() = { /* … */ } handleInputChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true, errors: {} }); const { errors } = await this.props.onSubmit(this.state.fields); this.setState({ isSubmitting: false, errors: errors || {} }); }; }
  119. let Field = ({ name, input, label, ...props }, {

    formState: { fields, errors }, formHandleInputChange }) const value = fields[name]; const error = errors[name] && errors[name][0]; const Component = typeof input === 'function' ? input : INPUTS[input] || Input; const inputProps = Component === Input ? { type: input, ...props } : props; return ( <div> <label htmlFor={name}>{label}: </label> <Component id={name} name={name} value={value} onChange={formHandleInputChange} {...inputProps} /> { error && <strong>{error}</strong>} </div> ); };
  120. let Field = ({ name, input, label, ...props }, {

    formState: { fields, errors }, formHandleInputChange }) const value = fields[name]; const error = errors[name] && errors[name][0]; const Component = typeof input === 'function' ? input : INPUTS[input] || Input; const inputProps = Component === Input ? { type: input, ...props } : props; return ( <div> <label htmlFor={name}>{label}: </label> <Component id={name} name={name} value={value} onChange={formHandleInputChange} {...inputProps} /> { error && <strong>{error}</strong>} </div> ); };
  121. class Form extends React.Component { state = { /* …

    */ }; static childContextTypes = { /* … */ }; getChildContext() = { /* … */ } render() = { /* … */ } handleInputChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true, errors: {} }); const { errors } = await this.props.onSubmit(this.state.fields); this.setState({ isSubmitting: false, errors: errors || {} }); }; }
  122. class Form extends React.Component { state = { /* …

    */ }; static childContextTypes = { /* … */ }; getChildContext() = { /* … */ } isUnmounted: boolean = false; componentWillUnmount() { this.isUnmounted = true; } render() = { /* … */ } handleInputChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true, errors: {} }); const { errors } = await this.props.onSubmit(this.state.fields); if (!this.isUnmounted) { this.setState({ isSubmitting: false, errors: errors || {} }); } }; }
  123. class Form extends React.Component { state = { /* …

    */ }; static childContextTypes = { /* … */ }; getChildContext() { /* … */ } isUnmounted: boolean = false; componentWillUnmount() { this.isUnmounted = true; } render() { /* … */ } handleChange = (e) => { /* … */ }; handleSubmit = async (e) => { e.preventDefault(); if (this.state.isSubmitting) { return; } this.setState({ isSubmitting: true, errors: {} }); const { errors } = await this.props.onSubmit(this.state.fields); if (!this.isUnmounted) { this.setState({ isSubmitting: false, errors: errors || {} }); } }; }
  124. None
  125. <Form> • simple form interface • extensible fields • protection

    for double submit • standardized form layout • standardized server responses
  126. None
  127. What is missing? • client side validations • theming •

    handling of deep nested forms • tests! • fun features ¯\_(ϑ)_/¯
  128. ! 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

    Libraries
  129. None
  130. https://github.com/RStankov/talks-code

  131. https://speakerdeck.com/rstankov/forms-with-react

  132. https://www.meetup.com/React-Sofia/

  133. Thanks )

  134. None