Skip to content

Commit e59783d

Browse files
committed
10 Table mock Completed
1 parent 2d5d8a7 commit e59783d

19 files changed

Lines changed: 809 additions & 0 deletions

hooks/10_TableMock/.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/10_TableMock/Readme.md

Lines changed: 306 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,306 @@
1+
# 10 Table mock
2+
3+
In this sample we are going to show a table with mock data.
4+
5+
We will simulate that this method is calling an ajax api (it will return a promise),
6+
by doing this we can check how easy is to replace it by a reall call in the next
7+
example.
8+
9+
# Steps to reproduce the sample
10+
11+
- We will take as starting point sample _09 sidebar_, let's copy the content from this
12+
sample and execute _npm install_.
13+
14+
```bash
15+
npm install
16+
```
17+
18+
- Let's define a model entity:
19+
20+
- Let's define a model entity in _src/model/member.ts_:
21+
22+
_./src/model/member.ts_
23+
24+
```javascript
25+
export interface MemberEntity {
26+
id: number;
27+
login: string;
28+
avatar_url: string;
29+
}
30+
```
31+
32+
> Excercise: model is starting to grow, couldn't it be a good idea to add a barrel _index_ file
33+
> under the _model_ folder, let's do that.
34+
35+
- Let's create our fake api.
36+
37+
_./src/api/memberApi.ts_
38+
39+
```typescript
40+
import { MemberEntity } from "../model/member";
41+
42+
export const getMembersCollection = (): Promise<MemberEntity[]> => {
43+
const promise = new Promise<MemberEntity[]>((resolve, reject) => {
44+
setTimeout(
45+
() =>
46+
resolve([
47+
{
48+
id: 1457912,
49+
login: "brauliodiez",
50+
avatar_url: "https://avatars.githubusercontent.com/u/1457912?v=3"
51+
},
52+
{
53+
id: 4374977,
54+
login: "Nasdan",
55+
avatar_url: "https://avatars.githubusercontent.com/u/4374977?v=3"
56+
}
57+
]),
58+
500
59+
);
60+
});
61+
62+
return promise;
63+
};
64+
```
65+
66+
> Using promises means you are going to use ES6 or you are going to include a
67+
> polyfill to provide compatibility on ES5 (need to install the polifyll and
68+
> include it your wepback entry point).
69+
70+
- Let's jump into the ui side, to create the table component we are going to follow
71+
a progressive approach:
72+
73+
1. Create the table component but just only display the names of the members that we
74+
get back.
75+
2. Create the table itself, it would look a bit messy.
76+
3. Time to refactor, let's create a row component that will hold the row element,
77+
and check if it's worth to break into a header and body component.
78+
4. Excercise: Some developer preffer to create this child components as function
79+
method in the main component, to avoid having a mess of properties, do you think
80+
is a good idea? Try to give a try and check the pros and cons.
81+
82+
- Let's create our first version of _memberTableComponent_.
83+
84+
_./src/components/memberTable.tsx_
85+
86+
```tsx
87+
import * as React from "react";
88+
import { MemberEntity } from "../model/member";
89+
import { getMembersCollection } from "../api/memberApi";
90+
91+
const useMemberCollection = () => {
92+
const [memberCollection, setMemberCollection] = React.useState<
93+
MemberEntity[]
94+
>([]);
95+
96+
const loadMemberCollection = () => {
97+
getMembersCollection().then(memberCollection =>
98+
setMemberCollection(memberCollection)
99+
);
100+
};
101+
102+
return { memberCollection, loadMemberCollection };
103+
};
104+
105+
export const MemberTableComponent = () => {
106+
const { memberCollection, loadMemberCollection } = useMemberCollection();
107+
108+
React.useEffect(() => {
109+
loadMemberCollection();
110+
}, []);
111+
112+
return (
113+
<>
114+
{memberCollection.map(member => (
115+
<h1 key={member.id}>{member.login}</h1>
116+
))}
117+
</>
118+
);
119+
};
120+
```
121+
122+
In this component we are doing many things:
123+
124+
- First we have created a custom hook to encapsulate the getter to memberCollection and
125+
the loadMemberCollection call to the api, by doing this code is more maintenalbe plus
126+
is a potential effect to be reused in other components.
127+
128+
- We just use the custom hook that we have create in the component.
129+
130+
- To invoke the loadMemberCollection on a similar event as _componentDidMount_
131+
we invoke _React.useEffect_ passing as a second parameter and empty array _[]_
132+
we are telling react only to execute this effect when the component is mounted
133+
(if not it would call on each update), from the React hooks docs:
134+
135+
https://reactjs.org/docs/hooks-effect.html
136+
137+
_If you want to run an effect and clean it up only once (on mount and unmount), you can pass an empty array ([]) as a second argument. This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run. This isn’t handled as a special case — it follows directly from how the inputs array always works. While passing [] is closer to the familiar componentDidMount and componentWillUnmount mental model, we suggest not making it a habit because it often leads to bugs, as discussed above. Don’t forget that React defers running useEffect until after the browser has painted, so doing extra work is less of a problem._
138+
139+
- Let's include it in the components barrel.
140+
141+
_./src/components/index.ts_
142+
143+
```diff
144+
export * from "./hello";
145+
export * from "./nameEdit";
146+
export * from "./colorBrowser";
147+
export * from "./colorPicker";
148+
export * from "./sidebar";
149+
+ export * from './memberTable';
150+
```
151+
152+
- Let's instatiate this component into the app:
153+
154+
First we import it
155+
156+
```diff
157+
import * as React from "react";
158+
import {
159+
HelloComponent,
160+
NameEditComponent,
161+
ColorBrowser,
162+
ColorPicker,
163+
- SidebarComponent
164+
+ SidebarComponent,
165+
+ MemberTableComponent,
166+
} from "./components";
167+
import { Color } from "./model/color";
168+
169+
```
170+
171+
Then add it to the markup section
172+
173+
_./src/app.tsx_
174+
175+
```diff
176+
return (
177+
<>
178+
<SidebarComponent isVisible={isVisible}>
179+
<h1>Cool Scfi movies</h1>
180+
<ul>
181+
<li>
182+
<a href="https://www.imdb.com/title/tt0816692/">Interstellar</a>
183+
</li>
184+
<li>
185+
<a href="https://www.imdb.com/title/tt0083658/">Blade Runner</a>
186+
</li>
187+
<li>
188+
<a href="https://www.imdb.com/title/tt0062622/">
189+
2001: a space odyssey
190+
</a>
191+
</li>
192+
</ul>
193+
</SidebarComponent>
194+
+ <MemberTableComponent/>
195+
<ColorBrowser color={color} />
196+
<ColorPicker color={color} onColorUpdated={setColor} />
197+
```
198+
199+
- Let's run the application and check that we are getting the list of member names.
200+
201+
```bash
202+
npm start
203+
```
204+
205+
- Time to start building our table layout, first iteration.
206+
207+
_./src/components/memberTable.tsx_
208+
209+
```diff
210+
return (
211+
<>
212+
+ <table>
213+
+ <thead>
214+
+ <tr>
215+
+ <th>
216+
+ Avatar
217+
+ </th>
218+
+ <th>
219+
+ Id
220+
+ </th>
221+
+ <th>
222+
+ Name
223+
+ </th>
224+
+ </tr>
225+
+ </thead>
226+
+ <tbody>
227+
{memberCollection.map(member => (
228+
- <h1 key={member.id}>{member.login}</h1>
229+
+ <tr>
230+
+ <td>
231+
+ <img src={member.avatar_url} style ={{maxWidth: '10rem'}}/>
232+
+ </td>
233+
+ <td>
234+
+ <span>{member.id}</span>
235+
+ </td>
236+
+ <td>
237+
+ <span>{member.login}</span>
238+
+ </td>
239+
+ </tr>
240+
))}
241+
+ </tbody>
242+
+ </table>
243+
</>
244+
);
245+
246+
```
247+
248+
- If we run the application we can check that the table is displayed, but... the code
249+
is hard to read.
250+
251+
```bash
252+
npm start
253+
```
254+
255+
- Let's componentize this, we could create a _MemberRowComponent_
256+
257+
_./src/components/memberTable.tsx_
258+
259+
```diff
260+
return (
261+
<>
262+
<table>
263+
<thead>
264+
<tr>
265+
<th>Avatar</th>
266+
<th>Id</th>
267+
<th>Name</th>
268+
</tr>
269+
</thead>
270+
<tbody>
271+
{memberCollection.map(member => (
272+
+ <MemberRow key={member.id} member={member}/>
273+
- <tr>
274+
- <td>
275+
- <img src={member.avatar_url} style={{ maxWidth: "10rem" }} />
276+
- </td>
277+
- <td>
278+
- <span>{member.id}</span>
279+
- </td>
280+
- <td>
281+
- <span>{member.login}</span>
282+
- </td>
283+
- </tr>
284+
))}
285+
</tbody>
286+
</table>
287+
</>
288+
);
289+
};
290+
291+
+ const MemberRow = ({member} : {member : MemberEntity}) =>
292+
+ <tr>
293+
+ <td>
294+
+ <img src={member.avatar_url} style={{ maxWidth: "10rem" }} />
295+
+ </td>
296+
+ <td>
297+
+ <span>{member.id}</span>
298+
+ </td>
299+
+ <td>
300+
+ <span>{member.login}</span>
301+
+ </td>
302+
+ </tr>
303+
```
304+
305+
> Excercise: we could go further with the refactoring, what about creating a
306+
> _TableHeaderComponent_ component and a _tableBodyComponent_ ?.

hooks/10_TableMock/package.json

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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+
"@types/node": "^11.9.4",
23+
"@types/react": "^16.8.3",
24+
"@types/react-dom": "^16.8.1",
25+
"awesome-typescript-loader": "^5.2.1",
26+
"babel-loader": "^8.0.5",
27+
"css-loader": "^2.1.0",
28+
"file-loader": "^3.0.1",
29+
"html-webpack-plugin": "^3.2.0",
30+
"mini-css-extract-plugin": "^0.5.0",
31+
"style-loader": "^0.23.1",
32+
"typescript": "^3.3.3",
33+
"url-loader": "^1.1.2",
34+
"webpack": "^4.29.3",
35+
"webpack-cli": "^3.2.3",
36+
"webpack-dev-server": "^3.1.14"
37+
},
38+
"dependencies": {
39+
"react": "^16.8.2",
40+
"react-dom": "^16.8.2"
41+
}
42+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { MemberEntity } from "../model/member";
2+
3+
export const getMembersCollection = (): Promise<MemberEntity[]> => {
4+
const promise = new Promise<MemberEntity[]>((resolve, reject) => {
5+
setTimeout(
6+
() =>
7+
resolve([
8+
{
9+
id: 1457912,
10+
login: "brauliodiez",
11+
avatar_url: "https://avatars.githubusercontent.com/u/1457912?v=3"
12+
},
13+
{
14+
id: 4374977,
15+
login: "Nasdan",
16+
avatar_url: "https://avatars.githubusercontent.com/u/4374977?v=3"
17+
}
18+
]),
19+
500
20+
);
21+
});
22+
23+
return promise;
24+
};

0 commit comments

Comments
 (0)