Coding Bootcamp Week 9: API Calls, React — Router, Context & Optimistic Rendering

Frontend Development

Reem Dalvi
9 min readMar 12, 2023

Fetch | Axios

There several different ways in which API calls can be made, which is a process of sending a request to an API endpoint and receiving a response.

Fetch API is a promise-based interface which means it uses async programming and the first argument passed is the url and the method defaults to GET. We also need to parse our json body before it can be used.

fetch('https://itunes.apple.com/search?term=badbadnotgood')
.then(response => response.json())
.then(body => console.log(body));

We can also configure properties such as the method and headers to our request as below.

fetch('https://www.example.com/api/people', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'Mary' }),
}).then((response) => response.json());

Axios

Promise based HTTP client for the browser and node.js

There are several reasons why using axios is more beneficial than fetch: ease of use, handling errors, automatic serialising and deserialising of the response data and compatibility with browser and Node.js environments.

To use it, you need to install it and import it into your file, since it is promise-based, you would need to chain .then() and .catch() methods to access the http response. Unlike fetch, axios will reject the returned promise if the status code is not 2**.

import axios from 'axios' // after you run this > npm install axios

axios
.get('https://api.songList.com/v1/Aphex_Twin')
.then((response) => {
console.log(response);
})
.catch((error) => {
console.log(error);
})

You can also deal with queries like filter items by category and sort by price:

import axios from 'axios'

// a custom axios instance to make several requests to same api
const api = axios.create({
baseURL: `https://vintage-shop.onrender.com/api/`,
});
api.get('/items', {
params: {
category: 'Relic',
sortBy: 'price'
}
})
.then(response => console.log(response.data))
.catch(error => console.log(error));

The url would look like this > https://vintage-shop.onrender.com/api/items?category=Relic&sortBy=price.

For post request, the second argument will be the request body.

api.post('/users', { firstName: 'Mary', lastName: 'Doe' })

useEffect

This hook is used to manage side effects in functional components. Side effects are any external interactions that your component might have, such as fetching data from an API, updating the browser’s title, or setting up event listeners.

The purpose of the useEffect hook is to allow you to perform side effects in your component without breaking the rules of React's rendering cycle.

It takes two arguments, the first one is the effect to run and second argument is the dependencies (an array of variables which allows the effect to run again should the variables values change). If there are no dependencies for this effect, the array can be left blank.

import { useEffect } from 'react';
import { fetchUserById } from '../utils/api.js'

// userId passed as props
const UserProfile = ({ userId }) => {
const [user, setUser] = useState({});
useEffect(() => {
fetchUserById(userId).then((userFromApi) => {
setUser(userFromApi);
});
}, [userId]);
return <section>
<h2>{user.username}</h2>
<section>
};

In React, the useEffect hook can also be used to perform cleanup after a side effect. If a side effect requires cleanup, such as closing a connection to an external resource or removing an event listener, you can return a function from the useEffect hook that will be run just before the component is removed from the page (unmounted). This can help prevent memory leaks and ensure that your application runs smoothly.

In the example below, we use the useEffect hook to set up an interval that increments the count state variable every second. We return a cleanup function from useEffect that clears the interval when the component is unmounted.

import React, { useState, useEffect } from 'react';

function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount((count) => count + 1);
}, 1000);
// Return a cleanup function to clear the interval
return () => {
clearInterval(intervalId);
};
}, []);
return (
<div>
<p>Count: {count}</p>
</div>
);
}
export default Timer;

Loading patterns

loading image..

When making HTTP requests in React, they will be asynchronous and may take some time to complete. To give users feedback that the page is loading, it’s common practice to use visual loading patterns.

To implement a loading pattern, you can use state to track whether or not the data has been loaded. Once the data has been received and set in state, the loading message is replaced with the actual data. By using conditional rendering, the loading message is rendered conditionally based on whether the data has been loaded or not.

import { useEffect } from 'react';

const Users = () => {
const [users, setUsers] = useState([]);
const [isLoading, setIsLoading] = useState(true);
useEffect(() => {
fetchUsers()
.then((usersFromApi) => {
setUsers(usersFromApi);
setIsLoading(false)
});
}, []);
if (isLoading) return <p>Loading...</p>
return <ul>
{users.map((user) => {
// render each user
})}
</ul>
};

Router

React applications are generally single page applications (SPAs) where all the JavaScript required to construct the website is sent in one go, instead of individual HTML pages.

This means that the path of the URL does not matter, as everything is sent on the home path anyway. However, this has two major drawbacks: different areas of the application are not individually addressable, and we lose the use of the browser’s history.

React Router is a library that allows client-side routing, which is different from server-side routing.

In server-side routing, the server has views for every single route of our app, and the browser sends a GET request to the server for each view.

In contrast, in client-side routing used in to modern web development/ React, the server provides a single HTML file and a bundle of JavaScript. The rendering of the application happens client-side, and we can use a router to catch changes in the URL and render different components accordingly.

BrowserRouter, Routes and Route

BrowserRouter component must be wrapped around our entire app, and we can then use other components from React Router to render our components on specific paths.

The Routes component takes one or more Route components as children, and it will render the Route that matches the current path. The Routecomponent takes a path prop, which is compared to the current URL. When a match is found, the component that the Route wraps will be rendered.

By using these components, we can create a multi-page app-like experience within a single-page application. The browser’s history is also updated accordingly, so users can use the back and forward buttons to navigate between pages.

> npm i react-router-dom

// src/index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
// src/App.js
import { Routes, Route } from 'react-router-dom';
const App = () => {
return (
<div className="App">
<h1>My App </h1>
<Routes>
<Route path="/" element={<Home />} />
</Routes>
</div>
);
};
// src/components/Home.jsx
const Home = () => (
<div>
<h2>Home Page</h2>
</div>
);

Link

In React Router, using anchor tags<a>to link between pages will cause the entire application to reload, which defeats the purpose of using a single-page application.

To avoid this default behavior, React Router provides a <Link> component that removes the need for a page reload when navigating between routes. The to prop is used to set the URL, and the <Link> component can be used in a NavBar component, for example.

<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
<Link to="/topics">Topics</Link>
</nav>

useParams

React Router allows for parametric endpoints, which are routes that contain variables that can be passed in through the URL.

React Router will extract the values of these variables and make them accessible through the [match.params] prop. To make use of these variables in a functional component, we can use the useParams hook provided by React Router, which returns an object containing the URL parameters.

This can be useful for fetching data from an API based on the URL parameters. To make API calls reusable and easier to manage, it is a good idea to separate them into their own module. React Router also provides additional features such as nested routing, styling active links, and redirects.

// src/App.js
import { Routes, Route } from 'react-router-dom';

const App = () => {
return (
<div className="App">
<h1>My App </h1>
<Routes>
<Route path="/topics" element={<Topics />} />
<Route path="/topics/:topic_slug" element={<SingleTopic />} />
</Routes>
</div>
);
};
// src/components/SingleTopic.jsx
import { useParams } from 'react-router-dom';
const SingleTopic = () => {
const { topic_slug } = useParams();
return (
<div>
<h2>Rendering {topic_slug}</h2>
</div>
);
};

useSearchParams

The useSearchParams hook in React Router allows us to access and modify queries in the URL of our application. We can access the queries by calling the hook, and it returns an instance of the URLSearchParams constructor.

We can then call methods like get to retrieve the values of specific queries. The example below is for when the client makes a request to /topics?sort_by=title

// src/components/Topics.jsx
import { useSearchParams } from 'react-router-dom';

const Topics = () => (
const [searchParams, setSearchParams] = useSearchParams();
console.log(searchParams); // URLSearchParams {}
const sortByQuery = searchParams.get('sort_by'); // "title"
<section>
<h2>Topics</h2>
</section>
);

If we need to update the queries programmatically, we can use the setSearchParams function that is returned by the hook.

React Router also provides the useNavigate hook, which returns a function to navigate programmatically to a specified path or to go back or forward a specified number of steps in the browser history. This function can be useful, for example, when we need to navigate to a ‘thank you’ page after submitting a form.

Context

The Context API in React is a way to share state between components without passing it through props. It’s useful for state that needs to be shared by multiple components or that can be considered global.

Examples include logged-in users, themes, or language settings.

Contexts can be created using createContext(), and a context value can be provided to a component using a Context.Provider. The useContext hook is then used to access the context value within a component.

Optimistic Rendering

like button image

When making updates to the data via other methods such as POST or PATCH, it is important to decide whether to refresh the data or perform the UI update locally with the existing data. In cases where it is not necessary to wait for a response from the API, an optimistic approach can be taken, where UI updates are made immediately assuming that the request will be successful, example liking a post.

Error handling is also crucial when making API requests, and appropriate feedback should be given to users in case of errors. A combination of both optimistic and non-optimistic approaches can be used depending on the situation, and developers should carefully consider the impact of their decisions on user experience.

Emotional check ✖️_✖️

The bootcamp is intense, with a deluge of information being imparted to me incessantly, week after week. But we have an upcoming frontend project week, during which we will construct our React frontend page to interface with our backend project.

I am looking forward to it but I cannot deny that I am also in need of a longer weekend, to partake in some restorative slumber and perhaps some social activities for a refreshing change.

Leap through time

--

--

Reem Dalvi

I like finding parallels between biological and computer science