React Suspense:
The Interactive Experience TM
π
DISCLAIMER
π
"Civilization advances by extending the number of important operations which we can perform without thinking about them."
Alfred North Whitehead
What does React
let you do without thinking?
π
Q: Why Do We Like React?
or any other framework
π
Vanilla JS
hard to reason about
const mapboxgl = require("mapbox-gl");
const buildMarker = require("./marker");
const attractions = require("./attractions");
/*
* App State
*/
const state = {
hotels: [],
restaurants: [],
activities: []
};
/*
* Instantiate the Map
*/
mapboxgl.accessToken = "pk.eyJ1IjoiY2Fzc2lvemVuIiwiYSI6ImNqNjZydGl5dDJmOWUzM3A4dGQyNnN1ZnAifQ.0ZIRDup0jnyUFVzUa_5d1g";
const map = new mapboxgl.Map({
container: "map-canvas",
center: [-74.0, 40.731],
zoom: 12.3, // starting zoom
pitch: 35,
bearing: 20,
style: "mapbox://styles/mapbox/streets-v10"
});
/*
* Populate the list of attractions
*/
attractions.load().then(list => {
list.hotels.forEach(attraction => makeOption(attraction, "hotels-choices"));
list.restaurants.forEach(attraction => makeOption(attraction, "restaurants-choices"));
list.activities.forEach(attraction => makeOption(attraction, "activities-choices"));
});
function makeOption(attraction, selector) {
const option = new Option(attraction.name, attraction.id); // makes a new option tag
const select = document.getElementById(selector);
select.add(option);
}
/*
* Attach Event Listeners
*/
// what to do when the `+` button next to a `select` is clicked
["hotels", "restaurants", "activities"].forEach(addEventHandlerFor);
function addEventHandlerFor(attractionType) {
document.getElementById(`${attractionType}-add`).addEventListener("click", () => handleAddAttraction(attractionType));
}
// Create attraction assets (itinerary item, delete button & marker)
function handleAddAttraction(attractionType) {
const select = document.getElementById(`${attractionType}-choices`);
const selectedId = select.value;
// Find the correct attraction given the category and ID
const selectedAttraction = attractions.find(attractionType, selectedId);
// If this attraction is already on state, return
if (state[attractionType].find(attractionData => +attractionData.id === +selectedId)) return;
//Build and add attraction
buildAttractionAssets(attractionType, selectedAttraction);
}
function buildAttractionAssets(category, attraction) {
// Create the Elements that will be inserted in the dom
const removeButton = document.createElement("button");
removeButton.className = "btn btn-xs btn-danger remove btn-circle";
removeButton.append("x");
const itineraryItem = document.createElement("li");
itineraryItem.className = "itinerary-item";
itineraryItem.append(attraction.name, removeButton);
// Create the marker
const marker = buildMarker(category, attraction.place.location);
// Create an 'attractionData' object that will be added to the state.
// It contains the assets created plus all the original selected attraction data.
const attractionData = Object.assign(
{
assets: {
itineraryItem,
marker
}
},
attraction
);
// Adds the attraction to the application state
state[category].push(attractionData);
addAttractionToDOM(category, attractionData);
removeButton.addEventListener("click", function remove() {
// Stop listening for the event
removeButton.removeEventListener("click", remove);
// Remove the current attrction from the application state
const index = state[category].indexOf(attractionData);
state[category].splice(index, 1);
removeAttractionFromDOM(category, attractionData);
});
}
function addAttractionToDOM(category, attractionData) {
// Append attraction elements to the DOM & Map
document.getElementById(`${category}-list`).append(attractionData.assets.itineraryItem);
attractionData.assets.marker.addTo(map);
// Animate the map
map.flyTo({ center: attractionData.place.location, zoom: 15 });
}
function removeAttractionFromDOM(category, attractionData) {
// Remove attraction's elements from the dom & Map
attractionData.assets.itineraryItem.remove();
attractionData.assets.marker.remove();
// Animate map to default position & zoom.
map.flyTo({ center: [-74.0, 40.731], zoom: 12.3 });
}
function buildAttractionAssets(category, attraction) {
// Create the Elements that will be inserted in the dom
const removeButton = document.createElement("button");
removeButton.className = "btn btn-xs btn-danger remove btn-circle";
removeButton.append("x");
const itineraryItem = document.createElement("li");
itineraryItem.className = "itinerary-item";
itineraryItem.append(attraction.name, removeButton);
// Create the marker
const marker = buildMarker(category, attraction.place.location);
// Create an 'attractionData' object that will be added to the state.
// It contains the assets created plus all the original selected attraction data.
const attractionData = Object.assign(
{
assets: {
itineraryItem,
marker
}
},
attraction
);
// Adds the attraction to the application state
state[category].push(attractionData);
addAttractionToDOM(category, attractionData);
removeButton.addEventListener("click", function remove() {
// Stop listening for the event
removeButton.removeEventListener("click", remove);
// Remove the current attrction from the application state
const index = state[category].indexOf(attractionData);
state[category].splice(index, 1);
removeAttractionFromDOM(category, attractionData);
});
}
π
ReactJS
Helps us Mount/Unmount
import React, {Component} from 'react'
import Navbar from './Navbar'
import Map from './Map'
import Panel from './Panel'
const attractions = require("./attractions");
const buildMarker = require("./marker");
export default class extends Component {
constructor() {
super()
this.state = {
hotels: [],
restaurants: [],
activities: []
}
this.addFunc = this.addFunc.bind(this)
this.delFunc = this.delFunc.bind(this)
}
addFunc(attractionType) {
const selectedId = document.getElementById(attractionType + '-choices').value
const selectedAttraction = attractions.find(attractionType, selectedId);
const newMarker = buildMarker(attractionType, selectedAttraction.place.location)
newMarker.addTo(this.map)
selectedAttraction.marker = newMarker
let newstate = Object.assign({}, this.state)
newstate[attractionType].push(selectedAttraction)
this.setState(newstate)
this.map.flyTo({ center: selectedAttraction.place.location, zoom: 15 });
}
delFunc(attractionType, id) {
let newstate = Object.assign({}, this.state)
newstate[attractionType] = newstate[attractionType].filter(x => {
if (x.id != id) return true
else x.marker.remove()
return false
})
this.setState(newstate)
}
render() {
return (<div>
<Navbar />
<div id="app" className="clearfix">
<Map ref={(x) => !x ? '' : this.map = x.map}/>
<Panel selections={this.state} addFunc={this.addFunc} delFunc={this.delFunc}/>
</div>
</div>)
}
}
State
Handlers
Render
π
What is Async Rendering?
π
What is Async Rendering?
π
What is Async Rendering?
π
What is Async Rendering?
π
Don't confuse with Async Data Fetching.
We've always had that.
Β
We're talking about React doing its rendering, asynchronously.
Β
that is the new thing.
Async Rendering
A Brief History
π
Async Rendering
A Brief History
π
Async Rendering
A Brief History
π
Async Rendering
A Brief History
π
Async Rendering
March 2018
π
What is React Suspense?
A generic way for components to suspend rendering while they load async data.
π
What is React Suspense?
A fundamentally new capability that
- lets you render a component tree βin backgroundβ
- while components are fetching data, and
- display them only after the whole tree is ready.
- For slow connections, it gives you full control over where and when to show a placeholder.
- It doesnβt destroy the previous view while this is happening.
π
Who made React Suspense?
π
Q: When can I have it?
A: By end 2018
π
Q: I don't like it/have time to learn it. Do I have to use it?
A: No. It is 100% opt-in.
(A: BUT it is very cool!)
π
ah I forgot the cheat sheet data.
Let me go fetch it
Β
I am notΒ throwing away my shot.
I promise I'll be back.
π©
React Suspense Cheat Sheet
Benefits/Uses βοΈ
-
Data FetchingβΒ Β
-
Code splitting
-
Img loading
-
CSS loading
-
-
Fix race conditions
-
Debouncing
-
Intentional statesβ
-
Preloading
-
Less boilerplate
-
Streaming SSR β οΈ
-
what else??βββ
Goals π
-
Render when readyβ
-
Fetch in render
-
No race defaultβ
-
Precise controlβ
-
Speculative fetch
-
Interactive AFβ
Glossary π€
- Idempotency != Purity
- Algebraic Effects
- Coroutinesβ
- Reconciliation
- Double Buffering
- Expiration β οΈ
Code π©βπ»
Primitives
Β -Β React.TimeoutβΒ Β Β Β Β Β Β Β
Β -Β React.AsyncMode
Low Level API
Β -Β simple-cache-providerβ
Β -Β Apollo/Relay cacheββ
Β - Β what else?? ββββ
High Level API (../future)
Β -Β createFetcher()
Β -Β <Placeholder delayMs>
Β -Β <Loading />
Β -Β this.deferSetState()
Β -Β <div hidden={true}> β οΈ
Β
Impact on Librariesββ
New Devtoolsβββ
by @swyxΒ π
π
"Civilization advances by extending the number of important operations which we can perform without thinking about them."
Alfred North Whitehead
What does React Suspense
let you do without thinking?
π
Pit of Success
"In stark contrast to... a journey across a desert to find victory through many trials and surprises, we want our customers to simply fall into winning practices by using our framework."
"AΒ well-designed system makes it easy to do the right things and annoying (but not impossible) to do the wrong things."
π
Links & Acknowledgements
- React Team Code
-
Community Code
- Demo Clones (one, two, three, four)
- @reactions/Fetch
- Apollo GraphQL
-
Auth0
-
Community Media
- https://dev.to/swyx/react-suspense-qa-28lc
- https://medium.com/@lmatteis/react-suspense-for-the-layman-caae7f48686f
- https://medium.com/@baphemot/understanding-react-suspense-1c73b4b0b1e6β
- https://medium.com/@pete_gleeson/creating-suspense-in-react-16-2-dcf4cb1a683f
- https://blogg.svt.se/svti/react-suspense-server-rendering/
- Harry Wolff
- Ryan Florence
π
Thank You
π
React Suspense: The Interactive Experience
By Shawn Swyx Wang
React Suspense: The Interactive Experience
- 5,007