Download - Nate Abele "Un-Dux Your Front-End"
Un-Dux Your Front-EndNate AbeleAdvisor Innovation Labs
UN-DUX YOUR FRONT-ENDNATE ABELE
THE UNREASONABLE EFFECTIVENESS OF DATA
OR…
UN-DUX YOUR FRONT-END
YOUR HOST: NATE ABELE
▸ Some PHP frameworks (Li3, CakePHP)
▸ AngularUI Router
▸ Architect @ AI Labs
▸ @nateabele
UN-DUX YOUR FRONT-END
ADVISOR INNOVATION LABS
CONCEPTS
CONCEPTS
STATE
< Component />
< Component />
< Component /> < Component />
< Component />
< Component /> < Component />
< Component />
< Component />
< Component /> < Component />
< Component /> < Component /> < Component />
< Component />
< Component /> < Component />
< Component /> < Component /> < Component />
< Component />
< Component /> < Component />
< Component /> < Component /> < Component />
< Component />
< Component /> < Component />
< Component /> < Component /> < Component />
UN-DUX YOUR FRONT-END
REDUX
▸ Single-value store
▸ Changes (actions) are simple values
▸ Few opinions
SETTING UP
$ yarn add
SETTING UP
$ yarn add reduxreact-reduxreact-router-reduxredux-loggerredux-sagaredux-mock-storeredux-immutableredux-thunkredux-promise-middlewarereact-redux-formredux-actredux-saga-test-planredux-queryredux-saga-async...
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, {
visibilityFilter: action.filter
})
default:
return state
}
}
ELMELM
CASIUM
import React from 'react';
import { container } from 'casium';import Message from 'casium/message';
class Increment extends Message {}class Decrement extends Message {}
export default container({
init: () => ({ count: 0 }),
update: [ [Increment, ({ count }) => ({ count: count + 1 })], [Decrement, ({ count }) => ({ count: count - 1 })] ],
view: ({ emit, count }) => ( <div> <button onClick={emit(Decrement)}> - </button> { count } <button onClick={emit(Increment)}> + </button> </div> )});
import React from 'react';
import { container } from 'casium';import Message from 'casium/message';
class Increment extends Message {}class Decrement extends Message {}
export default container({
init: () => ({ count: 0 }),
update: [ [Increment, ({ count }) => ({ count: count + 1 })], [Decrement, ({ count }) => ({ count: count - 1 })] ],
view: ({ emit, count }) => ( <div> <button onClick={emit(Decrement)}> - </button> { count } <button onClick={emit(Increment)}> + </button> </div> )});
import React from 'react';
import { container } from 'casium';import Message from 'casium/message';
class Increment extends Message {}class Decrement extends Message {}
export default container({
init: () => ({ count: 0 }),
update: [ [Increment, ({ count }) => ({ count: count + 1 })], [Decrement, ({ count }) => ({ count: count - 1 })] ],
view: ({ emit, count }) => ( <div> <button onClick={emit(Decrement)}> - </button> { count } <button onClick={emit(Increment)}> + </button> </div> )});
import React from 'react';
import { container } from 'casium';import Message from 'casium/message';
class Increment extends Message {}class Decrement extends Message {}
export default container({
init: () => ({ count: 0 }),
update: [ [Increment, ({ count }) => ({ count: count + 1 })], [Decrement, ({ count }) => ({ count: count - 1 })] ],
view: ({ emit, count }) => ( <div> <button onClick={emit(Decrement)}> - </button> { count } <button onClick={emit(Increment)}> + </button> </div> )});
RAMDAJS
import React from 'react';import { evolve } from 'ramda';
import { container } from 'casium';import Message from 'casium/message';
class Increment extends Message {}class Decrement extends Message {}
export default container({
init: () => ({ count: 0 }),
update: [ [Increment, evolve({ count: ct => ct + 1 })], [Decrement, evolve({ count: ct => ct - 1 })] ],
view: ({ emit, count }) => ( <div> <button onClick={emit(Decrement)}> - </button> { count } <button onClick={emit(Increment)}> + </button> </div> )});
import React from 'react';import { evolve } from 'ramda';
import { container } from 'casium';import Message from 'casium/message';
class Increment extends Message {}class Decrement extends Message {}
export default container({
init: () => ({ count: 0 }),
update: [ [Increment, evolve({ count: ct => ct + 1 })], [Decrement, evolve({ count: ct => ct - 1 })] ],
view: ({ emit, count }) => ( <div> <button onClick={emit(Decrement)}> - </button> { count } <button onClick={emit(Increment)}> + </button> </div> )});
import React from 'react';import { evolve, inc, dec } from 'ramda';
import { container } from 'casium';import Message from 'casium/message';
class Increment extends Message {}class Decrement extends Message {}
export default container({
init: () => ({ count: 0 }),
update: [ [Increment, evolve({ count: inc })], [Decrement, evolve({ count: dec })] ],
view: ({ emit, count }) => ( <div> <button onClick={emit(Decrement)}> - </button> { count } <button onClick={emit(Increment)}> + </button> </div> )});
import { merge } from 'ramda';/* … */
class UpdateForm extends Message {}class SignIn extends Message {}
export default container({ init: () => ({ email: '', password: '' }),
update: [[UpdateForm,(model, { key, value }) => merge(model, { [key]: value })
]],
view: ({ emit, email, password }) => ( <form> <input type='email' value={email}
onChange={emit([UpdateForm, { key: 'email' }])} /> <input type='password' value={password}
onChange={emit([UpdateForm, { key: 'password' }])} /> </form> )});
import { Http } from 'casium/commands';/* … */
class SignIn extends Message {}class SignInSuccess extends Message {}class SignInError extends Message {}
export default container({ init: () => ({ email: '', password: '', loading: false }),
update: [ [UpdateForm, (model, { key, value }) => /* … */],
[SignIn, model => [merge(model, { loading: true }),new Http.Post({url: '/login',data: { email: model.email, password: model.password },result: SignInSuccess,error: SignInError
})]]
],
view: ({ emit, email, password }) => ( <form onSubmit={emit(SignIn)}> /* … */ </form> )});
import { Http } from 'casium/commands';/* … */
class SignInSuccess extends Message {}class SignInError extends Message {}
export default container({ init: () => ({ email: '', password: '', loading: false }),
update: [ /* … */
[SignInSuccess, (model, { data }) => /* Handle the response… */][SignInError, (model, { status }) => /* Handle error… */]
],
view: ({ emit, email, password }) => ( <form onSubmit={emit(SignIn)}> /* … */ </form> )});
import { Post, formData } from 'casium/commands/http';
export default class SignIn extends Post {
constructor({ email, password, ...params }) { const id = 'my-app', secret = 'woo-sekrit';
super({ url: '/oauth/token', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic ' + btoa(id + ':' + secret) }, data: formData({ username: email, password, grant_type: 'password', scope: 'read write', client_secret: secret, client_id: id }), ...params }); }}
import { Post, formData } from 'casium/commands/http';import { SignInSuccess, SignInError } from './sign_in_container';
export default class SignIn extends Post {
constructor({ email, password, ...params }) { const id = 'my-app', secret = 'woo-sekrit';
super({ url: '/oauth/token', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': 'Basic ' + btoa(id + ':' + secret) }, data: formData({ username: email, password, grant_type: 'password', scope: 'read write', client_secret: secret, client_id: id }),
result: SignInSuccess,error: SignInError
...params }); }}
import { Http } from 'casium/commands';/* … */
export default container({ init: () => ({ email: '', password: '', loading: false }),
update: [ /* … */
[SignInSubmit, model => [merge(model, { loading: true }),new Http.Post({url: '/login',data: { email: model.email, password: model.password },result: SignInSuccess,error: SignInError
})]]
],
view: ({ emit, email, password }) => ( <form onSubmit={emit(SignInSubmit)}> /* … */ </form> )});
import { SignIn } from ‘./messages/sign_in’;/* … */
export default container({ init: () => ({ email: '', password: '', loading: false }),
update: [ /* … */
[SignInSubmit, model => [merge(model, { loading: true }),new SignIn({ email: model.email, password: model.password })
]] ],
view: ({ emit, email, password }) => ( <form onSubmit={emit(SignInSubmit)}> /* … */ </form> )});
import { SignIn } from ‘./messages/sign_in’;import { container, seq } from 'casium';/* … */
export default container({ init: () => ({ email: '', password: '', loading: false }),
update: [ /* … */
[SignInSubmit, seq(model => merge(model, { loading: true }),model => [model, new SignIn({ email: model.email, password: model.password})]
)] ],
view: ({ emit, email, password }) => ( <form onSubmit={emit(SignInSubmit)}> /* … */ </form> )});
import { merge, pick } from 'ramda';import { SignIn } from ‘./messages/sign_in’;import { container, seq, replace, commands } from 'casium';/* … */
export default container({ init: () => ({ email: '', password: '', loading: false }),
update: [ /* … */
[SignInSubmit, seq(replace({ loading: true }),commands(SignIn, pick(['email', 'password']))
)] ],
view: ({ emit, email, password }) => ( <form onSubmit={emit(SignInSubmit)}> /* … */ </form> )});
WHY?
GUARANTEES
EXAMPLE: PROMISES
GUARANTEES
‘PROBABLY’
$ git checkout b69c907
$ yarn test
[DEMO]
UN-DUX YOUR FRONT-END
REVIEW
▸ Diff view of changes
▸ Generated unit tests
▸ Time travel
▸ Message log export
▸ Websockets magic
UN-DUX YOUR FRONT-END
IMPLICATIONS
▸ Crossing boundaries
▸ Device farm
▸ Reproduce production errors
▸ Watch / replay user sessions
▸ Isolation
▸ Browser testing
▸ API testing
UN-DUX YOUR FRONT-END
IMPLICATIONS
▸ Diff analysis
UN-DUX YOUR FRONT-END
Sign Up.json
Log In.json
Set Prefs.json
UN-DUX YOUR FRONT-END
Sign Up.json
Log In.json
Set Prefs.json
$ webpack my-app.js
$ webpack my-app.js> my-app-df3db62731a0a32ef0c6.js
UN-DUX YOUR FRONT-END
df3db6273+
UN-DUX YOUR FRONT-END
Sign Up.json
Log In.json
+
+
df3db6273
df3db6273
UN-DUX YOUR FRONT-END
TRY CASIUM
▸ /ai-labs-team/casium
▸ /ai-labs-team/casium-devtools
▸ casium.io
THANKS!
QUESTIONS?
PHOTO CREDITS
▸ ‘This is not a pipe’: https://www.threadless.com/product/543/this_is_not_a_pipe
▸ ‘Cow’: https://www.emaze.com/@AWCTOFZ
▸ ‘Steak’: http://www.chicagomeat.com/products/t-bone-steak/
▸ ‘McDonald’s Hamburger’: https://www.businessinsider.com.au/man-saves-mcdonalds-burger-for-5-years-2014-7
▸ ‘Texas’: http://yalsa.ala.org/blog/2014/02/25/virtual-road-trip-texas-part-2/
▸ ‘DeLorean’: https://www.pinterest.com/pin/114419646753355072/
▸ ‘Redux Architecture’: https://medium.com/mofed/react-redux-architecture-overview-7b3e52004b6e