How a Front-End Engineer Evolves: Writing React Code at Different Skill Levels
Table of contents
As a front-end engineer, your coding style and approach change as you gain more experience and become familiar with advanced tools and techniques. In this article, we will use the Dog API to demonstrate how to fetch an image at different skill levels, from beginner to advanced. By the end, you'll see how you can evolve from using simple hooks to sophisticated solutions involving React Query and React's Suspense.
What is the Dog API?
The Dog API is a free and easy-to-use API that provides random dog images, facts, and information about different breeds. In this article, we will use it to fetch random dog images as we progress through different levels of front-end engineering expertise using ReactJS.
Here’s the basic Dog API endpoint we'll be using:
https://dog.ceo/api/breeds/image/random
This endpoint returns a JSON object containing a URL to a random dog image, which we’ll display in our React component at different levels of implementation.
1. Beginner: Simple Fetch Inside useEffect
As a beginner, the most common approach to fetch data from an API is to use React’s useEffect
hook alongside useState
. Here's how we can fetch an image from the Dog API:
import React, { useState, useEffect } from 'react';
function DogImage() {
const [dogImage, setDogImage] = useState('');
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchDogImage = async () => {
try {
const response = await fetch('https://dog.ceo/api/breeds/image/random');
const data = await response.json();
setDogImage(data.message);
} catch (err) {
setError('Failed to fetch dog image');
} finally {
setLoading(false);
}
};
fetchDogImage();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>{error}</p>;
return <img src={dogImage} alt="A Random Dog" />;
}
export default DogImage;
Reflection:
Fetching Logic: The
useEffect
hook runs after the component mounts and fetches the dog image.Pros: Simple, beginner-friendly, and gets the job done.
Cons: As your app grows, this pattern leads to code duplication, making state and side-effect management messy.
2. Intermediate: Custom Hook for Fetching Data
At an intermediate level, you start refactoring code to be more reusable by extracting the fetching logic into custom hooks. This keeps components focused on rendering and abstracts the data-fetching logic.
import React from 'react';
import useFetch from './useFetch';
function DogImage() {
const { data: dogImage, loading, error } = useFetch('https://dog.ceo/api/breeds/image/random');
if (loading) return <p>Loading...</p>;
if (error) return <p>{error}</p>;
return <img src={dogImage} alt="A Random Dog" />;
}
export default DogImage;
Custom Hook (useFetch
)
import { useState, useEffect } from 'react';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url);
const result = await response.json();
setData(result.message);
} catch (err) {
setError('Failed to fetch data');
} finally {
setLoading(false);
}
};
fetchData();
}, [url]);
return { data, loading, error };
}
export default useFetch;
Reflection:
Reusability: The
useFetch
hook can be reused across different components, making the code more maintainable.Separation of Concerns: The fetching logic is decoupled from the component, making it more readable and easier to manage.
Pros: Cleaner and more scalable.
Cons: Still manually managing state, loading, and error handling.
3. Advanced: React Query for Data Fetching
As an advanced developer, you start leveraging tools like React Query, which abstracts and optimizes data-fetching logic. It handles caching, synchronization, and background refetching, improving performance and developer experience.
import React from 'react';
import { useQuery } from 'react-query';
function fetchDogImage() {
return fetch('https://dog.ceo/api/breeds/image/random').then((response) => response.json());
}
function DogImage() {
const { data, error, isLoading } = useQuery('dogImage', fetchDogImage);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error fetching data</p>;
return <img src={data.message} alt="A Random Dog" />;
}
export default DogImage;
Reflection:
Automatic Caching: React Query caches fetched data and serves it on future requests without re-fetching unless necessary.
Error & Loading States: Handled automatically by React Query, reducing boilerplate.
Pros: Great for larger applications with complex data requirements.
Cons: Requires learning the React Query library.
4. Expert: Using React Query with Suspense for Data Fetching
At the expert level, you start using React's Suspense with React Query. Suspense simplifies async data loading and removes the need for manual loading states.
import React, { Suspense } from 'react';
import { useQuery } from 'react-query';
function fetchDogImage() {
return fetch('https://dog.ceo/api/breeds/image/random').then((response) => response.json());
}
function DogImageComponent() {
const { data } = useQuery('dogImage', fetchDogImage, { suspense: true });
return <img src={data.message} alt="A Random Dog" />;
}
function DogImage() {
return (
<Suspense fallback={<p>Loading...</p>}>
<DogImageComponent />
</Suspense>
);
}
export default DogImage;
Reflection:
Suspense for Data Fetching: React’s
Suspense
simplifies the management of loading states, making the code cleaner.Cleaner Code: No need to manage loading or error states manually within the component.
Pros: Highly optimized for large-scale applications.
Cons: Requires React 18 for
Suspense
support with data fetching.
Conclusion
As your expertise as a front-end engineer grows, so does your ability to write more maintainable, scalable, and efficient code. Whether you're just starting with a simple useEffect
hooks or implementing advanced solutions using React Query and Suspense, mastering the declarative approach to fetching data in React will prepare you for building more robust applications.