Why
React
is Not
Reactive

@swyx                                                             swyx.io/ReactRally

Infinite Builder

DX @ Netlify

/r/reactjs

65,000 React devs

Acknowledgements

CFP

Prep

Research

+ Survey members

+ Others

My MISSION

  • Give Credit
  • Learn in Public

STORY

👨‍💼: "make this request cancellable"

class MyComponent extends Component {
    componentDidMount() {
        this._isMounted = true
    }
    componentWillUnmount() {
        this._isMounted = false
    }
    
    getData = () => fetch('/api')
        .then(data => {
            if (this._isMounted) {
                this.setState({data: data.json()})
            }
        })

    // ...   
}

👨‍💼: "make this request cancellable"

  • also I want debouncing
  • and retries
  • and show recent history
  • and don't rerender if the input is the same

.debounce()

.retry()

.window()

.distinctUntilChanged()

lodash for async

aka Reactive Programming

"Especially good at ui's"

how it feels
to learn REACTIVE

Redux + createState

recycle

frint-react

reenhance-components

recompose

redux-observable

how it feels
to learn REACTIVE

  • Concepts
    • The Reactive Manifesto™
    • Reactive Programming
      • vs Functional Reactive Programming
      • vs Functional AND Reactive Programming
    • Hot vs Cold
    • Observables vs Observers
      • vs Subjects
      • BehaviorSubject vs ReplaySubject
      • oh also AsyncSubject
      • oh also Schedulers
  • Working with React
    • createState
    • recycle
    • frint-react
    • reenhance-components
    • recompose
    • redux-observable

this talk is not

Everyone knows spreadsheets

Visicalc (1979 - 1983)

Everyone knows spreadsheets

Lotus 1-2-3 (1983-2014)

Everyone knows spreadsheets

Excel (1985-?)

Spreadsheets are reactive

What's so great about spreadsheets?

  • Minimum Viable App (DB + UI)
  • Only business logic, 100% declarative
    • including update logic
    • rendering resolves dependencies
  • Always consistent (UI <=> Data)
    • Data in motion
    • Passive
  • in short: Reactive

<Angular />

"Like a Spreadsheet"

javascript is not reactive

var foo = 1
var bar = 7
var Answer = foo * bar
console.log(Answer) // 7
foo = 6
console.log(Answer) // 7 😢

"Data at rest"

But it has reactive api's

btn.addEventListener('click', handler)

function handler() {
    alert('hello world!')
}

consumer (handler) tied to the producer (btn)

observables

function Observable(cb) {
    const subscribe = next => cb({ next })
    return { subscribe }
}

Barebones Implementation of the spec

CODING IN STREAMS!

const source$ = new Observable(
    obs => obs.next('world!')
)

source$.subscribe(
    x => console.log('hello', x) // 'hello world!'
)

Basic usage (by convention, add $ to indicate observable stream)

consumer (console.log) decoupled from producer (source$)

Componentizing Event Streams

Observables = podcasts

observers subscribe to observables

listeners subscribe to podcasts

Everything is
(or can be)
a stream

  • DOM Events
    • Touch/Mouse
    • Scroll
    • Animation
    • Keyboard
    • Focus
    • Media
  • Web sockets
  • Server Events
  • setInterval/Timeout
  • Node Streams
  • Service Workers
  • XMLHTTPRequest
  • rAF/rIC
  • Public APIs
    • Firebase
    • SignalR
    • Splunk
    • MongoDB
    • etc.
List Credit: Jafar Husain

TL;DR

Note:  In academic language,

"Events" = All forms of discrete updates, not just DOM events

MEMES, MEMES EVERYWHERE

VIEW <= f(DATA)

v = f(d)

Leaky Abstraction: Data isn't static

VIEWSTREAM <= DATAstream

v$ = f(d$)

Leaky Abstraction?

👨‍💼: "How's it going?"

🤷‍♂️

3 months later...

  • STORY

  • QUESTION

Reactive is 👍 for UIs

WHY DOESn'T REACT USE STREAMS?

dumb question:

what if react was reactive?

In an alternate universe...
REACTIVE-REACT!

☢️

⚛️

counter

Update model

View

Update

0

1

2

Click

Click

REACTIVE-REACT:
​Component API

class Counter extends Component {
    initialState = 0
    increment = createHandler(() => 1)
    source() {
        const source$ = this.increment.$
        const reducer = (acc, n) => acc + n
        return { source$, reducer }
    }
    render(state) {
        return (
            <div>
                Count: {state}
                <button onClick={this.increment}>+</button>
            </div>
        )      
    }
}

Reactive-React

big picture

DOM

renderStream

MyApp

vDOM

source$

.subscribe()

render

createElement

appendChild

next State

DOM

patch

diff

vDOM

initial/prevState

Red = reactive

Reactive-React

bigGER picture

UIStream

MyApp

prevvDOM

source$

.subscribe()

render

next State

DOM

patch

diff

vDOM

prevState

renderer/reconciler

scheduler

REACTIVE-REACT:
reactive core

// import { diff, patch, createElement } from 'virtual-dom'

export function mount(element, container) {
  const {source$, initInstance} = renderStream(element)
  const rootNode = createElement(initInstance.dom)
  container.appendChild(rootNode) // initial mount
  return scan( // "stateful loop" similar to Array.reduce
    source$, 
    ({instance, state}, nextState) => {
        // render function defined elsewhere
        const nextinstance = render(element, instance, nextState, state) 
        const domDiff = diff(instance.dom, nextinstance.dom) // vDom diffing
        patch(rootNode, domDiff) // render to screen
        return {instance: nextinstance, state: nextState}
    }
    ,{instance: initInstance, state: INITIALSOURCE}
  ).subscribe() // starts the subscription to the App source stream
}

renderStream

vDOM

source$

render

patch()

diff()

subscribe()

Reactive-REACT

COUNTER DeMO

Blink

Update model

View

Update

Blink!

Blink!

Tick

Tick

(blank)

<Blink />

class Blink extends Component {
  source($) {
    const reducer = x => !x
    const source = Interval(1000) // tick every second
    return {source, reducer}
  }
  render(state) {
    const style = {visibility: state ? 'visible' : 'hidden'}
    return <div style={style}>Blink!</div>
  }
}

Time as a source
instead of clicks

FLappy bird

Update model

View

Timer Update

-1

Tick (-1)

Tick (-1)

Click Update

Click (+2)

1

0

Click (+2)

2

💩CRappy bird🐦

class CrappyBird extends Component {
    initialState = 0
    increment = createHandler(() => 2)
    source($) {
        const click$ = this.increment.$
        const time$ = Interval(1000, -1)
    render(state) {
        return (
            <div>
                Count: {state}
                <button onClick={this.increment}>+</button>
            </div>
        )      
    }
}

v$ = f(merge(d$, t$))

        const source$ = merge(click$, time$)
        const reducer = (acc, n) => acc + n
        return { source$, reducer }
    }
   

More possibilities FOR RESEARCH

  • Elm/Bret Victor-style history recalc hot reloading
  • Parent-child/sibling Stream sharing
  • Undo/Time travel HOC
  • Enter-Leave Animation hooks
  • Built in App level datastore
    • offline
    • analytics
    • anything Redux can do

I know, I know...

So
what's
missing? 

ok, react can be reactive...

  • STORY

  • QUESTION

  • MENTAL MODEL

Reactive is 👍 for UIs

React can be fully reactive. Why isn't it?

Too many updates

View

Update

Wasted!

View

Update

Update

Wasted!

View

Update

Update

Frame

Frame

e.g. keyUp + onChange in one event, or really fast updates

LIve + Reactive = 🙅‍♂️

Problems with REACTive-REACT

Poor interoperability

  • Javascript is not Reactive
    • Writing wrappers around every library?
  • Even harder if you have to output stream -and- DOM
    • DOM is retained mode, stream is immediate mode

Wasteful/Janky rendering

  • Too many updates
  • Expensive updates
  • Uninterruptible rendering

Expensive updates

  • Coding 100% Reactive:
    • Good DX: Code as if streams compute instantly
    • Bad UX: Compute takes time
      • This is a leaky abstraction
      • You can code around it
        • but end up adding so much state inside operators that you reimplement imperative programming
      • Caveat: Rx Schedulers

FREQUENT render is fine...

View

Update

View

Update

Render

View

Update

Render

Frame

Frame

...until it is too expensive

View

Update

Dropped!

Update

Render

View

Frame

Frame

Reactive-REACT

crazy charts DeMO

What you WANT

What you GOT

not created equal

View

Low Pri Update

Dropped!

Low

Render

View

Frame

Frame

High Pri Update

High

Render

View

  • User input updates > other updates
  • Low pri blocks High pri

THE

KEY

INSIGHT

Maybe we shouldn't push everything

Push DATA flow

  • "Reactive"
  • Real-time
  • Data in motion
  • e.g. Things you receive
    • Spreadsheets
    • Podcasts
    • Events

pull data flow

  • Imperative
  • Lazy, "On-demand"
  • Data at rest
  • e.g. Things you check
    • Todo List
    • Github PR
    • Sight & other senses

Data Producers

Data Consumers

PuSH vs puLL 2x2

Data Flows Push Pull
Single Promise Function Call
Multiple Callback/Observable Generator/Fiber
promise.then(data => {
    //...
})
let data = fn()
source$.subscribe(data => {
    //...
})
let gen = generatorFn()

let data = gen.next().value

Fiber :: Generåtor

Push + Pull MENTAL MODEL

Pull

Push

Update

View

Update

Queue
+
Render

Push whenever we want into the Queue

Pull whenever we want to render a Frame

Update

Present Day React: Batching updates

Blue = imperative

Red = reactive

Scheduler

React : "UI Virtual Machine" :: Fiber Scheduler : "React runtime system"

  • Not just a passive rendering layer
    • enqueueUpdate
    • addRootToSchedule/scheduleWork
    • and much more!
  • Lets you write Reactive Component API
    • But lazily pulls for performance

Queue
+
Render

Browsers reduce operating systems to a poorly debugged set of device drivers

@PMARCA

@bobmetcalfe

1995

React IS A virtual operating system that reduces BROWSERS TO A RENDER TARGET FOR
your apps

random react fanboy

2018

VIEW Runtime = f*(DATA stream)

v = f*(d$)

Fiber Scheduling

(more control than generators)

Seriously

where is the #SCHEDUlove

BUT WAIT

THERE'S MORE

You can use the mental model for so many things....

Push + Pull++

View

Low Pri

Low

WIP Priority Queue

High

Frame

Frame

High Pri

High

High
+
Low

Time Sliced Rendering

High Pri

Render

Current Queue

Low Pri

Priority Queue
+
Render

Who writes all the bits that fire actions?

Sunil Pai

Push + Pull++

Pull

Push

Update

Queue
+
Render

🌀

Frame

Frame

Side Effects

Source

Loaded!

Update

Queue
+
Render

  • .then(setState)
  • Redux Thunk
  • MobX
  • etc

App + Side Effects

React

Today's React needs imperative side effects

Push + Pull++

Pull

Push

Update

PQueue

+
Render

+

Cache Miss

🌀

Frame

Frame

Side Effects

Source

Loaded!

App

React

Cache Library

PQueue

+
Render

+

Cache Read

Suspense resolves Local Data Deps in Render

Suspend

Resume

More analogies

You use:

  • Podcatchers for Podcasts
  • Email Apps for Newsletters
  • Netflix for Television
  • Issue trackers for Issues

Push + pull is everywhere!!!

STORY

QUESTION

MENTAL MODEL

Reactive is 👍 for UIs

React can be fully reactive. Why isn't it?

BATCHING, Render cost, Push + Pull

#SchedulOVE

SPread the #SCHEDULOVE

 

swyx.io/ReactRally
(Slides, Github, Feedback, Further Reading)

Made with Slides.com