Lab 18: HTTP GET
Objectives
- Create an API object that loads data from an REST API
- Update a component to use the API object
- Add Pagination
Steps
Create an API object that loads data from an REST API
- Create the file - src\projects\projectAPI.ts.
- Create a - projectAPIobject and export it from the file.
- Implement a - getmethod that requires- pageand- limitparameters and sets the default to- page = 1and- limit=20. The projects should be sorted by name.- src\projects\projectAPI.tsimport { Project } from './Project';const baseUrl = 'http://localhost:4000';const url = `${baseUrl}/projects`;function translateStatusToErrorMessage(status: number) {switch (status) {case 401:return 'Please login again.';case 403:return 'You do not have permission to view the project(s).';default:return 'There was an error retrieving the project(s). Please try again.';}}function checkStatus(response: any) {if (response.ok) {return response;} else {const httpErrorInfo = {status: response.status,statusText: response.statusText,url: response.url,};console.log(`log server http error: ${JSON.stringify(httpErrorInfo)}`);let errorMessage = translateStatusToErrorMessage(httpErrorInfo.status);throw new Error(errorMessage);}}function parseJSON(response: Response) {return response.json();}// eslint-disable-next-linefunction delay(ms: number) {return function (x: any): Promise<any> {return new Promise((resolve) => setTimeout(() => resolve(x), ms));};}function convertToProjectModels(data: any[]): Project[] {let projects: Project[] = data.map(convertToProjectModel);return projects;}function convertToProjectModel(item: any): Project {return new Project(item);}const projectAPI = {get(page = 1, limit = 20) {return fetch(`${url}?_page=${page}&_limit=${limit}&_sort=name`).then(delay(600)).then(checkStatus).then(parseJSON).then(convertToProjectModels).catch((error: TypeError) => {console.log('log client error ' + error);throw new Error('There was an error retrieving the projects. Please try again.');});},};export { projectAPI };
Update a component to use the API object
- Open the file - src\projects\ProjectsPage.tsx.
- Use the - useStatefunction to create two additonal state variables- loadingand- error.- src\projects\ProjectsPage.tsx...function ProjectsPage() {const [projects, setProjects] = useState<Project[]>(MOCK_PROJECTS);+ const [loading, setLoading] = useState(false);+ const [error, setError] = useState<string | undefined>(undefined);...}- DO NOT DELETE the file - src\projects\MockProjects.ts. We will use it in our unit testing.
- Change the - projectsstate to be an empty array- [](be sure to remove the mock data).- src\projects\ProjectsPage.tsx- import { MOCK_PROJECTS } from './MockProjects';...function ProjectsPage() {- const [projects, setProjects] = useState<Project[]>(MOCK_PROJECTS);+ const [projects, setProjects] = useState<Project[]>([]);const [loading, setLoading] = useState(false);const [error, setError] = useState<string | undefined>(undefined);...}
- Implement the loading of the data from the API after the intial component render in a - useEffecthook. Follow these specifications.- Set state of loadingtotrue
- Call the API: projectAPI.get(1).
- If successful, set the returned datainto the componentsprojectsstate variable and set theloadingstate variable tofalse.
- If an error occurs, set the returned error's message error.messageto the componentserrorstate andloadingtofalse.
 - src\projects\ProjectsPage.tsx
- Set state of 
- Display the loading indicator below the - <ProjectList />. Only display the indicator when- loading=true.- If you want to try it yourself first before looking at the solution code use the - HTMLsnippet below to format the loading indicator.<div class="center-page"><span class="spinner primary"></span><p>Loading...</p></div>- src\projects\ProjectsPage.tsxfunction ProjectsPage() {const [projects, setProjects] = useState<Project[]>([]);const [loading, setLoading] = useState(false);const [error, setError] = useState<string | undefined>(undefined);...return (<Fragment><h1>Projects</h1><ProjectList onSave={saveProject} projects={projects} />+ {loading && (+ <div className="center-page">+ <span className="spinner primary"></span>+ <p>Loading...</p>+ </div>+ )}</Fragment>);}export default ProjectsPage;
- Add these - CSSstyles to center the loading indicator on the page.- src\index.css... //add below existing styleshtml,body,#root,.container,.center-page {height: 100%;}.center-page {display: flex;justify-content: center;align-items: center;}
- Display the error message above the - <ProjectList />using the- HTMLsnippet below. Only display the indicator when- erroris defined.- If you want to try it yourself first before looking at the solution code use the - HTMLsnippet below to format the error.<div class="row"><div class="card large error"><section><p><span class="icon-alert inverse "></span>{error}</p></section></div></div>- src\projects\ProjectsPage.tsxfunction ProjectsPage() {const [projects, setProjects] = useState<Project[]>([]);const [loading, setLoading] = useState(false);const [error, setError] = useState<string | undefined>(undefined);...return (<><h1>Projects</h1>+ {error && (+ <div className="row">+ <div className="card large error">+ <section>+ <p>+ <span className="icon-alert inverse "></span>+ {error}+ </p>+ </section>+ </div>+ </div>+ )}<ProjectList onSave={saveProject} projects={projects} />{loading && (<div className="center-page"><span className="spinner primary"></span><p>Loading...</p></div>)}</>);}export default ProjectsPage;
- Verify the application is working by following these steps in your - Chromebrowser.- Open the application on - http://localhost:3000.
- Open - Chrome DevTools.
- Refresh the page. 
- For a brief second, a loading indicator should appear.  
- Then, a list of projects should appear. 
- Click on the - Chrome DevTools- Networktab.
- Verify the request to - /projects?_page=1&_limit=20&_sort=nameis happening. 
- We are using a - delayfunction in- projectAPI.get()to delay the returning of data so it is easier to see the loading indicator. You can remove the- delayat this point.- src\projects\projectAPI.tsreturn fetch(`${url}?_page=${page}&_limit=${limit}&_sort=name`)- .then(delay(600)).then(checkStatus).then(parseJSON);
- Change the URL so the API endpoint cannot be reached. - src\projects\projectAPI.tsconst baseUrl = 'http://localhost:4000';- const url = `${baseUrl}/projects`;+ const url = `${baseUrl}/fail`;...
- In your browser, you should see the following error message displayed.  
- Fix the URL to the backend API before continuing to the next lab. - src\projects\projectAPI.tsx...const baseUrl = 'http://localhost:4000';+ const url = `${baseUrl}/projects`;- const url = `${baseUrl}/fail`;...
 
Add Pagination
- Use the - useStatefunction to create an additonal state variable- currentPage.- src\projects\ProjectsPage.tsx...function ProjectsPage() {const [projects, setProjects] = useState<Project[]>([]);const [loading, setLoading] = useState(false);const [error, setError] = useState<string | undefined>(undefined);+ const [currentPage, setCurrentPage] = useState(1);...}
- Update the - useEffectmethod to make- currentPagea dependency and use it when fetching the data.- src\projects\ProjectsPage.tsx
- Implement a - handleMoreClickevent handler and implement it by incrementing the page and then calling- loadProjects.- src\projects\ProjectsPage.tsx...function ProjectsPage() {...const [currentPage, setCurrentPage] = useState(1);...+ const handleMoreClick = () => {+ setCurrentPage((currentPage) => currentPage + 1);+ };...}
- Add a - More...button below the- <ProjectList />. Display the- More...button only when not- loadingand there is not an- errorand handle the- More...button's- clickevent and call- handleMoreClick.- src\projects\ProjectsPage.tsx...function ProjectsPage() {...return (<Fragment><h1>Projects</h1>{error && (<div className="row"><div className="card large error"><section><p><span className="icon-alert inverse "></span>{error}</p></section></div></div>)}<ProjectList onSave={saveProject} projects={projects} />+ {!loading && !error && (+ <div className="row">+ <div className="col-sm-12">+ <div className="button-group fluid">+ <button className="button default" onClick={handleMoreClick}>+ More...+ </button>+ </div>+ </div>+ </div>+ )}{loading && (<div className="center-page"><span className="spinner primary"></span><p>Loading...</p></div>)}</Fragment>);}export default ProjectsPage;
- Verify the application is working by following these steps in your browser. - Refresh the page.
- A list of projects should appear.
- Click on the More...button.
- Verify that 20 additional projects are appended to the end of the list.
- Click on the More...button again.
- Verify that another 20 projects are appended to the end of the list.
   