Community Forums

Frontend API Interaction

JD
John Doe

Hey everyone, I'm working on a new project that involves fetching data from a REST API and displaying it in a React frontend. I'm using `axios` for the requests, but I'm running into some issues with handling asynchronous operations and displaying loading states correctly.

Specifically, I'm trying to implement a pattern where I fetch data on component mount, show a "Loading..." message, and then render the data once the request is successful. If there's an error, I want to display an error message.

Here's a simplified version of what I have so far:

// Example Component import React, { useState, useEffect } from 'react'; import axios from 'axios'; function UserProfile({ userId }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchUser = async () => { try { setLoading(true); const response = await axios.get(`/api/users/${userId}`); setUser(response.data); setError(null); } catch (err) { setError('Failed to fetch user data.'); setUser(null); } finally { setLoading(false); } }; fetchUser(); }, [userId]); if (loading) { return
Loading user profile...
; } if (error) { return
Error: {error}
; } if (!user) { return
No user data available.
; } return (

{user.name}

Email: {user.email}

Member Since: {new Date(user.joinedDate).toLocaleDateString()}

); } export default UserProfile;

I'm wondering if there are more efficient or cleaner ways to handle this common pattern. Any advice on error boundaries, data fetching libraries, or best practices for API interaction in the frontend would be greatly appreciated!

AL
Alice Williams

Hi John, your approach is solid and a very common pattern for fetching data. Using `useEffect` with an async function inside is the standard way to handle side effects like API calls in React functional components.

For managing states like `loading` and `error`, your approach is good. Libraries like React Query (TanStack Query) or SWR can abstract this logic even further, providing built-in caching, background refetching, and better error handling out-of-the-box. They can significantly simplify data fetching and state management.

For example, with React Query:

// Example using React Query import React from 'react'; import { useQuery } from 'react-query'; import axios from 'axios'; function UserProfileQuery({ userId }) { const fetchUser = async () => { const { data } = await axios.get(`/api/users/${userId}`); return data; }; const { data: user, isLoading, isError, error } = useQuery(['user', userId], fetchUser); if (isLoading) { return
Loading user profile...
; } if (isError) { return
Error: {error.message}
; } return (

{user.name}

Email: {user.email}

Member Since: {new Date(user.joinedDate).toLocaleDateString()}

); } export default UserProfileQuery;

Also, consider using a dedicated error boundary component in React to catch errors anywhere in your component tree and display a fallback UI gracefully.

SB
Sam Gibson

Great question, John! Building on Alice's point about libraries, also check out the Fetch API, which is built into modern browsers and doesn't require an external dependency like `axios`. It returns Promises directly, making async/await usage very clean.

Regarding API interaction, think about how you structure your API calls. Consider creating a dedicated service or hook layer for fetching data. This keeps your components cleaner and makes your data fetching logic reusable and testable.

For example, a custom hook:

// Example Custom Hook import { useState, useEffect } from 'react'; import axios from 'axios'; function useFetch(url) { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const response = await axios.get(url); setData(response.data); setError(null); } catch (err) { setError(err); setData(null); } finally { setLoading(false); } }; fetchData(); }, [url]); return { data, loading, error }; } // Usage in component function UserProfileHook({ userId }) { const { data: user, loading, error } = useFetch(`/api/users/${userId}`); if (loading) return
Loading...
; if (error) return
Error: {error.message}
; if (!user) return
No data
; return (

{user.name}

Email: {user.email}

); }

This pattern is highly composable and makes components more declarative about their data needs.

Reply to this topic