Skip to content

Commit ec9d540

Browse files
committed
Add the suspense stuff
1 parent 26f948c commit ec9d540

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+34923
-512
lines changed

08-suspense/exercise/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,10 @@
33
"version": "0.1.0",
44
"private": true,
55
"dependencies": {
6-
"react": "^16.5.0",
7-
"react-dom": "^16.5.0"
6+
"@reach/router": "^1.2.1",
7+
"react": "../vendor/react",
8+
"react-dom": "../vendor/react-dom",
9+
"simple-cache-provider": "../vendor/simple-cache-provider"
810
},
911
"devDependencies": {
1012
"react-scripts": "1.0.10"
Lines changed: 69 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,75 @@
1-
/*
2-
3-
Make these two components work like a normal <select><option/></select>
4-
component.
5-
6-
First, don't worry about accessibility, we want to illustrate controlled v.
7-
uncontrolled components first.
8-
9-
First, make the uncontrolled usage work. This means it will keep the value in
10-
state.
11-
12-
1. Get the label to display correctly based on state.
13-
2. When you click the component it opens/closes
14-
3. When you click an option the component closes and updates the value in
15-
state, and the label displays correctly
16-
17-
Now, make the uncontrolled version work. Instead of reading from state, you'll
18-
read from props, and instead of setting state, you'll need to do something
19-
else!
20-
21-
Once you've got that done, get started on making it accessible.
22-
23-
Here are some guides, but we'll be doing it together as a class, too.
24-
25-
https://www.w3.org/TR/wai-aria-practices-1.1/#Listbox
26-
https://www.w3.org/TR/wai-aria-practices-1.1/examples/listbox/listbox-collapsible.html
27-
28-
*/
29-
30-
import "./index.css";
31-
import React from "react";
32-
import PropTypes from "prop-types";
33-
34-
class Select extends React.Component {
35-
static propTypes = {
36-
onChange: PropTypes.func,
37-
value: PropTypes.any,
38-
defaultValue: PropTypes.any
39-
};
40-
41-
render() {
42-
const isOpen = false;
43-
return (
44-
<div className="select">
45-
<button className="label">
46-
label <span className="arrow"></span>
47-
</button>
48-
{isOpen && <ul className="options">{this.props.children}</ul>}
49-
</div>
50-
);
51-
}
1+
import React, { Placeholder } from "react";
2+
import { cache } from "./lib/cache";
3+
import { createResource } from "simple-cache-provider";
4+
import { Router, Link } from "@reach/router";
5+
6+
let ContactsResource = createResource(async path => {
7+
let response = await fetch(`https://contacts.now.sh${path}`);
8+
let json = await response.json();
9+
return json;
10+
});
11+
12+
let ImageResource = createResource(src => {
13+
return new Promise(resolve => {
14+
let img = new Image();
15+
img.src = src;
16+
img.onload = () => {
17+
setTimeout(() => {
18+
resolve(src);
19+
}, 3000);
20+
};
21+
});
22+
});
23+
24+
function Img({ src, ...props }) {
25+
// eslint-disable-next-line jsx-a11y/alt-text
26+
return <img src={ImageResource.read(cache, src)} {...props} />;
5227
}
5328

54-
class Option extends React.Component {
55-
render() {
56-
return <li className="option">{this.props.children}</li>;
57-
}
29+
function Home() {
30+
let { contacts } = ContactsResource.read(cache, "/contacts");
31+
return (
32+
<div>
33+
<h1>Contacts</h1>
34+
<ul>
35+
{contacts.map(contact => (
36+
<li key={contact.id}>
37+
<Link to={contact.id}>
38+
{contact.first} {contact.last}
39+
</Link>
40+
</li>
41+
))}
42+
</ul>
43+
</div>
44+
);
5845
}
5946

60-
class App extends React.Component {
61-
state = {
62-
selectValue: "dosa"
63-
};
64-
65-
setToMintChutney = () => {
66-
this.setState({
67-
selectValue: "mint-chutney"
68-
});
69-
};
70-
71-
render() {
72-
return (
73-
<div className="app">
74-
<div className="block">
75-
<h2>Uncontrolled</h2>
76-
<Select defaultValue="tikka-masala">
77-
<Option value="tikka-masala">Tikka Masala</Option>
78-
<Option value="tandoori-chicken">Tandoori Chicken</Option>
79-
<Option value="dosa">Dosa</Option>
80-
<Option value="mint-chutney">Mint Chutney</Option>
81-
</Select>
82-
</div>
83-
84-
<div className="block">
85-
<h2>Controlled</h2>
86-
<p>
87-
<button onClick={this.setToMintChutney}>Set to Mint Chutney</button>
88-
</p>
89-
<Select
90-
value={this.state.selectValue}
91-
onChange={selectValue => {
92-
this.setState({ selectValue });
93-
}}
94-
>
95-
<Option value="tikka-masala">Tikka Masala</Option>
96-
<Option value="tandoori-chicken">Tandoori Chicken</Option>
97-
<Option value="dosa">Dosa</Option>
98-
<Option value="mint-chutney">Mint Chutney</Option>
99-
</Select>
100-
</div>
101-
</div>
102-
);
103-
}
47+
function Contact({ id }) {
48+
let { contact } = ContactsResource.read(cache, `/contacts/${id}`);
49+
return (
50+
<div>
51+
<h1>
52+
{contact.first} {contact.last}
53+
</h1>
54+
<p>
55+
<Placeholder delayMs={250} fallback={<span>Loading...</span>}>
56+
<Img
57+
alt={`${contact.first} smiling, maybe`}
58+
height="250"
59+
src={contact.avatar}
60+
/>
61+
</Placeholder>
62+
</p>
63+
<p>
64+
<Link to="/">Home</Link>
65+
</p>
66+
</div>
67+
);
10468
}
10569

106-
export default App;
70+
export default () => (
71+
<Router>
72+
<Home path="/" />
73+
<Contact path=":id" />
74+
</Router>
75+
);

08-suspense/exercise/src/App.start.js

Lines changed: 89 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,102 @@
11
/*
22
3-
Make these two components work like a normal <select><option/></select>
4-
component.
5-
6-
First, don't worry about accessibility, we want to illustrate controlled v.
7-
uncontrolled components first.
8-
9-
First, make the uncontrolled usage work. This means it will keep the value in
10-
state.
11-
12-
1. Get the label to display correctly based on state.
13-
2. When you click the component it opens/closes
14-
3. When you click an option the component closes and updates the value in
15-
state, and the label displays correctly
16-
17-
Now, make the uncontrolled version work. Instead of reading from state, you'll
18-
read from props, and instead of setting state, you'll need to do something
19-
else!
20-
21-
Once you've got that done, get started on making it accessible.
3+
1. Fill in `Contact` to use `ContactsResource` to fetch the contact, use the
4+
path: `/contacts/${id}` like this:
5+
6+
2. Notice how we transition before the image loads and then the page jumps
7+
around? We're waiting for the contact data but not the image. Create an
8+
`<Img/>` component and a new `ImgResource` to delay transitioning until
9+
the image is loaded
10+
11+
Remember, `createResource` needs to return a promise
12+
13+
Tips:
14+
15+
```
16+
// you can load images programatically
17+
let image = new Image();
18+
image.src = someUrl
19+
image.onload = () => {}
20+
21+
// You can create promises out of anything, here we'll use
22+
// setTimeout to make a "sleep" promise:
23+
function sleep(ms=1000) {
24+
return new Promise((resolve, reject) => {
25+
setTimeout(resolve, ms)
26+
})
27+
}
2228
23-
Here are some guides, but we'll be doing it together as a class, too.
29+
// combine those two things to create your `ImageResource`
30+
```
2431
25-
https://www.w3.org/TR/wai-aria-practices-1.1/#Listbox
26-
https://www.w3.org/TR/wai-aria-practices-1.1/examples/listbox/listbox-collapsible.html
32+
3. Finally, render a `Placeholder` (you'll need to import it from react) around
33+
`Img` with a `2000` delayMs, and then artificially delay your ImageResource by
34+
3000ms with setTimeout. What happens when you click the links now?
2735
2836
*/
2937

30-
import "./index.css";
3138
import React from "react";
32-
import PropTypes from "prop-types";
33-
34-
class Select extends React.Component {
35-
static propTypes = {
36-
onChange: PropTypes.func,
37-
value: PropTypes.any,
38-
defaultValue: PropTypes.any
39-
};
40-
41-
render() {
42-
const isOpen = false;
43-
return (
44-
<div className="select">
45-
<button className="label">
46-
label <span className="arrow"></span>
47-
</button>
48-
{isOpen && <ul className="options">{this.props.children}</ul>}
49-
</div>
50-
);
51-
}
52-
}
53-
54-
class Option extends React.Component {
55-
render() {
56-
return <li className="option">{this.props.children}</li>;
57-
}
39+
import { cache } from "./lib/cache";
40+
import { createResource } from "simple-cache-provider";
41+
import { Router, Link } from "@reach/router";
42+
43+
let ContactsResource = createResource(async path => {
44+
let response = await fetch(`https://contacts.now.sh${path}`);
45+
let json = await response.json();
46+
return json;
47+
});
48+
49+
function Home() {
50+
let { contacts } = ContactsResource.read(cache, "/contacts");
51+
return (
52+
<div>
53+
<h1>Contacts</h1>
54+
<ul>
55+
{contacts.map(contact => (
56+
<li key={contact.id}>
57+
<Link to={contact.id}>
58+
{contact.first} {contact.last}
59+
</Link>
60+
</li>
61+
))}
62+
</ul>
63+
</div>
64+
);
5865
}
5966

60-
class App extends React.Component {
61-
state = {
62-
selectValue: "dosa"
63-
};
64-
65-
setToMintChutney = () => {
66-
this.setState({
67-
selectValue: "mint-chutney"
68-
});
69-
};
70-
71-
render() {
72-
return (
73-
<div className="app">
74-
<div className="block">
75-
<h2>Uncontrolled</h2>
76-
<Select defaultValue="tikka-masala">
77-
<Option value="tikka-masala">Tikka Masala</Option>
78-
<Option value="tandoori-chicken">Tandoori Chicken</Option>
79-
<Option value="dosa">Dosa</Option>
80-
<Option value="mint-chutney">Mint Chutney</Option>
81-
</Select>
82-
</div>
83-
84-
<div className="block">
85-
<h2>Controlled</h2>
86-
<p>
87-
<button onClick={this.setToMintChutney}>Set to Mint Chutney</button>
88-
</p>
89-
<Select
90-
value={this.state.selectValue}
91-
onChange={selectValue => {
92-
this.setState({ selectValue });
93-
}}
94-
>
95-
<Option value="tikka-masala">Tikka Masala</Option>
96-
<Option value="tandoori-chicken">Tandoori Chicken</Option>
97-
<Option value="dosa">Dosa</Option>
98-
<Option value="mint-chutney">Mint Chutney</Option>
99-
</Select>
100-
</div>
101-
</div>
102-
);
67+
let FAKE_DATA = {
68+
contact: {
69+
first: "Ryan",
70+
last: "Florence",
71+
avatar: "https://placekitten.com/510/510"
10372
}
73+
};
74+
75+
function Contact({ id }) {
76+
// don't use FAKE_DATA, use `ContactsResource`
77+
let { contact } = FAKE_DATA;
78+
return (
79+
<div>
80+
<h1>
81+
{contact.first} {contact.last}
82+
</h1>
83+
<p>
84+
<img
85+
alt={`${contact.first} smiling, maybe`}
86+
height="250"
87+
src={contact.avatar}
88+
/>
89+
</p>
90+
<p>
91+
<Link to="/">Home</Link>
92+
</p>
93+
</div>
94+
);
10495
}
10596

106-
export default App;
97+
export default () => (
98+
<Router>
99+
<Home path="/" />
100+
<Contact path=":id" />
101+
</Router>
102+
);

0 commit comments

Comments
 (0)