Skip to content

Commit b3f461a

Browse files
committed
Added Episode 5 source
1 parent bf38665 commit b3f461a

File tree

16 files changed

+360
-0
lines changed

16 files changed

+360
-0
lines changed

episode5/README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Contexts (Part 2)
2+
3+
ReactCasts, episode 5.
4+
5+
On the last episode I talked about how context is a cool feature but also how it has 2 problems - the fact that the API is experimental (it will end up changing) and the fact that updates might not propagate if any component in the middle of the hierarchy implements ShouldComponentUpdate. On this episode, we will tackle this problems and show how to use Context in a safe way.
6+
7+
Screencast video:
8+
https://www.youtube.com/watch?v=mwYHDXS6uSc
9+
10+
# Outline
11+
12+
- Review the problems: Experimental API that might change and change propagation
13+
- Problem 1:
14+
- Extract the context stuff from `ContentPanel` to `WithLocaleHOC`
15+
- Problem 2:
16+
- Example: Implement ShouldComponentUpdate on InternalPanel
17+
- Introduce the subscription mechanism
18+
19+
20+
## Links:
21+
ShouldComponentUpdate Documentation: https://facebook.github.io/react/docs/react-component.html#shouldcomponentupdate
22+
23+
How to safely use React context: https://medium.com/@mweststrate/how-to-safely-use-react-context-b7e343eff076#.wrea2wbqq
24+
25+
# Build & Run Instructions
26+
27+
1. To build and run the code in this directory, ensure you have [npm](https://www.npmjs.com) installed
28+
29+
2. Install
30+
```
31+
npm install
32+
```
33+
34+
3. Start the application
35+
```
36+
npm start
37+
```

episode5/package.json

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "i18n",
3+
"version": "0.1.0",
4+
"private": true,
5+
"devDependencies": {
6+
"react-scripts": "0.7.0"
7+
},
8+
"dependencies": {
9+
"react": "^15.3.2",
10+
"react-dom": "^15.3.2"
11+
},
12+
"scripts": {
13+
"start": "react-scripts start",
14+
"build": "react-scripts build",
15+
"test": "react-scripts test --env=jsdom",
16+
"eject": "react-scripts eject"
17+
}
18+
}

episode5/public/favicon.ico

24.3 KB
Binary file not shown.

episode5/public/index.html

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
7+
<!--
8+
Notice the use of %PUBLIC_URL% in the tag above.
9+
It will be replaced with the URL of the `public` folder during the build.
10+
Only files inside the `public` folder can be referenced from the HTML.
11+
12+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
13+
work correctly both with client-side routing and a non-root public URL.
14+
Learn how to configure a non-root public URL by running `npm run build`.
15+
-->
16+
<title>React App</title>
17+
</head>
18+
<body>
19+
<div id="root"></div>
20+
<!--
21+
This HTML file is a template.
22+
If you open it directly in the browser, you will see an empty page.
23+
24+
You can add webfonts, meta tags, or analytics to this file.
25+
The build step will place the bundled scripts into the <body> tag.
26+
27+
To begin the development, run `npm start`.
28+
To create a production bundle, use `npm run build`.
29+
-->
30+
</body>
31+
</html>

episode5/src/App.css

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
h1 {
2+
margin: 0;
3+
}
4+
5+
footer {
6+
position: absolute;
7+
bottom: 10px;
8+
right: 10px;
9+
color: rgba(0, 0, 0, .15);
10+
font-weight: bold;
11+
font-size: 18px;
12+
}
13+
14+
15+
.panel {
16+
position: relative;
17+
border-radius: 4px;
18+
box-shadow: rgba(0, 0, 0, .25) 0 1px 6px;
19+
background-color: #ddd;
20+
margin: 15px;
21+
padding: 10px;
22+
width: 664px;
23+
height: 400px;
24+
z-index: 10;
25+
}
26+
27+
28+
.internalPanel {
29+
position: relative;
30+
border-radius: 4px;
31+
box-shadow: rgba(0, 0, 0, .25) 0 1px 6px;
32+
background-color: #999;
33+
margin: 30px;
34+
padding: 10px;
35+
width: 600px;
36+
height: 300px;
37+
z-index: 20;
38+
}
39+
40+
41+
.contentPanel {
42+
position: relative;
43+
border-radius: 2px;
44+
border: solid 1px #777;
45+
background-color: #909090;
46+
margin: 30px;
47+
padding: 10px;
48+
width: 520px;
49+
height: 200px;
50+
z-index: 30;
51+
}
52+
53+
54+
button {
55+
font-family: inherit;
56+
font-size: 100%;
57+
padding: .5em 1em;
58+
color: #444;
59+
color: rgba(0,0,0,.8);
60+
border: 1px solid #999;
61+
border: 0 rgba(0,0,0,0);
62+
background-color: #bbb;
63+
text-decoration: none;
64+
border-radius: 2px;
65+
display: inline-block;
66+
zoom: 1;
67+
line-height: normal;
68+
white-space: nowrap;
69+
vertical-align: middle;
70+
text-align: center;
71+
cursor: pointer;
72+
user-select: none;
73+
}
74+
75+
button:hover {
76+
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#1a000000', GradientType=0);
77+
background-image: -webkit-gradient(linear,0 0,0 100%,from(transparent),color-stop(40%,rgba(0,0,0,.05)),to(rgba(0,0,0,.1)));
78+
background-image: -webkit-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));
79+
background-image: -moz-linear-gradient(top,rgba(0,0,0,.05) 0,rgba(0,0,0,.1));
80+
background-image: -o-linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));
81+
background-image: linear-gradient(transparent,rgba(0,0,0,.05) 40%,rgba(0,0,0,.1));
82+
}
83+
84+
button:active {
85+
box-shadow: 0 0 0 1px rgba(0,0,0,.15) inset,0 0 6px rgba(0,0,0,.2) inset;
86+
border-color: #000\9;
87+
}
88+
89+
button:focus {outline:0;}
90+
91+
nav {
92+
float:right;
93+
}
94+
a {
95+
margin-right: 10px;
96+
cursor: pointer;
97+
}

episode5/src/App.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import Panel from './components/Panel';
3+
import Locale from './locales/Locale';
4+
import './App.css';
5+
6+
class App extends Component {
7+
constructor(props, context) {
8+
super(props, context);
9+
this.locale = new Locale('en');
10+
}
11+
static childContextTypes = {
12+
locale: PropTypes.object
13+
}
14+
15+
state = {
16+
currentLocale: 'en'
17+
}
18+
19+
componentWillUpdate(nextProps, nextState) {
20+
this.locale.setLanguage(nextState.currentLocale);
21+
}
22+
23+
getChildContext() {
24+
return {locale: this.locale}
25+
}
26+
27+
changeLocale(locale){
28+
this.setState({currentLocale: locale})
29+
}
30+
31+
render() {
32+
return (
33+
<div>
34+
<nav>
35+
<a onClick={() => this.changeLocale('en')}>🇺🇸</a>
36+
<a onClick={() => this.changeLocale('pt')}>🇧🇷</a>
37+
</nav>
38+
<Panel />
39+
</div>
40+
);
41+
}
42+
}
43+
44+
export default App;

episode5/src/App.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import App from './App';
4+
5+
it('renders without crashing', () => {
6+
const div = document.createElement('div');
7+
ReactDOM.render(<App />, div);
8+
});
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import React, { Component, PropTypes } from 'react';
2+
import WithLocaleHOC from './WithLocaleHOC';
3+
4+
class ContentPanel extends Component {
5+
static propTypes = {
6+
locale: PropTypes.object
7+
}
8+
render() {
9+
const { locale } = this.props;
10+
return (
11+
<div className="contentPanel">
12+
<h1>{locale.strings.header}</h1>
13+
<p>
14+
{locale.strings.text}
15+
</p>
16+
<button>{locale.strings.buttonLabel}</button>
17+
<footer>ContentPanel.js</footer>
18+
</div>
19+
);
20+
}
21+
}
22+
23+
export default WithLocaleHOC(ContentPanel);
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import React, { Component } from 'react';
2+
import ContentPane from './ContentPanel';
3+
4+
class InternalPanel extends Component {
5+
shouldComponentUpdate() {
6+
return false;
7+
}
8+
render() {
9+
return (
10+
<div className="internalPanel">
11+
<ContentPane />
12+
<footer>InternalPanel.js</footer>
13+
</div>
14+
);
15+
}
16+
}
17+
18+
export default InternalPanel;

episode5/src/components/Panel.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import React, { Component } from 'react';
2+
import InternalPanel from './InternalPanel';
3+
import WithLocaleHOC from './WithLocaleHOC';
4+
5+
class Panel extends Component {
6+
render() {
7+
return (
8+
<div className="panel">
9+
<InternalPanel />
10+
<footer>{this.props.locale.strings.footer} Panel.js</footer>
11+
</div>
12+
);
13+
}
14+
}
15+
16+
export default WithLocaleHOC(Panel);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import React, { Component, PropTypes } from 'react';
2+
3+
const WithLocaleHOC = (WrappedComponent) => {
4+
return class WithLocaleHOC extends Component {
5+
static contextTypes = {
6+
locale: PropTypes.object
7+
}
8+
9+
componentDidMount() {
10+
this.context.locale.subscribe(() => this.forceUpdate());
11+
}
12+
13+
render() {
14+
const { locale } = this.context;
15+
return <WrappedComponent {...this.props} locale={locale} />
16+
}
17+
}
18+
}
19+
20+
export default WithLocaleHOC;

episode5/src/index.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
body {
2+
margin: 0;
3+
padding: 0;
4+
font-family: sans-serif;
5+
}

episode5/src/index.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import React from 'react';
2+
import ReactDOM from 'react-dom';
3+
import App from './App';
4+
import './index.css';
5+
6+
ReactDOM.render(
7+
<App />,
8+
document.getElementById('root')
9+
);

episode5/src/locales/Locale.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import en from './en.json';
2+
import pt from './pt.json';
3+
4+
const locales = {en, pt};
5+
6+
class Locale {
7+
constructor(language) {
8+
this.strings = locales[language];
9+
this.subscriptions = [];
10+
}
11+
12+
setLanguage(language) {
13+
this.strings = locales[language];
14+
this.subscriptions.forEach(cb => cb());
15+
}
16+
17+
subscribe(callback) {
18+
this.subscriptions.push(callback);
19+
}
20+
}
21+
22+
export default Locale;

episode5/src/locales/en.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"header": "Heading Text",
3+
"text": "This content is inside the third panel, and so it is also inside the internal panel and the parent panel.",
4+
"buttonLabel": "I'm a button",
5+
"footer": "Chubby Unicorns © 2016"
6+
}

episode5/src/locales/pt.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"header": "Texto de Cabeçalho",
3+
"text": "O Araketu, o Araketu quando toca, deixa todo mundo pulando que nem pipoca. O fogo é fogo! Esquenta...",
4+
"buttonLabel": "Sou um botão",
5+
"footer": "Unicórnios Gorduchos © 2016"
6+
}

0 commit comments

Comments
 (0)