taming forms with react

75
REACT FORMS ... a crime scene ... CRIME SCENE - DO NOT CROSS

Upload: greecejs

Post on 22-Jan-2018

16 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Taming forms with React

REACT FORMS... a crime scene ...

CRIME SCENE - DO NOT CROSS

Page 2: Taming forms with React

mehiel@DOM

React, since 2015Logicea, since 2013CanJS, since 2011ExtJS, since 2010jQuery, since 2008Web, since 2004

Page 3: Taming forms with React

Forms: why bother?

InevitableStill a PainBusiness InterestFun?

Page 4: Taming forms with React

Forms: pwn factor

|-------------------------------------------∞

^ ^ ^ ^

html react TODAY pwned

Page 5: Taming forms with React

Forms: watch for

the Designerthe Userthe APIthe Browser

Page 6: Taming forms with React

Forms: ingredients

Flexible LayoutVariety of InputsDynamic FieldsDynamic ValuesField DependenciesLookup DataValidationHints & ErrorsField StatusRequests & Responses

Page 7: Taming forms with React

React: the weapon

Declarative

Painless interactive UIs.Design views for each state in your appPredictable and easier to debug

Component Based

Encapsulated with own stateComposable to make complex UIsKeep state out of the DOM

Page 8: Taming forms with React

// the simplest react component as of React 16

function WorldGreeter() {

return "Hello, World"

}

<WorldGreeter /> => "Hello, World"

// with some JSX sauce

function FriendGreeter({ name }) {

return <div>Hello {name}!</div>

}

<FriendGreeter name="Bob" /> => "Hello Bob!"

Page 9: Taming forms with React

class NobleGreeter extends React.Component {

// componentLifecycleFunctions()

// this.setState()

render() {

return (

<div>

Hello {this.props.title} {this.props.name}

</div>

)

}

}

<NobleGreeter title="sir" name="William" /> => "Hello sir William"

Page 10: Taming forms with React

REACT FORMSa crime in 3 acts

Page 11: Taming forms with React

ACT 1the victim

Page 12: Taming forms with React

Act 1 : Scene 1 : ■

function ProfileForm1() {

return (

<form>

<input type="text" name="username" />

<input type="text" name="email" />

<input type="password" name="password" />

</form>

)

}

Page 13: Taming forms with React

Act 1 : Scene 1 : ▸

Page 14: Taming forms with React

Act 1 : Scene 2 : ■

<div>

<label htmlFor="username">Username</label>

<input type="text" name="username" id="username" />

</div>

<div>

<label htmlFor="email">E-mail</label>

<input type="text" name="email" id="email" />

</div>

<div>

<label htmlFor="password">Password</label>

<input type="password" name="password" id="password" />

</div>

Page 15: Taming forms with React

Act 1 : Scene 2 : ▸

UsernameE-mail

Password

Page 16: Taming forms with React

Act 1 : Scene 3 : ■

export class ProfileForm3 extends Component {

onSubmit = (ev) => {

ev.preventDefault() // disable browser post

const data = { ... }

fetch('/api/me', { method: 'POST', body: ... })

}

inputs = {}

render() {

return (<form onSubmit={this.onSubmit}> ... </form>)

}

}

Page 17: Taming forms with React

Act 1 : Scene 3 : ■

inputs = {}

onSubmit = (ev) => {...

const data = {

username: this.inputs.username.value,

}

}

<form onSubmit={this.onSubmit}>...

<input type="text" name="username" id="username"

ref={(el) => this.inputs.username = el} />

</form>

Page 18: Taming forms with React

Act 1 : Scene 3 : ▸

UsernameE-mail

PasswordSubmit

Page 19: Taming forms with React

Act 1 : Scene 4 : ■

export class ProfileForm4 extends Component {

onSubmit = (ev) => {...

const data = {}

new FormData(ev.target).forEach(

(value, name) => data[name] = value

)

...}

render() {

...

<input type="text" name="username" id="username" />

}

}

Page 20: Taming forms with React

Act 1 : Scene 4 : ▸

UsernameE-mail

PasswordSubmit

Page 21: Taming forms with React

Break : Controlled Input : ⚛

With controlled components react state is the “single source of truth”.

<input type="text" value="GreeceJS" />

GreeceJS

<input type="text" value={name} onChange={handleNameChange} />

Page 22: Taming forms with React

Break : Controlled Input : ⚛

feature uncontrolled controlledon submit value retrieval ✅ ✅

validating on submit ✅ ✅instant �eld validation ❌ ✅

conditionally disabling inputs ❌ ✅enforcing input format ❌ ✅

dynamic inputs ❌ ✅

Page 23: Taming forms with React

Act 1 : Scene 5 : ■

export class ProfileForm5 extends Component {

state = {}

onUsernameChange = (ev) => {

const value = ev.target.value

this.setState(prevState =>

({ ...prevState, username: value })) // async

}

onEmailChange = (ev) => { ... }

onPasswordChange = (ev) => { ... }

onSubmit = (ev) => {

const data = this.state

post('/api/me', data)

}

render() { ... }

}

Page 24: Taming forms with React

Act 1 : Scene 5 : ■

render() {

const { username = '', email = '', password = '' } = this.state

// undefined values will mark inputs as uncontrolled

// -> unintented use -> maybe bugs -> react warns

return (

<form onSubmit={this.onSubmit}>

<div>

<label htmlFor="username">Username</label>

<input type="text" name="username" id="username"

value={username} onChange={this.onUsernameChange} />

</div>

...

}

Page 25: Taming forms with React

Act 1 : Scene 5 : ▸

UsernameE-mail

PasswordSubmit

Page 26: Taming forms with React

Act 1 : Scene 6 : ■

export class ProfileForm6 extends Component {

state = { username: '', email: '', password: '' }

onSubmit = (ev) => {...}

onChange = (ev) => {

const { name, value } = ev.target

this.setState(prevState => ({ ...prevState, [name]: value }))

}

render() {

const { username, email, password } = this.state

...

<input type="text" name="username" id="username"

value={username} onChange={this.onChange} />

}

}

Page 27: Taming forms with React

Act 1 : Scene 6 : ▸

UsernameE-mail

PasswordSubmit

Page 28: Taming forms with React

ACT 2set the stage

Page 29: Taming forms with React

Break : Container Components : ⚛

You’ll �nd your components much easier to reuse and reason about if you dividethem into two categories.

I call them Container and Presentational components- Dan Abramov

Page 30: Taming forms with React

Break : Container Components : ⚛

Concerned with how things work

Provide the data and behavior

Call �ux actions and provide callbacks

Often stateful, as they tend to serve as data sources

Usually generated using higher order components

Page 31: Taming forms with React

Act 2 : Scene 1 : ■

export const ProfileContainer = Child => {

class Container extends React.Component {

state = { values: {}, errors: {} };

onSubmit = ev => post('/api/me', this.state.values)

onChange = (values, errors) => {

this.setState(prevState => ({ ...prevState, values, errors }));

};

render() {

const { children, ...childProps } = this.props;

const { values, errors } = this.state;

const { onChange, onSubmit } = this;

return (

<Child {...childProps}

form={{values, errors, onChange, onSubmit}}>

{children}</Child>

)}}

return Container;

}}

Page 32: Taming forms with React

Act 2 : Scene 1 : ■

class _ProfileForm7 extends Component {

onChange = (ev) => {

const { values, onChange } = this.props.form

const { name, value } = ev.target

const newValues = { ...values, [name]: value }

onChange(newValues)

}

render() {

const { values, onSubmit } = this.props.form

return <form onSubmit={onSubmit}>...

<input type="text" name="username" id="username"

value={values.username || ''} onChange={this.onChange} />

}}

const ProfileForm7 = ProfileContainer(_ProfileForm7)

export default ProfileForm7

Page 33: Taming forms with React

Act 2 : Scene 1 : ▸

UsernameE-mail

PasswordSubmit

Page 34: Taming forms with React

Act 2 : Scene 2 : ■

<form onSubmit={onSubmit}>

<div>

<label htmlFor="username">Username</label>

<input type="text" name="username" id="username"

value={values.username || ''} onChange={this.onChange} />

</div>

<div>

<label htmlFor="email">E-mail</label>

<input type="text" name="email" id="email"

value={values.email || ''} onChange={this.onChange} />

</div>

<div>

<label htmlFor="password">Password</label>

<input type="password" name="password" id="password"

value={values.password || ''} onChange={this.onChange} />

</div>

<input type="submit" />

</form>

Page 35: Taming forms with React

Act 2 : Scene 2 : ■

class Fields extends Component {

...

}

Page 36: Taming forms with React

Act 2 : Scene 2 : ■

class _ProfileForm8 extends Component {

schema = [{...}, {...}, {...}]

render() {

const { values, onChange, onSubmit } = this.props.form

return (

<form onSubmit={onSubmit}>

<Fields schema={this.schema}

values={values} onChange={onChange} />

<input type="submit" />

</form>

)

}

}

const ProfileForm8 = ProfileContainer(_ProfileForm8)

export { ProfileForm8 }

Page 37: Taming forms with React

Act 2 : Scene 2 : ■

class Fields extends Component {

onChangeHandler = ev => {

const { values, onChange } = this.props;

const { name, value } = ev.target;

const newValues = {...values, [name]: (value || null) };

onChange(newValues);

};

render() {

const { schema, values } = this.props;

return (<div>...</div>);

}

}

Page 38: Taming forms with React

Act 2 : Scene 2 : ■

schema = [{

path: "username",

label: "Username",

renderer: (path, value, label, onChange) => {

return (

<div>

<label htmlFor="username">Username</label>

<input type="text" name="username" id="username"

value={value} onChange={onChange} />

</div>

)

}

}]

Page 39: Taming forms with React

Act 2 : Scene 2 : ■

class Fields extends Component {

onChangeHandler = ...

render() {

const { schema, values } = this.props;

return (<div>

{schema.map(field =>

field.renderer(

field.path,

values[field.path] || '',

field.label,

this.onChangeHandler)

)}

</div>);

}}

Page 40: Taming forms with React

Act 2 : Scene 2 : ■

class _ProfileForm8 extends Component {

schema = [{...}, {...}, {...}]

render() {

const { values, onChange, onSubmit } = this.props.form

return (

<form onSubmit={onSubmit}>

<Fields schema={this.schema}

values={values} onChange={onChange} />

<input type="submit" />

</form>

)

}

}

const ProfileForm8 = ProfileContainer(_ProfileForm8)

export { ProfileForm8 }

Page 41: Taming forms with React

Act 2 : Scene 2 : ▸

UsernameE-mail

PasswordSubmit

Page 42: Taming forms with React

Act 2 : Scene 3 : ■

schema = [{

path: "username",

label: "Username",

renderer: (path, value, label, onChange) => {

return (

<div>

<label htmlFor="username">Username</label>

<input type="text" name="username" id="username"

value={value} onChange={onChange} />

</div>

)

}

}]

Page 43: Taming forms with React

Act 2 : Scene 3 : ■

const textRenderer = (path, value, label, onChange) => (

<div>

<label htmlFor={path}>{label}</label>

<input type="text" name={path} id={path}

value={value} onChange={onChange} />

</div>

)

schema = [

{ path: "username", label: "Username", renderer: textRenderer }

]

Page 44: Taming forms with React

Act 2 : Scene 3 : ■

const textRenderer = (type = 'text') =>

(path, value, label, onChange) => (

<div>

<label htmlFor={path}>{label}</label>

<input type={type} name={path} id={path}

value={value} onChange={onChange} />

</div>

)

const passwordRenderer = () => textRenderer('password')

schema = [

{path: "username", label: "Username", renderer: textRenderer()},

{path: "email", label: "E-mail", renderer: textRenderer()},

{path: "password", label: "Password", renderer: passwordRenderer()}

]

Page 45: Taming forms with React

Act 2 : Scene 3 : ■

class _ProfileForm9 extends Component {

schema = [

{ path: "username", label: ..., renderer: textRenderer() },

{ path: "email", label: ..., renderer: textRenderer() },

{ path: "password", label: ..., renderer: passwordRenderer() }

]

render() {

const { values, onChange, onSubmit } = this.props.form

return (

<form onSubmit={onSubmit}>

<Fields schema={this.schema}

values={values} onChange={onChange} />

<input type="submit" />

</form>

)

}

}

const ProfileForm9 = ProfileContainer(_ProfileForm9)

export { ProfileForm9 }

Page 46: Taming forms with React

Act 2 : Scene 3 : ▸

UsernameE-mail

PasswordSubmit

Page 47: Taming forms with React

Act 2 : Scene 4 : ■

class _ProfileForm10 extends Component {

schema = (values) => {

const _schema = [

{ path: "username", label: ..., renderer: textRenderer() },

{ path: "email", label: ..., renderer: textRenderer() }

]

if (values.email) _schema.push(

{ path: "password", label: ..., renderer: passwordRenderer() }

)

return _schema;

}

render() {

const { values, onChange, onSubmit } = this.props.form

return ( ... <Fields schema={this.schema(values)} ... )

}

}

Page 48: Taming forms with React

Act 2 : Scene 4 : ▸

UsernameE-mail

Submit

Page 49: Taming forms with React

ACT 3the perfect crime!

Page 50: Taming forms with React

yeah, well! Not yet in the public but...yarn add @logicea/react-forms

Page 51: Taming forms with React

Act 3 : Scene 1 : ■

export const ProfileContainer = Child => {

class Container extends React.Component {

state = { values: {}, errors: {} };

onSubmit = ev => post('/api/me', this.state.values)

onChange = (values, errors) => {

this.setState(prevState => ({ ...prevState, values, errors }));

};

render() {

const { children, ...childProps } = this.props;

const { values, errors } = this.state;

const { onChange, onSubmit } = this;

return (

<Child {...childProps}

form={{values, errors, onChange, onSubmit}}>

{children}</Child>

)}}

return Container;

}}

Page 52: Taming forms with React

Act 3 : Scene 1 : ■

onChange = ev => {

const { values, errors } = ev.detail

this.setState(prevState => ({ ...prevState, values, errors }));

};

Page 53: Taming forms with React

Act 3 : Scene 1 : ■

import Fields from '@logicea/react-forms/Fields'

class _ProfileForm11 extends Component {

schema = (values) => {

const _schema = [

{ row: 1, path: "username", ... },

{ row: 2, path: "email", ... }

]

if(...) _schema.push({ row: 3, path: "password", ... }) })

}

render() { ... }

}

Page 54: Taming forms with React

Act 3 : Scene 1 : ▸

UsernameE-mail

Submit

Page 55: Taming forms with React

Act 3 : Scene 2 : ■

import Fields from '@logicea/react-forms/Fields'

import yup from 'yup'

class _ProfileForm12 extends Component {

schema = [

{ path: "username", validator: yup.string().required(), ... },

{ path: "email", validator: yup.string().email(), ... },

{ path: "password", validator: yup.string().min(4), ... },

]

render() {

const { values, errors, onChange, onSubmit } = this.props.form

return ... <Fields schema={this.schema(values)}

values={values} errors={errors} onChange={onChange} />

}

}

Page 56: Taming forms with React

Act 3 : Scene 2 : ■

const textRenderer = (type = 'text') =>

(path, value, label, onChange, error) => (

<div>

<label>...</label>

<input ... />

{error && <div style={{ color: 'red' }}>{error}</div>}

</div>

)

const passwordRenderer = () => textRenderer('password')

Page 57: Taming forms with React

Act 3 : Scene 2 : ▸

UsernameE-mail

PasswordSubmit

Page 58: Taming forms with React

Act 3 : Scene 3 : ■

class _ProfileForm13 extends Component {

schema = (values) => {

const _schema = [

...

{ row: 4, path: "country", label: "Country",

dependands: ["city"], renderer: textRenderer() },

{ row: 5, path: "city", label: "City",

renderer: textRenderer() },

]

return _schema;

}

render() { ... }

}

Page 59: Taming forms with React

Act 3 : Scene 3 : ▸

UsernameE-mail

PasswordCountry

CitySubmit

Page 60: Taming forms with React

Act 3 : Scene 4 : ■

const COUNTRIES = [

'Greece',

'Burundi',

'Democratic Republic of Congo',

'Central African Republic'

]

const CITIES = {

'Greece': ['Athens', 'Thessaloniki'],

'Burundi': ['Bujumbura', 'Muyinga'],

'Democratic Republic of Congo': ['Kinshasa', 'Lubumbashi'],

'Central African Republic': ['Bangui', 'Bimbo']

}

Page 61: Taming forms with React

Act 3 : Scene 4 : ■

const selectRenderer = (options = [])

=> (path, value, label, onChange, error) => (

<div>

<label htmlFor={path}>{label}</label>

<select name={path} id={path}

value={value} onChange={onChange}>

<option value=''></option>

{options.map(o => <option value={o} key={o}>{o}</option>)}

</select>

{error && <div style={{ color: 'red' }}>{error}</div>}

</div>

)

}

Page 62: Taming forms with React

Act 3 : Scene 4 : ■

class _ProfileForm14 extends Component {

schema = (values, allCountries, allCities) => {

const cities = values.country ? allCities[values.country] : []

const _schema = [...

{ path: "country", renderer: selectRenderer(allCountries) ...

{ path: "city", renderer: selectRenderer(cities) ...

]

return _schema;

}

render() {

const schema = this.schema(values, COUNTRIES, CITIES)

...

}

}

Page 63: Taming forms with React

Act 3 : Scene 4 : ▸

UsernameE-mail

PasswordCountry

CitySubmit

Page 64: Taming forms with React

Act 3 : Scene 5 : ■

const getCountries = (props) => {

return new Promise(r => setTimeout(() => r(COUNTRIES), 200))

}

const getCities = (props) => {

const selectedCountry = props.form.values.country

const citiesResponse = CITIES[selectedCountry] || []

return new Promise(r => setTimeout(() => r(citiesResponse), 200))

}

Page 65: Taming forms with React

Act 3 : Scene 5 : ■

class _ProfileForm15 extends Component {

schema = (values, countries, countryCities) => [...

{ path: "country", renderer: selectRenderer(countries) ...

{ path: "city", renderer: selectRenderer(countryCities) ...

]

render() {...

const { countries, cities } = this.props.lookups

const schema = this.schema(values, countries, cities)

}

}

const ProfileForm15 = compose(ProfileContainer(), Lookups({

initial: ['countries'],

resolver: (lookupField) => { ... },

rules: (oldProps, newProps) => { ... },

}))(_ProfileForm15)

export { ProfileForm15 }

Page 66: Taming forms with React

Act 3 : Scene 5 : ■

const lookupsResolver = (lookupField) => {

switch (lookupField) { // eslint-disable-line default-case

case 'countries': return getCountries;

case 'cities': return getCities;

}

}

const lookupRules = (oldProps, newProps) => {

let lookups = []

const countryChanged =

oldProps.form.values.country !== newProps.form.values.country

if (countryChanged) lookups.push('cities')

return { lookups }

}

Page 67: Taming forms with React

Act 3 : Scene 5 : ▸

UsernameE-mail

PasswordCountry

CitySubmit

Page 68: Taming forms with React
Page 69: Taming forms with React
Page 70: Taming forms with React

Forms: other libs

complexfeature fullbloated

simplestate mgmtredux-free our inspiration

joi basednot mature

react-joi-forms

Page 71: Taming forms with React

Performance

Page 72: Taming forms with React

Performance - @logicea/react-forms

Page 73: Taming forms with React

Performance - Formik

Page 74: Taming forms with React

Performance - Formik vs Redux Form

Page 75: Taming forms with React