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.js.Create a
projectAPIobject and export it from the file.Implement a
getmethod that requirespageandlimitparameters and sets the default topage = 1andlimit=20. The projects should be sorted by name.json-server supports sorting and paging using the following syntax.
`${url}?_page=${page}&_limit=${limit}&_sort=name`;src\projects\projectAPI.jsimport { Project } from "./Project";const baseUrl = "http://localhost:4000";const url = `${baseUrl}/projects`;function translateStatusToErrorMessage(status) {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) {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) {return response.json();}// eslint-disable-next-linefunction delay(ms) {return function (x) {return new Promise((resolve) => setTimeout(() => resolve(x), ms));};}const projectAPI = {get(page = 1, limit = 20) {return fetch(`${url}?_page=${page}&_limit=${limit}&_sort=name`).then(delay(600)).then(checkStatus).then(parseJSON).then((projects) => {return projects.map((p) => {return new Project(p);});}).catch((error) => {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.js.Use the
useStatefunction to create two additonal state variablesloadinganderror.src\projects\ProjectsPage.js...function ProjectsPage() {const [projects, setProjects] = useState(MOCK_PROJECTS);+ const [loading, setLoading] = useState(false);+ const [error, setError] = useState(undefined);...}DO NOT DELETE the file
src\projects\MockProjects.js. 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.js- import { Project } from './Project';- import { MOCK_PROJECTS } from './MockProjects';...function ProjectsPage() {- const [projects, setProjects] = useState(MOCK_PROJECTS);+ const [projects, setProjects] = useState([]);const [loading, setLoading] = useState(false);const [error, setError] = useState(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.js- Set state of
Display the loading indicator below the
<ProjectList />. Only display the indicator whenloading=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.jsfunction ProjectsPage() {const [projects, setProjects] = useState<Project[]>([]);const [loading, setLoading] = useState(false);const [error, setError] = useState(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 theHTMLsnippet below. Only display the indicator whenerroris 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.jsfunction ProjectsPage() {const [projects, setProjects] = useState<Project[]>([]);const [loading, setLoading] = useState(false);const [error, setError] = useState(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 DevToolsNetworktab.Verify the request to
/projects?_page=1&_limit=20&_sort=nameis happening.
We are using a
delayfunction inprojectAPI.get()to delay the returning of data so it is easier to see the loading indicator. You can remove thedelayat this point.src\projects\projectAPI.jsreturn 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.jsconst 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.js...const baseUrl = 'http://localhost:4000';+ const url = `${baseUrl}/projects`;- const url = `${baseUrl}/fail`;...
Add Pagination
Use the
useStatefunction to create an additonal state variablecurrentPage.src\projects\ProjectsPage.js...function ProjectsPage() {const [projects, setProjects] = useState([]);const [loading, setLoading] = useState(false);const [error, setError] = useState(undefined);+ const [currentPage, setCurrentPage] = useState(1);...}Update the
useEffectmethod to makecurrentPagea dependency and use it when fetching the data.src\projects\ProjectsPage.js
Implement a
handleMoreClickevent handler and implement it by incrementing the page and then callingloadProjects.src\projects\ProjectsPage.js...function ProjectsPage() {...const [currentPage, setCurrentPage] = useState(1);...+ const handleMoreClick = () => {+ setCurrentPage((currentPage) => currentPage + 1);+ };...}Add a
More...button below the<ProjectList />. Display theMore...button only when notloadingand there is not anerrorand handle theMore...button'sclickevent and callhandleMoreClick.src\projects\ProjectsPage.js...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.
