Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 4707500

Browse filesBrowse files
committed
Updated readme
1 parent d3be32d commit 4707500
Copy full SHA for 4707500

File tree

Expand file treeCollapse file tree

1 file changed

+118
-188
lines changed
Filter options
  • 02 create API and expose it in Container
Expand file treeCollapse file tree

1 file changed

+118
-188
lines changed

‎02 create API and expose it in Container/readme.md

Copy file name to clipboardExpand all lines: 02 create API and expose it in Container/readme.md
+118-188Lines changed: 118 additions & 188 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
# 01 Hardcoded list component
1+
# 02 Create API and expose it in Container
22

3-
In this sample we are going to change our page component to add a hardcoded list of members.
3+
In this sample we are going to add an Api to our application and integrate it into our Container component, replacing the previously added hardcoded data.
44

5-
We will use as start up point _00 Boilerplate_.
5+
We will use as start up point _01 Hardcoded list component.
66

77
Summary steps:
88

9-
- Define the view model.
10-
- Create a component to show a table with a list of members.
11-
- Create a component to show a row that is part of the table component.
12-
- Modify the page component to show a table with hardcoded members.
9+
- Create a data model for the API.
10+
- Implement an API call that fetches data from the web and parses it into the previously defined data model.
11+
- Create an auxiliary mapper module to parse from the api data model to the view model used in the Container component tree.
12+
- Modify the container component to use the API calls and the subsequently returned data.
1313

1414
# Prerequisites
1515

@@ -19,205 +19,139 @@ Install [Node.js and npm](https://nodejs.org/en/) if they are not already instal
1919
2020
## Steps to build it
2121

22-
- Copy the content of the `00 Boilerplate` folder to an empty folder for the sample.
22+
- Copy the content of the `01 Hardcoded list component` folder to an empty folder for the sample.
2323

2424
- Install the npm packages described in the `package.json` and verify that it works:
2525

2626
```
2727
npm install
2828
```
29-
30-
- Let's define our view model. As we are later going to obtain a list of GitHub members, we specify that a member will have an id, a name and an avatar URL. Therefore, our _viewModel.ts_ file will contain:
29+
30+
-
31+
32+
- We will start by creating a suitable folder structure for our new API.
33+
```
34+
.
35+
└── api/
36+
└── model/
37+
├── index.ts
38+
├── member.ts
39+
├── memberApi.ts
40+
└── index.ts
41+
```
42+
43+
- First, we add an `api subfolder` inside `src`. Then, inside `src/api` we create a new subfolder named `src/api/model`
44+
45+
- Let's start by defining the data model used in our API. Our data source will be a list of GitHub members. As we know from the previous case, we want to be able to display the id, name and profile image (represented as an Url) inside our members page. Thus, the data that we really care to retrieve should hold 1 number property for the id, and 2 string properties for the user login name and avatar image Url respectively . Consequently, we will create a `member.ts` file inside `src/api/model` with the content below:
3146

3247
```javascript
3348
export interface MemberEntity {
34-
id : number;
35-
name : string;
36-
avatarUrl : string;
49+
login: string;
50+
id: number;
51+
avatar_url: string;
3752
}
3853
```
3954

40-
- Now, we are going to create a folder named _components_, under _members_ folder.
55+
- And since we want to be able to access this interface later from both the API call itself (to properly format the data fetched) and from our auxiliary mapper modulee (to ensure that we can parse from this interface to the one used internally in our view model), we will also define a barrel `index.ts` file for our `src/api/model` folder, as follows:
4156

42-
- Then, we will add some files for the components that we need to show a list of members in our page. Under _components_ folder, create _index.ts_, _memberRow.tsx_ and _memberTable.tsx_.
57+
´´´javascript
58+
export {MemberEntity} from './member';
59+
´´´
4360

44-
- The _src_ folder structure should be like the following one:
61+
- Next we can start working on our `memberApi.ts` file. We will import our data model from `./model` barrel index file. We will also need to define some constants to store the root Url for our data source service, and the specific endpoint we want to call to retrieve the list of members. We can do by adding the following lines.
4562

46-
```
47-
.
48-
└── src/
49-
└── pages/
50-
└── members/
51-
├── components/
52-
├── index.ts
53-
├── memberRow.tsx
54-
└── memberTable.tsx
55-
├── index.ts
56-
├── viewModel.ts
57-
├── container.tsx
58-
└── page.tsx
59-
└── index.html
60-
└── main.tsx
63+
```javascript
64+
import { MemberEntity } from './model';
65+
66+
const baseRoot = 'https://api.github.com/orgs/lemoncode';
67+
const membersURL = `${baseRoot}/members`
6168
```
6269

63-
- Let's create the component that will show a member's details in a row.
64-
- The properties of our component will include just a member.
65-
- Our component will return the HTML code that renders the member received in the properties argument.
66-
- Therefore, we need to add the following code to our _memberRow.tsx_ file:
70+
- We want to define a get/fetch REST call to retrieve our list of members from the server. In order to do this, we must send an aynchronous call to the server, using a Promise to store said data once it is available in our app. Thus, we define a `fetchMemberList` method that performs the aformentioned 'fetch' operation and parses the corresponding data, as follows:
6771

6872
```javascript
69-
import * as React from 'react';
70-
import { MemberEntity } from '../viewModel';
73+
export const fetchMemberList = () : Promise<MemberEntity[]> => {
74+
75+
return fetch(membersURL)
76+
.then(checkStatus)
77+
.then(parseJSON)
78+
.then(resolveMembers)
7179

72-
interface Props {
73-
member : MemberEntity;
74-
}
7580

76-
export const MemberRow = (props : Props) => (
77-
<tr>
78-
<td><img src={props.member.avatarUrl} style={{ width: '200px' }} /></td>
79-
<td>{props.member.id}</td>
80-
<td>{props.member.name}</td>
81-
</tr>
82-
);
81+
}
8382
```
8483

85-
- Now, let's create the component that will show the list of members. To do so, we need to import the MemberRow component into _memberTable.tsx_ and render it accordingly. In this case, the properties will be an array of members:
84+
- As noted in the code above, we will first fetch the results from our Url endpoint, and then we will first check that the data could be retrieved successfully, parse said data into JSON, and finally resolve said data according to the API data model we have defined.
8685

87-
```javascript
88-
import * as React from 'react';
89-
import { MemberEntity } from '../viewModel';
90-
import { MemberRow } from './memberRow';
86+
- Regarding thee `checkStatus` method, we will simply forward the response if we got an OK reply from the Backend. Otherwise, we will throw an error according to the status received. Notice that we do not need to wrap the returned value inside a Promise (for example, using `Promise.resolve()`), as the `then` call already returns a promise resolved with the data returned. Thus, we can chain then properly without incurring any typing errors on behalf of Typescript.
9187

92-
interface Props {
93-
memberList : MemberEntity[];
88+
```javascript
89+
const checkStatus = (response : Response) : Response => {
90+
if (response.status >= 200 && response.status < 300) {
91+
return response;
92+
} else {
93+
let error = new Error(response.statusText);
94+
throw error;
95+
}
9496
}
95-
96-
export const MemberTable = (props : Props) => (
97-
<table className="table">
98-
<thead>
99-
<tr>
100-
<th>Picture</th>
101-
<th>Id</th>
102-
<th>Name</th>
103-
</tr>
104-
</thead>
105-
<tbody>
106-
{
107-
props.memberList.map(
108-
(member) => <MemberRow
109-
key={member.id}
110-
member={member}
111-
/>
112-
)
113-
}
114-
</tbody>
115-
</table>
116-
);
11797
```
11898

119-
- Now, let's use barrel and export MemberTable in _./src/pages/members/components/index.ts_:
99+
- If the members data was retrieved succesfully, we then take the corresponding JSON content.
120100

121101
```javascript
122-
export { MemberTable } from './memberTable';
102+
const parseJSON = (response : Response) : any => {
103+
return response.json();
104+
}
105+
123106
```
124107

125-
- It's the moment to include our MemberTable in our _page.tsx_ component.
126-
- We need to import MemberEntity.
127-
- We need to define the properties: it will be the list of members.
128-
- We need to convert the component from a function into a class.
108+
- And finally, for each object in our data list (i.e. for each member in our members list), we will retrieve the three values we are interested in (using destructuring to make the code more concise), build a new object with these 3 values (using the short syntax for property assignment, i.e. `{id, login, avatar_url} equals {id:id, login:login, avatar_url:avatar_url})`), and finally we 'cast' our object into our api data model, as we do meet the required interface (types match).
129109

130-
```diff
131-
import * as React from 'react';
132-
+ import { MemberEntity } from './viewModel';
133-
+ import { MemberTable } from './components';
134-
135-
+ interface Props {
136-
+ memberList: MemberEntity[];
137-
+ fetchMemberList: () => void;
138-
+ }
139-
140-
- export const MemberListPage = () => (
141-
- <h1>Hello from member list page</h1>
142-
- );
143-
144-
+ export class MemberListPage extends React.Component<Props, {}> {
145-
+
146-
+ render() {
147-
+ return (
148-
+ <MemberTable
149-
+ memberList={this.props.memberList}
150-
+ />
151-
+ );
152-
+ }
153-
+ }
110+
```javascript
111+
const resolveMembers = (data : any) : MemberEntity[] => {
112+
const members = data.map(
113+
({id, login, avatar_url,}) => ({ id, login, avatar_url, } as MemberEntity)
114+
);
154115

116+
return members;
117+
}
155118
```
156119

157-
- Now, we have to modify _container.tsx_.
158-
- We need to import MemberEntity.
159-
- We need to define the State: it will be the list of members.
160-
- We need to convert the component from a function into a class.
161120

162-
```diff
163-
import * as React from 'react';
164-
import { MemberListPage } from './page';
165-
+ import { MemberEntity } from './viewModel';
166-
167-
+ interface State {
168-
+ memberList : MemberEntity[];
169-
+ }
170-
171-
- export class MemberListContainer extends React.Component<{}, {}> {
172-
- render() {
173-
- return (
174-
- <MemberListPage/>
175-
- );
176-
- }
177-
- }
178-
179-
+ export class MemberListContainer extends React.Component<{}, State> {
180-
+
181-
+ constructor(props) {
182-
+ super(props);
183-
+ this.state = { memberList: [] };
184-
+ }
185-
+
186-
+ render() {
187-
+ return (
188-
+ <MemberListPage
189-
+ memberList={this.state.memberList}
190-
+ />
191-
+ );
192-
+ }
193-
+ }
194-
```
121+
- We have finished our API, now we need to do some changes on our container file and folder to properly expose the API to it.
195122

196-
- At this point, there is a piece missing: when our page is created, we need a call to get the list of members whenever it is ready. We will do it in _page.tsx_ using the method _componentDidMount()_.
123+
- First, we will create a new file inside our `src/pages/members` folder called `mapper.ts`. This will be an auxiliary file that parses between our api data model and the view model used in our components. The code we need to add would be the following:
197124

198-
```diff
199-
interface Props {
200-
memberList: MemberEntity[];
201-
+ fetchMemberList: () => void;
202-
}
125+
```javascript
126+
import * as apiModel from '../../api/model';
127+
import * as vm from './viewModel';
128+
129+
const mapMemberFromModelToVm = (member: apiModel.MemberEntity) : vm.MemberEntity => (
130+
{
131+
id: member.id,
132+
avatarUrl: member.avatar_url,
133+
name: member.login,
134+
}
135+
)
203136

204-
export class MemberListPage extends React.Component<Props, {}> {
137+
export const mapMemberListFromModelToVm = (memberList: apiModel.MemberEntity[]) : vm.MemberEntity[] => (
138+
memberList.map(mapMemberFromModelToVm)
139+
)
140+
```
205141

206-
+ componentDidMount() {
207-
+ this.props.fetchMemberList();
208-
+ }
142+
- The method `mapMemberListFromModelToVm` is the one that actually maps the members list retrieved from the Backend into the data model used in our components. Internally, it will call `mapMemberFromModelToVm` to process and parse each member object inside the list. We do not need to use this parsing method outside of our container, so we will not be adding any methods from `mapper.ts` into the `index.ts` file of our container folder.
209143

210-
render() {
211-
return (
212-
<MemberTable
213-
memberList={this.props.memberList}
214-
/>
215-
);
216-
}
217-
}
144+
- The last steps remaining will revolve around changing the code of our `container.tsx` component to use the new API endpoint alongside our mapper's parsing method. First, we will start by adding the new dependencies to our file header.
145+
146+
```diff
147+
import * as React from 'react';
148+
import { MemberListPage } from './page';
149+
import { MemberEntity } from './viewModel';
150+
+ import { fetchMemberList } from '../../api';
151+
+ import { mapMemberListFromModelToVm } from './mapper';
218152
```
219153

220-
- Now, what we need to do is to simulate how to get the list of members. As it should normally be an asynchronous call, we will use a timeout to return a list of hardcoded members in _container.tsx_.
154+
- And finally, we will replace the hardcoded block of the `fetchMembers` method to use instead a call to our `fetchMemberList` API endpoint
221155

222156
```diff
223157
export class MemberListContainer extends React.Component<{}, State> {
@@ -227,37 +161,33 @@ export class MemberListPage extends React.Component<Props, {}> {
227161
this.state = { memberList: [] };
228162
}
229163

230-
+ fetchMembers = () => {
231-
+ setTimeout(() => {
164+
fetchMembers = () => {
165+
- setTimeout(() => {
166+
- this.setState({
167+
- memberList: [
168+
- {
169+
- id: 1,
170+
- name: 'John',
171+
- avatarUrl: 'https://avatars1.githubusercontent.com/u/1457912?v=4',
172+
- },
173+
- {
174+
- id: 2,
175+
- name: 'Martin',
176+
- avatarUrl: 'https://avatars2.githubusercontent.com/u/4374977?v=4',
177+
- },
178+
- ]
179+
- });
180+
- }, 500);
181+
+ fetchMemberList().then((memberList) => {
232182
+ this.setState({
233-
+ memberList: [
234-
+ {
235-
+ id: 1,
236-
+ name: 'John',
237-
+ avatarUrl: 'https://avatars1.githubusercontent.com/u/1457912?v=4',
238-
+ },
239-
+ {
240-
+ id: 2,
241-
+ name: 'Martin',
242-
+ avatarUrl: 'https://avatars2.githubusercontent.com/u/4374977?v=4',
243-
+ },
244-
+ ]
183+
+ memberList: mapMemberListFromModelToVm(memberList),
245184
+ });
246-
+ }, 500);
247-
+ }
248-
249-
render() {
250-
return (
251-
<MemberListPage
252-
memberList={this.state.memberList}
253-
+ fetchMemberList={this.fetchMembers}
254-
/>
255-
);
185+
+ });
256186
}
257-
}
187+
258188
```
259189

260-
Now if you execute `npm start` and go to `http://localhost:8080/`, you will see the list of hardcoded members.
190+
Now if you execute `npm start` and go to `http://localhost:8080/`, you will see the list of members retrieved from our Url.
261191

262192
# About Lemoncode
263193

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.