Un-dux Your Front-End

7fca546408cc6d46ab158f06baed2535?s=47 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.

7fca546408cc6d46ab158f06baed2535?s=128

Nate Abele

November 26, 2017
Tweet

Transcript

  1. 6O%VY:PVS'SPOU&OE /BUF"CFMF "EWJTPS*OOPWBUJPO-BCT

  2. UN-DUX YOUR FRONT-END NATE ABELE

  3. THE UNREASONABLE EFFECTIVENESS OF DATA OR…

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

    frameworks (Li3, CakePHP) ▸ AngularUI Router ▸ Architect @ AI Labs ▸ @nateabele ▸ natea@advisorinnovationlabs.com
  5. UN-DUX YOUR FRONT-END ADVISOR INNOVATION LABS

  6. CONCEPTS

  7. None
  8. None
  9. None
  10. None
  11. None
  12. None
  13. STATE

  14. None
  15. < Component /> < Component /> < Component /> <

    Component /> < Component /> < Component />
  16. < Component /> < Component /> < Component /> <

    Component /> < Component /> < Component />
  17. None
  18. UN-DUX YOUR FRONT-END REDUX ▸ Single-value store ▸ Changes (actions)

    are simple values ▸ Few opinions
  19. 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 ...
  20. None
  21. function todoApp(state = initialState, action) { switch (action.type) { case

    SET_VISIBILITY_FILTER: return Object.assign({}, state, { visibilityFilter: action.filter }) default: return state } }
  22. None
  23. ELM ELM

  24. None
  25. CASIUM

  26. 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> ) });
  27. None
  28. 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> ) });
  29. 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> ) });
  30. 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> ) });
  31. RAMDAJS

  32. 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> ) });
  33. 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> ) });
  34. 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> ) });
  35. 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> ) });
  36. 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> ) });
  37. 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> ) });
  38. 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 }); } }
  39. 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 }); } }
  40. 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> ) });
  41. 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> ) });
  42. 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> ) });
  43. 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> ) });
  44. WHY?

  45. TESTING

  46. describe('+', () => { /* … */ });

  47. GUARANTEES

  48. EXAMPLE: PROMISES

  49. ‘PROBABLY’

  50. $ git checkout b69c907

  51. $ yarn test

  52. [DEMO]

  53. UN-DUX YOUR FRONT-END REVIEW ▸ Diff view of changes ▸

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

    ▸ Reproduce production errors ▸ Watch / replay user sessions ▸ Isolation ▸ Browser testing ▸ API testing
  55. UN-DUX YOUR FRONT-END IMPLICATIONS ▸ Diff analysis

  56. UN-DUX YOUR FRONT-END Sign Up.json Log In.json Set Prefs.json

  57. UN-DUX YOUR FRONT-END Sign Up.json Log In.json Set Prefs.json

  58. $ webpack my-app.js > my-app-df3db62731a0a32ef0c6.js

  59. UN-DUX YOUR FRONT-END df3db6273 +

  60. UN-DUX YOUR FRONT-END Sign Up.json Log In.json + + df3db6273

    df3db6273
  61. UN-DUX YOUR FRONT-END TRY CASIUM ▸ /ai-labs-team/casium ▸ /ai-labs-team/casium-devtools ▸

    casium.io
  62. THANKS!

  63. QUESTIONS?

  64. UN-DUX YOUR FRONT-END CONTACT ▸ /nateabele ▸ @nateabele ▸ natea@advisorinnovationlabs.com

  65. 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