The Interactive Experience TM
π
π
Alfred North Whitehead
π
π
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);
});
}
π
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
π
π
π
π
π
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.
A Brief History
π
A Brief History
π
A Brief History
π
A Brief History
π
March 2018
π
π
A fundamentally new capability that
π
π
π
(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.
π©
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Β π
π
Alfred North Whitehead
π
π
π
π