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)

Why React is -Not- Reactive

By Shawn Swyx Wang

Why React is -Not- Reactive

Functional-reactive libraries like RxJS make it easy to understand how data changes, giving us tools to declaratively handle events and manage state. But while our render methods react to state changes, React isn’t reactive. Instead, we write imperative event-handlers, and trip up on gotchas like async setState and race conditions. Why? In this talk we build a Reactive React to show the difference between the "push" and "pull" paradigms of data flow and understand why React chooses to manage Scheduling as a core Design Principle, enabling awesome features like async rendering and Suspense!

  • 1,208
Loading comments...

More from Shawn Swyx Wang