Forms with React

Forms with React

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

7a0e72a6f55811246bb5d9a946fd2e49?s=128

Radoslav Stankov

March 07, 2017
Tweet

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