AWESOME STATE MANAGEMENT FOR REACT* *AND OTHER VIRTUAL-DOM - - PowerPoint PPT Presentation

awesome state management for react
SMART_READER_LITE
LIVE PREVIEW

AWESOME STATE MANAGEMENT FOR REACT* *AND OTHER VIRTUAL-DOM - - PowerPoint PPT Presentation

AWESOME STATE MANAGEMENT FOR REACT* *AND OTHER VIRTUAL-DOM LIBRARIES Fred Daoud - @foxdonut00 WHY AWESOME? NOT A LIBRARY. A SIMPLE PATTERN. WORKS WITH ANY VIRTUAL DOM LIBRARY VIRTUAL DOM IS NICE VIEW = FUNCTION(MODEL) HOW DO I MANAGE


slide-1
SLIDE 1

AWESOME STATE MANAGEMENT FOR REACT*

*AND OTHER VIRTUAL-DOM LIBRARIES Fred Daoud - @foxdonut00

slide-2
SLIDE 2

WHY AWESOME?

NOT A LIBRARY. A SIMPLE PATTERN.

slide-3
SLIDE 3

WORKS WITH ANY VIRTUAL DOM LIBRARY

slide-4
SLIDE 4

VIRTUAL DOM IS NICE

VIEW = FUNCTION(MODEL)

slide-5
SLIDE 5

“ HOW DO I MANAGE STATE? ”

slide-6
SLIDE 6

REDUX MOBX CEREBRAL CYCLE.JS

slide-7
SLIDE 7

LET'S LOOK AT A DIFFERENT APPROACH.

slide-8
SLIDE 8

MEIOSIS

HTTP:/ /MEIOSIS.JS.ORG

slide-9
SLIDE 9

THE MEIOSIS PATTERN

model ← single source of truth view = function(model) update model → view is automatically re-rendered

slide-10
SLIDE 10

model = single source of truth view = function(model)

const initialModel = { counter: 0 }; const view = model => ( <div>Counter is {model.counter}</div> );

slide-11
SLIDE 11

actions update the model

const createActions = update => ({ increment: () => update(model => { model.counter++; return model; }) // after calling update(...), // view is automatically re-rendered });

slide-12
SLIDE 12

views call actions

const createView = actions => model => ( <div> <div>Counter is {model.counter}</div> <button onClick={actions.increment}>Increment</button> </div> );

slide-13
SLIDE 13

WHAT IS UPDATE? HOW DO WE AUTOMATICALLY RE-RENDER THE VIEW?

slide-14
SLIDE 14

MEIOSIS PATTERN IMPLEMENTATION Minimal streams: just map and scan Or, write your own minimal implementation

slide-15
SLIDE 15

FLYD STREAMS

MAP

const s1 = flyd.stream(); const s2 = s1.map(value => value * 10); s2.map(value => console.log(value)); s1(5); s1(10); // console output is 50 100

slide-16
SLIDE 16

FLYD STREAMS

SCAN

const s1 = flyd.stream(); const add = (x, y) => x + y; const s2 = flyd.scan(add, 0, s1); s2.map(value => console.log(value)); s1(5); s1(13); s1(24); // console output is 0 5 18 42

slide-17
SLIDE 17

THE MEIOSIS PATTERN

const initialModel = { counter: 0 }; const update = flyd.stream(); const applyUpdate = (model, modelUpdate) => modelUpdate(model); const models = flyd.scan(applyUpdate, initialModel, update); const view = createView(createActions(update)); const element = document.getElementById("app"); models.map(model => ReactDOM.render(view(model), element));

slide-18
SLIDE 18

USING DIFFERENT VIRTUAL DOM LIBS

models.map(model => ReactDOM.render(view(model), element)); models.map(model => Inferno.render(view(model), element)); models.map(model => m.render(element, view(model))); models.map(model => preact.render(view(model), element, element.lastElementChild)); const render = view => element = patch(element, view); models.map(model => render(view(model)));

slide-19
SLIDE 19

MODEL UPDATE PLAIN

update(model => { model.counter++; return model; });

slide-20
SLIDE 20

LODASH LODASH FP RAMDA IMMUTABLE.JS

update(model => _.update(model, "counter", _.partial(_.add,1))); update(_.update("counter", _.add(1))); update(R.over(R.lensProp("counter"), R.add(1))); update(model => model.update("counter", v => v + 1));

slide-21
SLIDE 21

COMPONENTS

slide-22
SLIDE 22

A COMPONENT IS JUST AN OBJECT WITH FUNCTIONS

import { createActions } from "./actions"; import { createView } from "./view"; // This is the same 'update' stream ↓↓ export const createTemperature = update => ({ model: () => ({ date: "", value: 20, units: "C" }), view: createView(createActions(update)) });

slide-23
SLIDE 23

TEMPERATURE EXAMPLE

slide-24
SLIDE 24

ACTIONS (1/2)

export const createActions = update => ({ editDate: evt => update(model => { model.date = evt.target.value; return model; }), increase: amount => () => update(model => { model.value = model.value + amount; return model; }),

slide-25
SLIDE 25

ACTIONS (2/2)

changeUnits: () => update(model => { if (model.units === "C") { model.units = "F"; model.value = Math.round( model.value * 9 / 5 + 32 ); } else { model.units = "C"; model.value = Math.round( (model.value - 32) / 9 * 5 ); } return model; }) });

slide-26
SLIDE 26

VIEW

export const createView = actions => model => ( <div> <div>Date: <input type="text" size="10" value={model.date}

  • nChange={actions.editDate}/></div>

<span>Temperature: {model.value}&deg;{model.units} </span> <div> <button onClick={actions.increase(1)}>Increase</button> <button onClick={actions.increase(-1)}>Decrease</button> </div> <div> <button onClick={actions.changeUnits}>Units</button> </div> </div> );

slide-27
SLIDE 27

THE MEIOSIS PATTERN

const update = flyd.stream(); const temperature = createTemperature(update); // <-------- const initialModel = temperature.model(); // <-------- const applyUpdate = (model, modelUpdate) => modelUpdate(model); const models = flyd.scan(applyUpdate, initialModel, update); const element = document.getElementById("app"); models.map(model => ReactDOM.render(temperature.view(model), // <-------- element));

slide-28
SLIDE 28

MEIOSIS TRACER

TIME-TRA VEL DEV TOOL

slide-29
SLIDE 29

MEIOSIS TRACER IN CHROME DEVTOOLS

slide-30
SLIDE 30

MEIOSIS TRACER IN PAGE

slide-31
SLIDE 31

USING MEIOSIS TRACER IN CHROME

Install meiosis as a dev dependency Install the Chrome extension

// Only for using Meiosis Tracer in development. import { trace } from "meiosis"; trace({ update, dataStreams: [ models ] });

slide-32
SLIDE 32

USING MEIOSIS TRACER IN PAGE

Also install meiosis-tracer as a dev dep.

<div id="tracer"></div> // Only for using Meiosis Tracer in development. import { trace } from "meiosis"; import meiosisTracer from "meiosis-tracer"; trace({ update, dataStreams: [ models ] }); meiosisTracer({ selector: "#tracer" });

slide-33
SLIDE 33

REUSABLE COMPONENTS

slide-34
SLIDE 34

REUSING THE TEMPERATURE COMPONENT

export const createApp = update => { const components = { air: createTemperature(nest(update, "air")), water: createTemperature(nest(update, "water")) }; return { model: () => ({ air: components.air.model(), water: components.water.model() }), view: createView(components) }; };

slide-35
SLIDE 35

NESTING THE UPDATE FUNCTION

export const nest = (update, path) => modelUpdate => update(model => { model[path] = modelUpdate(model[path]); return model; });

slide-36
SLIDE 36

THE VIEW

export const createView = components => model => ( <div> <h4>App</h4> {components.air.view(model.air)} {components.water.view(model.water)} </div> );

slide-37
SLIDE 37

REUSING THE TEMPERATURE COMPONENT

slide-38
SLIDE 38

PATH REPETITION

const components = { air: createTemperature(nest(update, "air")), water: createTemperature(nest(update, "water")) }; model: () => ({ air: components.air.model(), water: components.air.model() }) <div> {components.air.view(model.air)} {components.water.view(model)} </div>

slide-39
SLIDE 39

ELIMINATING PATH REPETITION

const components = createComponents(update, { air: createTemperature, // passes nest(update, "air") water: createTemperature // also wraps view(model["water"]) }); return { // returns { air: c.air.model(), water: c.water.model() } model: combineComponents(components, "model"), view: createView(components) }; <div> {components.air.view(model)} {components.water.view(model)} </div>

slide-40
SLIDE 40

COMPUTED PROPERTIES

slide-41
SLIDE 41

COMPUTED PROPERTIES (1/2)

export const createTemperature = update => { const computed = model => { let temp = model.value; if (model.units === "F") { temp = Math.round((temp - 32) * 5 / 9); } model.comment = (temp < 10) ? "COLD!" : (temp > 40) ? "HOT" : ""; return model; };

slide-42
SLIDE 42

COMPUTED PROPERTIES (2/2)

const view = createView(createActions(update)); return { model: () => ({ date: "", value: 20, units: "C" }), view: model => view(computed(model)) // view: R.compose(view, computed) }; };

slide-43
SLIDE 43

COMPUTED PROPERTIES

slide-44
SLIDE 44

ROUTING

slide-45
SLIDE 45

ROUTING EXAMPLE

slide-46
SLIDE 46

ROUTING: PAGES

export const pages = { home: { id: "Home", tab: "Home" }, coffee: { id: "Coffee", tab: "Coffee" }, beer: { id: "Beer", tab: "Beer" }, beerDetails: { id: "BeerDetails", tab: "Beer" } };

slide-47
SLIDE 47

ROUTING: NA VIGATION

export const createNavigation = update => { const navigate = (page, params = {}) => update(model => Object.assign(model, ({ page, params }))); const navigateToBeer = () => { services.loadBeer().then(beerList => { update(model => Object.assign(model, { beerList })); navigate(pages.beer); }); }; return { navigateToHome, navigateToBeer, ... }; };

slide-48
SLIDE 48

ROUTING: APP

export const createApp = (update, navigation) => { const homeComponent = createHome(update); //more... const pageMap = { [pages.home.id]: homeComponent, //more... }; return { view: model => { const component = pageMap[model.page.id]; return ( // render tabs, model.page.tab determines active tab {component.view(model)} ); } }; };

slide-49
SLIDE 49

ROUTING: ROUTES

export const createRouter = navigation => { const routes = { "/": { id: pages.home.id, action: navigation.navigateToHome }, "/coffee/:id?": { id: pages.coffee.id, action: navigation.navigateToCoffee }, "/beer": { id: pages.beer.id, action: navigation.navigateToBeer }, "/beer/:id": { id: pages.beerDetails.id, action: navigation.navigateToBeerDetails } };

slide-50
SLIDE 50

ROUTING: RESOLVE ROUTE

import Mapper from "url-mapper"; const resolveRoute = () => { const route = document.location.hash.substring(1); const resolved = urlMapper.map(route, routes); if (resolved) { resolved.match.action(resolved.values); } }; window.onpopstate = resolveRoute;

slide-51
SLIDE 51

ROUTING: ROUTE SYNC

const routeMap = Object.keys(routes).reduce((result, route) => { result[routes[route].id] = route; return result; }, {}); const routeSync = model => { const segment = routeMap[model.page.id] || "/"; const route = urlMapper.stringify(segment, model.params||{}); if (document.location.hash.substring(1) !== route) { window.history.pushState({}, "", "#" + route); } };

slide-52
SLIDE 52

ROUTING EXAMPLE

slide-53
SLIDE 53

MEIOSIS

Documentation • Examples Tracer • Gitter chat Fred Daoud • @foxdonut00

HTTP:/ /MEIOSIS.JS.ORG