Today we will complete the 5-part, 10-challenge series with two very important concepts to understand:
Creating a custom hook for data fetching
Building a responsive layout with React and CSS
Challenge 9: Create a custom hook for data fetching
First, let’s take a look at the file structure of our completed project:
├── src
│ ├── components
│ │ └── App.ts
│ ├── hooks
│ │ └── useFetchData.ts
│ └── index.ts
├── package.json
└── tsconfig.json
We are keeping things simple as you don’t need to embellish or add functionality to your app during an interview. Just focus on the challenge presented to you. So CUSTOM HOOKS. Even if you don’t know what a custom hook is, you probably remember what a hook is, and simple logic might tell you that this one will be custom as we will put whatever we want in the body and execute it! So let’s do a quick review of React’s hooks:
React Hooks:
Here’s a list of all the React hooks and their use cases:
useState
: This hook allows you to add state to functional components. You can use this hook to manage any kind of stateful data, such as form data, toggling a boolean value, or a counter.useEffect
: This hook allows you to perform side effects in functional components. You can use this hook to fetch data from an API, manipulate the DOM, or set up event listeners.useContext
: This hook allows you to access the nearestContext
object in a functional component. You can use this hook to access global data or to avoid prop drilling.useReducer
: This hook allows you to manage complex state in functional components using a reducer pattern. You can use this hook to manage state that has multiple values or requires complex updates.useCallback
: This hook allows you to memoize functions in functional components. You can use this hook to optimize the performance of your application by preventing unnecessary re-renders.useMemo
: This hook allows you to memoize values in functional components. You can use this hook to optimize the performance of your application by preventing unnecessary re-computations.useRef
: This hook allows you to create a mutable reference in functional components. You can use this hook to store a reference to a DOM node, manage focus, or cache a value between renders.useLayoutEffect
: This hook allows you to perform a side effect after the DOM has been updated but before the browser paints the screen. You can use this hook to measure the size or position of a DOM node or to animate an element.useImperativeHandle
: This hook allows you to customize the instance value that is exposed to parent components when usingref
. You can use this hook to restrict the operations that can be performed on a component or to provide a more intuitive API for your component.useDebugValue
: This hook allows you to display debugging information in React DevTools for custom hooks. You can use this hook to provide a human-readable label for a custom hook or to display additional information about its state.
Overall, these hooks provide a wide range of functionality for managing state, handling side effects, and optimizing the performance of React applications. By mastering these hooks, you can build robust and performant applications with React.
With that said, none of those are ‘custom’ hooks, which add an infinite amount of functionality to our tools… you’ll see how right now!
Custom Hooks:
Let’s start with the hook, found in hooks/useFetchData.ts
hooks/useFetchData.ts
import { useState, useEffect } from "react";
interface FetchDataOptions<TData> {
initialData?: TData;
url: string;
}
export function useFetchData<TData = any>({
initialData,
url,
}: FetchDataOptions<TData>) {
const [data, setData] = useState<TData | null>(initialData ?? null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const responseData = await response.json();
setData(responseData);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
In the example above, we define an interface FetchDataOptions
that defines the options for our custom hook. It takes in a generic type TData
which represents the type of data that we will be fetching.
interface FetchDataOptions<TData> {
initialData?: TData;
url: string;
}
We then define the useFetchData
hook which takes in an object of type FetchDataOptions
and returns an object with three properties:
data
: This is the data that we fetched. It's initially set tonull
, but will be updated once we receive a response from the server.loading
: This is a boolean that indicates whether the data is currently being fetched. It's initially set totrue
, but will be set tofalse
once the data has been received.error
: This is anError
object that will be set if there was an error while fetching the data.
export function useFetchData<TData = any>({
initialData,
url,
}: FetchDataOptions<TData>) {
const [data, setData] = useState<TData | null>(initialData ?? null);
const [loading, setLoading] = useState<boolean>(true);
const [error, setError] = useState<Error | null>(null);
Inside the useEffect
hook, we define an asynchronous function fetchData
that will make a fetch
request to the specified url
. We then update the state based on the response that we receive.
useEffect(() => {
async function fetchData() {
try {
const response = await fetch(url);
const responseData = await response.json();
setData(responseData);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}
fetchData();
}, [url]);
return { data, loading, error };
}
NOTE: The async/await
syntax and try/catch
blocks are important features of modern JavaScript that allow you to write asynchronous code that is easier to read and understand.
In the useFetchData
hook example provided earlier, async/await
is used to perform an asynchronous fetch
request to the specified url
. The await
keyword is used to pause the execution of the fetchData
function until the fetch
request is complete and the response has been returned.
Here’s a breakdown of the async/await
syntax used in the useFetchData
hook:
async function fetchData() {
try {
const response = await fetch(url);
const responseData = await response.json();
setData(responseData);
} catch (error) {
setError(error);
} finally {
setLoading(false);
}
}
async function
: This keyword combination is used to define an asynchronous function. An asynchronous function always returns a promise.await
: This keyword is used to pause the execution of the function until the promise is resolved or rejected.try/catch
: This is a standard JavaScript error handling syntax that allows you to catch any errors that might occur during the execution of thefetch
request. If an error occurs, it will be caught and thesetError
function will be called with the error object.• The
finally
block is also used in the example to set theloading
state tofalse
once thefetch
request is complete, regardless of whether it was successful or not.
Overall, async/await
and try/catch
are powerful features of modern JavaScript that allow you to write more readable and maintainable asynchronous code. By using these features in combination with React hooks like useEffect
, you can create efficient and robust applications that handle asynchronous operations with ease.
App.ts
Finally, we return the data
, loading
, and error
values as an object. These values can then be used in the component that is using the custom hook.
The next file we need to create is useFetchData in the Hooks directory: hooks/useFetchData
. In this file we will be using the useFetchData
hook to fetch a list of posts from the JSONPlaceholder API. We then render the data once it has been fetched, or an error message if there was an error.
App.ts
import { useFetchData } from "./useFetchData";
interface Post {
userId: number;
id: number;
title: string;
body: string;
}
function App() {
const { data, loading, error } = useFetchData<Post[]>({
url: "https://jsonplaceholder.typicode.com/posts",
});
if (loading) {
return <div>Loading...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
return (
<div>
{data?.map((post) => (
<div key={post.id}>
<h2>{post.title}</h2>
<p>{post.body}</p>
</div>
))}
</div>
);
}
Give this a shot and let me know what you think!!! Good luck 👍
Challenge 10: Building a responsive layout with React and CSS
Our last challenge is not so difficult as it is important to master. Responsize layouts are no longer just a nice feature. You, myself, and everyone else expect our sites to work and look beautiful no matter what the screen size. Spoiled we are? Either way, it has become a standard, so let’s cover a few different ways you can use and apply CSS to your React App.
App.js
import React from 'react';
import './App.css';
function App() {
return (
<div className="App">
<header>
<h1>My Website</h1>
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
</header>
<main>
<section className="hero">
<h2>Welcome to My Website</h2>
<p>Here you can learn all about me and what I do.</p>
</section>
<section className="services">
<h2>My Services</h2>
<ul>
<li>Service 1</li>
<li>Service 2</li>
<li>Service 3</li>
</ul>
</section>
<section className="testimonials">
<h2>Testimonials</h2>
<ul>
<li>
<img src="https://placehold.it/100x100" alt="Testimonial"/>
<blockquote>"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat felis at leo maximus iaculis. Suspendisse fringilla orci vitae eros consequat laoreet."</blockquote>
<cite>John Doe</cite>
</li>
<li>
<img src="https://placehold.it/100x100" alt="Testimonial"/>
<blockquote>"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed feugiat felis at leo maximus iaculis. Suspendisse fringilla orci vitae eros consequat laoreet."</blockquote>
<cite>Jane Smith</cite>
</li>
</ul>
</section>
</main>
<footer>
<p>© 2023 JonChristie.net</p>
</footer>
</div>
);
}
export default App;
So now we have a very simple website layout with a header
, main content
, and footer
. We’ve included three sections of content: a hero section, a services section, and a testimonials section.
CSS media queries
Now make this layout responsive, we can use CSS media queries to adjust the layout based on the size of the screen. Here’s an example of how you can do this:
.App {
max-width: 1000px;
margin: 0 auto;
padding: 0 20px;
}
header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 0;
border-bottom: 1px solid #ccc;
}
nav ul {
display: flex;
list-style: none;
margin: 0;
padding: 0;
}
nav li {
margin-left: 20px;
}
main {
padding: 20px 0;
}
section h2 {
margin-bottom: 10px;
}
.services ul {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 0;
}
.services li {
flex: 0 0 calc(33.333% - 10px);
margin-bottom: 20px;
padding: 20px;
border: 1px solid #ccc;
}
.testimonials ul {
display: flex;/* App.css - continued */
/* Media queries */
/* Mobile */
@media only screen and (max-width: 600px) {
header {
flex-direction: column;
align-items: flex-start;
}
nav {
order: 2;
width: 100%;
}
nav ul {
flex-direction: column;
}
nav li {
margin: 10px 0;
}
.hero {
text-align: center;
}
.services li {
flex: 0 0 100%;
}
}
/* Tablet */
@media only screen and (min-width: 601px) and (max-width: 900px) {
.services li {
flex: 0 0 calc(50% - 10px);
}
}
/* Desktop */
@media only screen and (min-width: 901px) {
.services li {
flex: 0 0 calc(33.333% - 10px);
}
}
Notice the media queries. What the heck is a media query? I’m glad you asked!
This is a media query in CSS that specifies a set of styles to be applied when the screen width is at most 600 pixels.
/* Mobile */
@media only screen and (max-width: 600px) {SyntaxError: Unexpected identifier 'replaceCarriageReturns'. Expected an opening '(' before a function's parameter list.
The @media
rule is used in CSS to define different styles for different media types, such as screen, print, or handheld devices. In this case, the media type is screen
, which refers to screens of devices like desktops, laptops, tablets, and smartphones.
The only
keyword is used to indicate that this media query should only be applied if the device supports the specified media type. This is useful to avoid applying styles to non-screen media types.
The and
the keyword is used to combine different conditions in a media query. In this case, we're specifying a single condition that the screen width must be at most 600 pixels.
Finally, the curly braces {}
contain the styles that will be applied when the media query condition is met. In this case, any styles defined inside these braces will only be applied when the screen width is at most 600 pixels.
Again, this last challenge was more of a fundamentals review. If that's the case, always feel free to build on top of my demos. Just clone them from the links provided!!!
Thanks so much for reading through the series. You made it all the way!!!!
Well as always, I truly hope it helped you in some fashion, and keep your eyes your for more articles coming up!!!!!
Complete Code:
Creating a custom hook for data fetching
Building a responsive layout with React and CSS
Series:
Part 1 of 5: React Coding Practice Problems
Part 2 of 5: React Coding Practice Problems
Part 3 of 5: React Coding Practice Problems
Part 4 of 5: React Coding Practice Problems
Enjoy and never hesitate to hit me up with questions, comments, jobs, or anything tech related!!! Please leave a ⭐️ in the GitHub Repo if you found some value!!!!