Why
React
is Not
Reactive
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
- Libraries
- RxJS
- Most.js
- Kefir.js
- Bacon.js
- xstream
- zen-observable
- Talks
- People
- Andre Staltz
- Ben Lesh/Tracy Lee
- Evan Czaplicki
- Erik Meijer
- Conal Elliot
this talk is not
- Full Intro to Reactive Programming
- Intro to Functional Reactive Programming
- But push-pull FRP is cool
- Intro to RxJS/xstream/Bacon/Kefir/Most
- But Jafar Husain's FEM course is 🔥
- Also John Lindquist's Egghead.io lessons
- Criticism of anyone's libraries at all
- Seriously, no call to action in this talk
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$)
Scheduling on The Platform: MDN on requestIdleCallback, cooperative scheduling and the Background Tasks API!
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
~300 LOC
~2200 LOC
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!
- 6,064