Skip to content

Commit 502c9ae

Browse files
committed
15 context ported
1 parent 7332659 commit 502c9ae

18 files changed

Lines changed: 678 additions & 0 deletions

hooks/15_Context/.babelrc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"presets": [
3+
[
4+
"@babel/preset-env",
5+
{
6+
"useBuiltIns": "entry"
7+
}
8+
]
9+
]
10+
}

hooks/15_Context/Readme.md

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
# 15 Context
2+
3+
In this sample we are going to learn how React 16 context api works.
4+
5+
This will allow us to share information between components without having to go through props drilldown or having to add redux support to our project.
6+
7+
We will take a startup point sample 15 Context:
8+
9+
## Steps
10+
11+
- We want to store just the _login_ field once the user logs in and display it in the page B (or in wathever page or component we need it), let's add a default value ('no user').
12+
13+
- Let's start by creating a context, we will call it _sessionContext_, and add the proper typing
14+
15+
_./src/common/sessionContext.tsx_
16+
17+
```javascript
18+
import * as React from "react";
19+
20+
export interface SessionContextProps {
21+
login: string;
22+
updateLogin: value => void;
23+
}
24+
25+
export const createDefaultUser = (): SessionContextProps => ({
26+
login: "no user",
27+
updateLogin: value => {
28+
console.warn(
29+
"if you are reading this, likely you forgot to add the provider on top of your app"
30+
);
31+
}
32+
});
33+
34+
export const SessionContext =
35+
React.createContext < SessionContextProps > createDefaultUser();
36+
```
37+
38+
- This session context will expose a _provider_ (it will serve us to set the login name in the context), and a _consumer_ (that will let us consume the login name from the context at any point of the application).
39+
We will create a component (we will name it _SessionProvider_) that on one hand will store in the state the login name and bind it to the _SessionContext_ and on the other hand it will act as a wrapper (usually it will sit on top of the application and wrap the application).
40+
41+
_./src/common/sessionContext.tsx_
42+
43+
Append this to the bottom of the file.
44+
45+
```typescript
46+
export const SessionProvider: React.StatelessComponent = props => {
47+
const [login, setLogin] = React.useState<string>("");
48+
49+
return (
50+
<SessionContext.Provider value={{ login, updateLogin: setLogin }}>
51+
{props.children}
52+
</SessionContext.Provider>
53+
);
54+
};
55+
```
56+
57+
- Let's import the _SessionProvider_ in the barrel _index_.
58+
59+
_./src/common/index.ts_
60+
61+
```diff
62+
export * from "./notification";
63+
export * from "./textFieldForm";
64+
+ export * from "./sessionContext";
65+
```
66+
67+
- Let's setup the _sessionProvider_ at the top of our application.
68+
69+
_./src/app.tsx_
70+
71+
```diff
72+
import * as React from "react";
73+
import { HashRouter, Switch, Route } from "react-router-dom";
74+
import { LoginPage } from "./pages/loginPage";
75+
import { PageB } from "./pages/pageB";
76+
+ import { SessionProvider } from "./common";
77+
78+
export const App = () => {
79+
return (
80+
<>
81+
+ <SessionProvider>
82+
<HashRouter>
83+
<Switch>
84+
<Route exact={true} path="/" component={LoginPage} />
85+
<Route path="/pageB" component={PageB} />
86+
</Switch>
87+
</HashRouter>
88+
+ </SessionProvider>
89+
</>
90+
);
91+
};
92+
93+
```
94+
95+
- Let's access the context in PageB using the _useContext_.
96+
97+
_./src/pages/pageB.tsx_
98+
99+
```diff
100+
import * as React from "react";
101+
import { Link } from "react-router-dom";
102+
+ import { SessionContext } from '../common';
103+
104+
- export const PageB = () => (
105+
+ export const PageB = () => {
106+
+
107+
+ const loginContext = React.useContext(SessionContext);
108+
+
109+
+ return (
110+
<div>
111+
<h2>Hello from page B</h2>
112+
+ <h3>User logged in: {loginContext.login}</h3>
113+
<br />
114+
<Link to="/">Navigate to Login</Link>
115+
</div>
116+
+ )
117+
+}
118+
```
119+
120+
- Let's update the loginPage.
121+
122+
_./src/pages/loginPage.tsx_
123+
124+
```diff
125+
+ import { TextFieldForm } from "../common";
126+
import { TextFieldForm, SessionContext } from "../common";
127+
128+
```
129+
130+
_./src/pages/loginPage.tsx_
131+
132+
```diff
133+
const LoginPageInner = (props: Props) => {
134+
+ const loginContext = React.useContext(SessionContext);
135+
136+
const [loginInfo, setLoginInfo] = React.useState<LoginEntity>(
137+
createEmptyLogin()
138+
);
139+
const [loginFormErrors, setLoginFormErrors] = React.useState<LoginFormErrors>(
140+
createDefaultLoginFormErrors()
141+
);
142+
const [showLoginFailedMsg, setShowLoginFailedMsg] = React.useState(false);
143+
const { classes } = props;
144+
145+
const onLogin = () => {
146+
loginFormValidation.validateForm(loginInfo).then(formValidationResult => {
147+
if (formValidationResult.succeeded) {
148+
if (isValidLogin(loginInfo)) {
149+
props.history.push("/pageB");
150+
+ loginContext.updateLogin(loginInfo.login);
151+
} else {
152+
setShowLoginFailedMsg(true);
153+
}
154+
} else {
155+
alert("error, review the fields");
156+
const updatedLoginFormErrors = {
157+
...loginFormErrors,
158+
...formValidationResult.fieldErrors
159+
};
160+
setLoginFormErrors(updatedLoginFormErrors);
161+
}
162+
});
163+
};
164+
```
165+
166+
- Let's give a try.
167+
168+
# About Basefactor + Lemoncode
169+
170+
We are an innovating team of Javascript experts, passionate about turning your ideas into robust products.
171+
172+
[Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services.
173+
174+
[Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services.
175+
176+
For the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend

hooks/15_Context/package.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"name": "react-typescript-by-sample",
3+
"version": "1.0.0",
4+
"description": "React Typescript examples",
5+
"main": "index.js",
6+
"scripts": {
7+
"start": "webpack-dev-server --mode development --inline --hot --open",
8+
"build": "webpack --mode development"
9+
},
10+
"keywords": [
11+
"react",
12+
"typescript",
13+
"hooks"
14+
],
15+
"author": "Braulio Diez Botella",
16+
"license": "MIT",
17+
"devDependencies": {
18+
"@babel/cli": "^7.2.3",
19+
"@babel/core": "^7.2.2",
20+
"@babel/polyfill": "^7.2.5",
21+
"@babel/preset-env": "^7.3.1",
22+
"@material-ui/core": "^3.9.2",
23+
"@material-ui/icons": "^3.0.2",
24+
"@types/react": "^16.8.3",
25+
"@types/react-dom": "^16.8.1",
26+
"@types/react-router-dom": "^4.3.1",
27+
"awesome-typescript-loader": "^5.2.1",
28+
"babel-loader": "^8.0.5",
29+
"css-loader": "^2.1.0",
30+
"file-loader": "^3.0.1",
31+
"html-webpack-plugin": "^3.2.0",
32+
"mini-css-extract-plugin": "^0.5.0",
33+
"style-loader": "^0.23.1",
34+
"typescript": "^3.3.3",
35+
"url-loader": "^1.1.2",
36+
"webpack": "^4.29.3",
37+
"webpack-cli": "^3.2.3",
38+
"webpack-dev-server": "^3.1.14"
39+
},
40+
"dependencies": {
41+
"lc-form-validation": "^2.0.0",
42+
"react": "^16.8.2",
43+
"react-dom": "^16.8.2",
44+
"react-router-dom": "^4.3.1"
45+
}
46+
}

hooks/15_Context/src/api/login.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import {LoginEntity} from '../model/login';
2+
3+
// Just a fake loginAPI
4+
export const isValidLogin = (loginInfo : LoginEntity) : boolean =>
5+
(loginInfo.login === 'admin' && loginInfo.password === 'test');

hooks/15_Context/src/app.tsx

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import * as React from "react";
2+
import { HashRouter, Switch, Route } from "react-router-dom";
3+
import { LoginPage } from "./pages/loginPage";
4+
import { PageB } from "./pages/pageB";
5+
import { SessionProvider } from "./common";
6+
7+
export const App = () => {
8+
return (
9+
<>
10+
<SessionProvider>
11+
<HashRouter>
12+
<Switch>
13+
<Route exact={true} path="/" component={LoginPage} />
14+
<Route path="/pageB" component={PageB} />
15+
</Switch>
16+
</HashRouter>
17+
</SessionProvider>
18+
</>
19+
);
20+
};
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export * from "./notification";
2+
export * from "./textFieldForm";
3+
export * from "./sessionContext";
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import * as React from "react";
2+
import Button from "@material-ui/core/Button";
3+
import Snackbar from "@material-ui/core/Snackbar";
4+
import IconButton from "@material-ui/core/IconButton";
5+
import CloseIcon from "@material-ui/icons/Close";
6+
import { withStyles } from "@material-ui/core";
7+
8+
interface Props {
9+
classes?: any;
10+
message: string;
11+
show: boolean;
12+
onClose: () => void;
13+
}
14+
15+
const styles = theme => ({
16+
close: {
17+
padding: theme.spacing.unit / 2
18+
}
19+
});
20+
21+
const NotificationComponentInner = (props: Props) => {
22+
const { classes, message, show, onClose } = props;
23+
24+
return (
25+
<Snackbar
26+
anchorOrigin={{
27+
vertical: "bottom",
28+
horizontal: "left"
29+
}}
30+
open={show}
31+
autoHideDuration={3000}
32+
onClose={onClose}
33+
ContentProps={{
34+
"aria-describedby": "message-id"
35+
}}
36+
message={<span id="message-id">{message}</span>}
37+
action={[
38+
<IconButton
39+
key="close"
40+
aria-label="Close"
41+
color="inherit"
42+
className={classes.close}
43+
onClick={onClose}
44+
>
45+
<CloseIcon />
46+
</IconButton>
47+
]}
48+
/>
49+
);
50+
};
51+
52+
export const NotificationComponent = withStyles(styles)(
53+
NotificationComponentInner
54+
);
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import * as React from "react";
2+
3+
export interface SessionContextProps {
4+
login: string;
5+
updateLogin: (value) => void;
6+
}
7+
8+
export const createDefaultUser = (): SessionContextProps => ({
9+
login: "no user",
10+
updateLogin: value => {
11+
console.warn(
12+
"if you are reading this, likely you forgot to add the provider on top of your app"
13+
);
14+
}
15+
});
16+
17+
export const SessionContext = React.createContext<SessionContextProps>(
18+
createDefaultUser()
19+
);
20+
21+
export const SessionProvider: React.StatelessComponent = props => {
22+
const [login, setLogin] = React.useState<string>("");
23+
24+
return (
25+
<SessionContext.Provider value={{ login, updateLogin: setLogin }}>
26+
{props.children}
27+
</SessionContext.Provider>
28+
);
29+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import * as React from "react";
2+
import TextField from "@material-ui/core/TextField";
3+
import Typography from "@material-ui/core/Typography/Typography";
4+
5+
interface Props {
6+
name: string;
7+
label: string;
8+
onChange: any;
9+
value: string;
10+
error?: string;
11+
type?: string;
12+
}
13+
14+
const defaultProps: Partial<Props> = {
15+
type: "text"
16+
};
17+
18+
const onTextFieldChange = (
19+
fieldId: string,
20+
onChange: (fieldId, value) => void
21+
) => e => {
22+
onChange(fieldId, e.target.value);
23+
};
24+
25+
export const TextFieldForm: React.StatelessComponent<Props> = props => {
26+
const { name, label, onChange, value, error, type } = props;
27+
return (
28+
<>
29+
<TextField
30+
label={label}
31+
margin="normal"
32+
value={value}
33+
type={type}
34+
onChange={onTextFieldChange(name, onChange)}
35+
/>
36+
<Typography variant="caption" color="error" gutterBottom>
37+
{props.error}
38+
</Typography>
39+
</>
40+
);
41+
};

0 commit comments

Comments
 (0)