Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Un-dux Your Front-End

Nate Abele
November 26, 2017

Un-dux Your Front-End

Introducing Casium, a new approach to application architecture and state management, which will replace Redux in your React applications.

Presented at Frontend Frameworks Days in Kiev, and LibertyJS in Philadelphia, November 2017.

Nate Abele

November 26, 2017
Tweet

More Decks by Nate Abele

Other Decks in Programming

Transcript

  1. UN-DUX YOUR FRONT-END YOUR HOST: NATE ABELE ▸ Some PHP

    frameworks (Li3, CakePHP) ▸ AngularUI Router ▸ Architect @ AI Labs ▸ @nateabele ▸ [email protected]
  2. < Component /> < Component /> < Component /> <

    Component /> < Component /> < Component />
  3. < Component /> < Component /> < Component /> <

    Component /> < Component /> < Component />
  4. SETTING UP $ yarn add redux react-redux react-router-redux redux-logger redux-saga

    redux-mock-store redux-immutable redux-thunk redux-promise-middleware react-redux-form redux-act redux-saga-test-plan redux-query redux-saga-async ...
  5. function todoApp(state = initialState, action) { switch (action.type) { case

    SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) default: return state } }
  6. 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> ) });
  7. 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> ) });
  8. 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> ) });
  9. 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> ) });
  10. 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> ) });
  11. 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> ) });
  12. 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> ) });
  13. import { merge } from 'ramda'; /* … */ class

    UpdateForm 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> ) });
  14. import { Http } from 'casium/commands'; /* … */ class

    SignInSubmit extends Message {} class SignInSuccess extends Message {} class SignInError extends Message {} export default container({ init: () => ({ email: '', password: '', loading: false }), update: [ [UpdateForm, (model, { key, value }) => /* … */], [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> ) });
  15. 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(SignInSubmit)}> /* … */ </form> ) });
  16. 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 }); } }
  17. 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 }); } }
  18. 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> ) });
  19. 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> ) });
  20. 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> ) });
  21. 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> ) });
  22. UN-DUX YOUR FRONT-END REVIEW ▸ Diff view of changes ▸

    Generated unit tests ▸ Time travel ▸ Message log export ▸ Websockets magic
  23. UN-DUX YOUR FRONT-END IMPLICATIONS ▸ Crossing boundaries ▸ Device farm

    ▸ Reproduce production errors ▸ Watch / replay user sessions ▸ Isolation ▸ Browser testing ▸ API testing
  24. 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