We use cookies to enhance your experience on the site
CodeWorlds

API Handling and Data Fetching

Communicating with external APIs is one of the most common use cases for side effects in React applications. In this module, we'll learn how to efficiently fetch, process, and manage data from external sources.

Basics of fetching data from an API

In web applications, data often comes from external APIs that provide information in JSON, XML, or other formats. On our cosmic journey, we can imagine a mission control center fetching data from various satellites, spaceships, and research stations.

Simple example of fetching data with useEffect

1import React, { useState, useEffect } from 'react';
2
3function AstronautsList() {
4  const [astronauts, setAstronauts] = useState([]);
5  const [loading, setLoading] = useState(true);
6  const [error, setError] = useState(null);
7
8  useEffect(() => {
9    // Async function for fetching data
10    async function fetchAstronauts() {
11      try {
12        setLoading(true);
13        // Fetching data
14        const response = await fetch('https://api.spaceagency.com/astronauts');
15
16        // Check if response is OK
17        if (!response.ok) {
18          throw new Error(`HTTP Error: ${response.status}`);
19        }
20
21        // Parse JSON response
22        const data = await response.json();
23
24        // Update state
25        setAstronauts(data);
26        setError(null);
27      } catch (err) {
28        // Error handling
29        setError(`Failed to fetch data: ${err.message}`);
30        setAstronauts([]);
31      } finally {
32        // Mark loading as complete
33        setLoading(false);
34      }
35    }
36
37    // Call the function
38    fetchAstronauts();
39  }, []); // Empty dependency array - execute only once
40
41  // Render based on state
42  if (loading) return <div>Loading data...</div>;
43  if (error) return <div>Error: {error}</div>;
44
45  return (
46    <div>
47      <h2>Active Astronauts</h2>
48      <ul>
49        {astronauts.map(astronaut => (
50          <li key={astronaut.id}>
51            {astronaut.name} - {astronaut.role}
52          </li>
53        ))}
54      </ul>
55    </div>
56  );
57}

In the example above:

  1. We use
    useState
    to store data, loading state, and errors
  2. We use
    useEffect
    with an empty dependency array to fetch data only once after mounting
  3. We use
    async/await
    to handle asynchronous data fetching
  4. We handle different states (loading, error, success) in the user interface

Advanced data fetching techniques

1. Fetching data with parameters

Often we need to fetch data based on parameters that can change:

1function MissionDetails({ missionId }) {
2  const [mission, setMission] = useState(null);
3  const [loading, setLoading] = useState(true);
4  const [error, setError] = useState(null);
5
6  useEffect(() => {
7    let isMounted = true; // Flag to prevent state updates after unmount
8
9    async function fetchMission() {
10      try {
11        setLoading(true);
12
13        const response = await fetch(
14          `https://api.spaceagency.com/missions/${missionId}`
15        );
16
17        if (!response.ok) {
18          throw new Error(`HTTP Error: ${response.status}`);
19        }
20
21        const data = await response.json();
22
23        // Update state only if component is still mounted
24        if (isMounted) {
25          setMission(data);
26          setError(null);
27        }
28      } catch (err) {
29        if (isMounted) {
30          setError(`Failed to fetch mission data: ${err.message}`);
31          setMission(null);
32        }
33      } finally {
34        if (isMounted) {
35          setLoading(false);
36        }
37      }
38    }
39
40    fetchMission();
41
42    // Cleanup function
43    return () => {
44      isMounted = false; // Mark that the component has been unmounted
45    };
46  }, [missionId]); // Refetch data when missionId changes
47
48  // Rendering
49  if (loading) return <div>Loading mission data...</div>;
50  if (error) return <div>Error: {error}</div>;
51  if (!mission) return <div>Mission not found</div>;
52
53  return (
54    <div className="mission-details">
55      <h2>{mission.name}</h2>
56      <p>Status: {mission.status}</p>
57      <p>Launch date: {new Date(mission.launchDate).toLocaleDateString()}</p>
58      <p>Commander: {mission.commander}</p>
59      {/* More mission details */}
60    </div>
61  );
62}

In this example:

  1. We fetch data based on
    missionId
    , which can change
  2. We use an
    isMounted
    flag to prevent state updates after unmount
  3. We add a cleanup function that sets
    isMounted = false
    on unmount

2. Canceling fetch requests

The Fetch API doesn't have a built-in cancellation mechanism, but we can use AbortController to abort a request:

1function SpaceWeather({ location }) {
2  const [weatherData, setWeatherData] = useState(null);
3  const [loading, setLoading] = useState(true);
4  const [error, setError] = useState(null);
5
6  useEffect(() => {
7    // Create AbortController to cancel the request
8    const abortController = new AbortController();
9    const signal = abortController.signal;
10
11    async function fetchWeatherData() {
12      try {
13        setLoading(true);
14
15        const response = await fetch(
16          `https://api.spaceagency.com/weather/${location}`,
17          { signal } // Assign signal to the request
18        );
19
20        if (!response.ok) {
21          throw new Error(`HTTP Error: ${response.status}`);
22        }
23
24        const data = await response.json();
25        setWeatherData(data);
26        setError(null);
27      } catch (err) {
28        // Ignore cancellation errors
29        if (err.name === 'AbortError') {
30          console.log('Weather request was cancelled');
31        } else {
32          setError(`Failed to fetch weather data: ${err.message}`);
33          setWeatherData(null);
34        }
35      } finally {
36        setLoading(false);
37      }
38    }
39
40    fetchWeatherData();
41
42    // Cleanup function cancels the request if the component
43    // is unmounted before fetching completes
44    return () => {
45      abortController.abort();
46    };
47  }, [location]);
48
49  // Rendering
50  if (loading) return <div>Loading weather data...</div>;
51  if (error) return <div>Error: {error}</div>;
52  if (!weatherData) return <div>No weather data available</div>;
53
54  return (
55    <div className="space-weather">
56      <h2>Space Weather: {location}</h2>
57      <p>Radiation: {weatherData.radiation} mSv</p>
58      <p>Solar activity: {weatherData.solarActivity}</p>
59      <p>Temperature: {weatherData.temperature}°C</p>
60    </div>
61  );
62}

In this example:

  1. We use
    AbortController
    to create a signal that can abort a Fetch request
  2. We pass the signal to the Fetch request options
  3. In the cleanup function, we call
    abortController.abort()
    to cancel an ongoing request
  4. We specifically handle the
    AbortError
    that is thrown when a request is cancelled

3. Handling multiple concurrent requests

Sometimes we need to fetch data from several sources simultaneously:

1function MissionDashboard({ missionId }) {
2  const [missionData, setMissionData] = useState({
3    details: null,
4    crew: [],
5    telemetry: null
6  });
7  const [loading, setLoading] = useState(true);
8  const [error, setError] = useState(null);
9
10  useEffect(() => {
11    let isMounted = true;
12
13    async function fetchMissionData() {
14      try {
15        setLoading(true);
16
17        // Fetch data in parallel using Promise.all
18        const [detailsResponse, crewResponse, telemetryResponse] = await Promise.all([
19          fetch(`https://api.spaceagency.com/missions/${missionId}`),
20          fetch(`https://api.spaceagency.com/missions/${missionId}/crew`),
21          fetch(`https://api.spaceagency.com/missions/${missionId}/telemetry`)
22        ]);
23
24        // Check responses
25        if (!detailsResponse.ok || !crewResponse.ok || !telemetryResponse.ok) {
26          throw new Error('One or more requests failed');
27        }
28
29        // Parse data in parallel
30        const [details, crew, telemetry] = await Promise.all([
31          detailsResponse.json(),
32          crewResponse.json(),
33          telemetryResponse.json()
34        ]);
35
36        if (isMounted) {
37          setMissionData({ details, crew, telemetry });
38          setError(null);
39        }
40      } catch (err) {
41        if (isMounted) {
42          setError(`Failed to fetch mission data: ${err.message}`);
43        }
44      } finally {
45        if (isMounted) {
46          setLoading(false);
47        }
48      }
49    }
50
51    fetchMissionData();
52
53    return () => {
54      isMounted = false;
55    };
56  }, [missionId]);
57
58  // Rendering
59  if (loading) return <div>Loading mission data...</div>;
60  if (error) return <div>Error: {error}</div>;
61
62  const { details, crew, telemetry } = missionData;
63
64  return (
65    <div className="mission-dashboard">
66      <h1>{details.name} - Control Panel</h1>
67
68      <section className="mission-details">
69        <h2>Mission Details</h2>
70        <p>Status: {details.status}</p>
71        <p>Objective: {details.objective}</p>
72      </section>
73
74      <section className="crew-info">
75        <h2>Crew</h2>
76        <ul>
77          {crew.map(member => (
78            <li key={member.id}>
79              {member.name} - {member.role}
80            </li>
81          ))}
82        </ul>
83      </section>
84
85      <section className="telemetry-data">
86        <h2>Telemetry</h2>
87        <p>Oxygen: {telemetry.oxygen}%</p>
88        <p>Fuel: {telemetry.fuel}%</p>
89        <p>Velocity: {telemetry.velocity} km/h</p>
90      </section>
91    </div>
92  );
93}

In this example:

  1. We use
    Promise.all()
    for parallel execution of multiple requests
  2. We gather all data in a single
    missionData
    state
  3. We handle all data in one component

Best practices and patterns

1. Extracting data fetching logic

To improve readability and code maintenance, it's worth extracting the data fetching logic outside the component:

1// api.js - API Services
2const API_BASE_URL = 'https://api.spaceagency.com';
3
4export async function fetchAstronauts() {
5  const response = await fetch(`${API_BASE_URL}/astronauts`);
6  if (!response.ok) {
7    throw new Error(`HTTP Error: ${response.status}`);
8  }
9  return response.json();
10}
11
12export async function fetchMission(missionId) {
13  const response = await fetch(`${API_BASE_URL}/missions/${missionId}`);
14  if (!response.ok) {
15    throw new Error(`HTTP Error: ${response.status}`);
16  }
17  return response.json();
18}
19
20// Component with extracted API logic
21function AstronautsList() {
22  const [astronauts, setAstronauts] = useState([]);
23  const [loading, setLoading] = useState(true);
24  const [error, setError] = useState(null);
25
26  useEffect(() => {
27    let isMounted = true;
28
29    async function getAstronauts() {
30      try {
31        setLoading(true);
32        const data = await fetchAstronauts();
33
34        if (isMounted) {
35          setAstronauts(data);
36          setError(null);
37        }
38      } catch (err) {
39        if (isMounted) {
40          setError(`Failed to fetch data: ${err.message}`);
41          setAstronauts([]);
42        }
43      } finally {
44        if (isMounted) {
45          setLoading(false);
46        }
47      }
48    }
49
50    getAstronauts();
51
52    return () => {
53      isMounted = false;
54    };
55  }, []);
56
57  // Rendering
58  //
59}

2. Creating custom hooks for data fetching

For repeatable data fetching operations, the best solution is to create a custom hook:

1// useFetch.js - Custom hook for data fetching
2function useFetch(url, options = {}) {
3  const [data, setData] = useState(null);
4  const [loading, setLoading] = useState(true);
5  const [error, setError] = useState(null);
6
7  useEffect(() => {
8    let isMounted = true;
9    const abortController = new AbortController();
10    const signal = abortController.signal;
11
12    async function fetchData() {
13      try {
14        setLoading(true);
15
16        const response = await fetch(url, {
17          ...options,
18          signal,
19        });
20
21        if (!response.ok) {
22          throw new Error(`HTTP Error: ${response.status}`);
23        }
24
25        const result = await response.json();
26
27        if (isMounted) {
28          setData(result);
29          setError(null);
30        }
31      } catch (err) {
32        if (err.name !== 'AbortError' && isMounted) {
33          setError(`Failed to fetch data: ${err.message}`);
34          setData(null);
35        }
36      } finally {
37        if (isMounted) {
38          setLoading(false);
39        }
40      }
41    }
42
43    fetchData();
44
45    return () => {
46      isMounted = false;
47      abortController.abort();
48    };
49  }, [url, JSON.stringify(options)]);
50
51  return { data, loading, error };
52}
53
54// Using the custom hook
55function PlanetInfo({ planetId }) {
56  const { data: planet, loading, error } = useFetch(
57    `https://api.spaceagency.com/planets/${planetId}`
58  );
59
60  if (loading) return <div>Loading...</div>;
61  if (error) return <div>Error: {error}</div>;
62  if (!planet) return <div>Planet not found</div>;
63
64  return (
65    <div className="planet-card">
66      <h2>{planet.name}</h2>
67      <p>Type: {planet.type}</p>
68      <p>Diameter: {planet.diameter} km</p>
69      <p>Distance from Sun: {planet.distanceFromSun} million km</p>
70    </div>
71  );
72}

3. More advanced data fetching hook with caching and retry mechanism

1// useDataFetching.js
2function useDataFetching(fetchFn, dependencies = [], options = {}) {
3  const {
4    initialData = null,
5    cacheKey = null,
6    maxRetries = 3,
7    retryDelay = 1000,
8  } = options;
9
10  const [data, setData] = useState(initialData);
11  const [loading, setLoading] = useState(true);
12  const [error, setError] = useState(null);
13
14  // Using useRef to track attempts
15  const retriesRef = useRef(0);
16
17  // Check cache on first render
18  useEffect(() => {
19    if (cacheKey) {
20      const cachedData = localStorage.getItem(cacheKey);
21      if (cachedData) {
22        try {
23          setData(JSON.parse(cachedData));
24          setLoading(false);
25        } catch (e) {
26          console.error('Error parsing cached data:', e);
27        }
28      }
29    }
30  }, [cacheKey]);
31
32  // Main data fetching logic
33  useEffect(() => {
34    let isMounted = true;
35    const abortController = new AbortController();
36
37    async function fetchData() {
38      try {
39        setLoading(true);
40
41        const result = await fetchFn(abortController.signal);
42
43        if (isMounted) {
44          setData(result);
45          setError(null);
46
47          // Save to cache if available
48          if (cacheKey) {
49            localStorage.setItem(cacheKey, JSON.stringify(result));
50          }
51
52          // Reset retry counter
53          retriesRef.current = 0;
54        }
55      } catch (err) {
56        if (!isMounted) return;
57
58        if (err.name === 'AbortError') {
59          console.log('Request cancelled');
60          return;
61        }
62
63        // Retry logic
64        if (retriesRef.current < maxRetries) {
65          console.log(`Attempt ${retriesRef.current + 1} of ${maxRetries} failed. Retrying in ${retryDelay}ms`);
66
67          setTimeout(() => {
68            if (isMounted) {
69              retriesRef.current += 1;
70              fetchData(); // Retry
71            }
72          }, retryDelay * Math.pow(2, retriesRef.current)); // Exponential backoff
73
74          return;
75        }
76
77        // After exhausting all attempts
78        setError(`Failed to fetch data: ${err.message}`);
79        setData(initialData);
80      } finally {
81        if (isMounted) {
82          setLoading(false);
83        }
84      }
85    }
86
87    fetchData();
88
89    return () => {
90      isMounted = false;
91      abortController.abort();
92    };
93  }, [...dependencies]);
94
95  // Function for manual data refresh
96  const refetch = useCallback(async () => {
97    setLoading(true);
98    retriesRef.current = 0;
99
100    try {
101      const abortController = new AbortController();
102      const result = await fetchFn(abortController.signal);
103      setData(result);
104      setError(null);
105
106      if (cacheKey) {
107        localStorage.setItem(cacheKey, JSON.stringify(result));
108      }
109    } catch (err) {
110      setError(`Failed to refresh data: ${err.message}`);
111    } finally {
112      setLoading(false);
113    }
114  }, [fetchFn, cacheKey]);
115
116  return { data, loading, error, refetch };
117}
118
119// Usage example
120function MarsMissions() {
121  // Function to fetch data
122  const fetchMissions = useCallback(async (signal) => {
123    const response = await fetch('https://api.spaceagency.com/mars/missions', { signal });
124    if (!response.ok) throw new Error(`HTTP Error: ${response.status}`);
125    return response.json();
126  }, []);
127
128  // Using the advanced hook
129  const {
130    data: missions,
131    loading,
132    error,
133    refetch
134  } = useDataFetching(fetchMissions, [], {
135    initialData: [],
136    cacheKey: 'mars-missions-cache',
137    maxRetries: 2
138  });
139
140  return (
141    <div className="mars-missions">
142      <div className="header">
143        <h2>Mars Missions</h2>
144        <button onClick={refetch} disabled={loading}>
145          {loading ? 'Refreshing...' : 'Refresh'}
146        </button>
147      </div>
148
149      {loading && missions.length === 0 && <div>Loading missions...</div>}
150      {error && <div className="error">Error: {error}</div>}
151
152      <ul className="missions-list">
153        {missions.map(mission => (
154          <li key={mission.id} className={`mission-item ${mission.status}`}>
155            <h3>{mission.name}</h3>
156            <p>Launch: {new Date(mission.launchDate).toLocaleDateString()}</p>
157            <p>Status: {mission.status}</p>
158          </li>
159        ))}
160      </ul>
161
162      {missions.length === 0 && !loading && !error && (
163        <p>No missions found.</p>
164      )}
165    </div>
166  );
167}

In this advanced example:

  1. We use a custom
    useDataFetching
    hook with retry support, caching, and manual refresh functionality
  2. We implement exponential backoff for retries (each subsequent attempt waits longer)
  3. We use parameters to customize hook behavior for different use cases
  4. We handle both initial loading and manual data refresh

Error handling when fetching data

Beyond basic error handling in components, it's worth implementing more advanced techniques:

1. Error Boundary component

Error boundaries allow elegant handling of unexpected errors:

1// ErrorBoundary.js
2class ErrorBoundary extends React.Component {
3  constructor(props) {
4    super(props);
5    this.state = { hasError: false, error: null, errorInfo: null };
6  }
7
8  static getDerivedStateFromError(error) {
9    // Update state so next render shows fallback UI
10    return { hasError: true };
11  }
12
13  componentDidCatch(error, errorInfo) {
14    // You can also log the error to a monitoring service
15    console.error('Caught error:', error, errorInfo);
16    this.setState({ error, errorInfo });
17
18    // Send error to reporting service
19    // logErrorToService(error, errorInfo);
20  }
21
22  render() {
23    if (this.state.hasError) {
24      // You can render any fallback UI
25      return (
26        <div className="error-boundary">
27          <h2>Something went wrong!</h2>
28          <p>An error occurred while rendering the component.</p>
29          <details>
30            <summary>Error details</summary>
31            <p>{this.state.error && this.state.error.toString()}</p>
32            <p>Components: {this.state.errorInfo && this.state.errorInfo.componentStack}</p>
33          </details>
34          <button onClick={() => this.setState({ hasError: false })}>
35            Try again
36          </button>
37        </div>
38      );
39    }
40
41    return this.props.children;
42  }
43}
44
45// Using error boundary for a component
46function App() {
47  return (
48    <div className="app">
49      <header>Mission Control Center</header>
50
51      <ErrorBoundary>
52        <MissionDashboard missionId="mars-2030" />
53      </ErrorBoundary>
54
55      <ErrorBoundary>
56        <AstronautsList />
57      </ErrorBoundary>
58
59      <footer>Space Agency © 2023</footer>
60    </div>
61  );
62}

2. Retry strategies

When an API is unstable, it's worth implementing automatic retry strategies:

1async function fetchWithRetry(url, options = {}, maxRetries = 3, delay = 1000) {
2  let retries = 0;
3
4  while (retries < maxRetries) {
5    try {
6      const response = await fetch(url, options);
7
8      if (!response.ok) {
9        throw new Error(`HTTP Error: ${response.status}`);
10      }
11
12      return await response.json();
13    } catch (err) {
14      retries++;
15
16      if (retries >= maxRetries) {
17        throw err; // All attempts failed, pass error forward
18      }
19
20      console.log(`Attempt ${retries} of ${maxRetries} failed. Retrying in ${delay}ms`);
21
22      // Wait before next attempt (exponential backoff)
23      await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, retries - 1)));
24    }
25  }
26}

3. Handling connectivity issues

It's also worth handling internet connectivity issues:

1// Hook for monitoring connection status
2function useOnlineStatus() {
3  const [isOnline, setIsOnline] = useState(navigator.onLine);
4
5  useEffect(() => {
6    function handleOnline() {
7      setIsOnline(true);
8    }
9
10    function handleOffline() {
11      setIsOnline(false);
12    }
13
14    window.addEventListener('online', handleOnline);
15    window.addEventListener('offline', handleOffline);
16
17    return () => {
18      window.removeEventListener('online', handleOnline);
19      window.removeEventListener('offline', handleOffline);
20    };
21  }, []);
22
23  return isOnline;
24}
25
26// Usage in a component
27function DataFetchingComponent() {
28  const isOnline = useOnlineStatus();
29  const { data, loading, error, refetch } = useFetch('https://api.example.com/data');
30
31  if (!isOnline) {
32    return (
33      <div className="offline-message">
34        <h3>No internet connection</h3>
35        <p>Check your connection and try again.</p>
36      </div>
37    );
38  }
39
40  if (loading) return <div>Loading...</div>;
41  if (error) return <div>Error: {error}</div>;
42
43  return (
44    <div>
45      <h2>Data</h2>
46      {/* Rendering data */}
47    </div>
48  );
49}

API simulation during development

During application development, the real API may be unavailable or not ready. In such cases, it's worth simulating the API:

1. Simple simulation with delay

1// mockApi.js
2const DELAY = 1000; // Simulating network delay
3
4export function mockFetch(data, shouldFail = false, failureRate = 0) {
5  return new Promise((resolve, reject) => {
6    setTimeout(() => {
7      // Simulating random error
8      if (shouldFail || Math.random() < failureRate) {
9        reject(new Error('Simulated API error'));
10        return;
11      }
12
13      resolve(data);
14    }, DELAY);
15  });
16}
17
18// Sample data
19const MOCK_ASTRONAUTS = [
20  { id: 1, name: 'John Smith', role: 'Commander' },
21  { id: 2, name: 'Anna Johnson', role: 'Pilot' },
22  { id: 3, name: 'Peter Williams', role: 'Flight Engineer' }
23];
24
25export function getAstronauts() {
26  return mockFetch(MOCK_ASTRONAUTS, false, 0.2);
27}

2. More sophisticated simulation with MSW (Mock Service Worker)

MSW is a library that simulates API requests at the browser level:

1// mockServiceWorker.js
2import { setupWorker, rest } from 'msw';
3
4// Simulation data
5const astronauts = [
6  { id: 1, name: 'John Smith', role: 'Commander' },
7  { id: 2, name: 'Anna Johnson', role: 'Pilot' },
8  { id: 3, name: 'Peter Williams', role: 'Flight Engineer' }
9];
10
11const missions = [
12  {
13    id: 'mars-2030',
14    name: 'Mars 2030',
15    status: 'Planned',
16    launchDate: '2030-05-15',
17    commander: 'John Smith'
18  },
19  {
20    id: 'iss-2023',
21    name: 'ISS Expedition 70',
22    status: 'In Progress',
23    launchDate: '2023-09-01',
24    commander: 'Anna Johnson'
25  }
26];
27
28// Defining handlers
29export const handlers = [
30  // Fetching all astronauts
31  rest.get('https://api.spaceagency.com/astronauts', (req, res, ctx) => {
32    return res(
33      ctx.delay(1000),
34      ctx.status(200),
35      ctx.json(astronauts)
36    );
37  }),
38
39  // Fetching astronaut by ID
40  rest.get('https://api.spaceagency.com/astronauts/:id', (req, res, ctx) => {
41    const { id } = req.params;
42    const astronaut = astronauts.find(a => a.id === parseInt(id));
43
44    if (!astronaut) {
45      return res(
46        ctx.delay(500),
47        ctx.status(404),
48        ctx.json({ error: 'Astronaut not found' })
49      );
50    }
51
52    return res(
53      ctx.delay(500),
54      ctx.status(200),
55      ctx.json(astronaut)
56    );
57  }),
58
59  // Fetching missions
60  rest.get('https://api.spaceagency.com/missions', (req, res, ctx) => {
61    return res(
62      ctx.delay(800),
63      ctx.status(200),
64      ctx.json(missions)
65    );
66  }),
67
68  // Fetching mission by ID
69  rest.get('https://api.spaceagency.com/missions/:id', (req, res, ctx) => {
70    const { id } = req.params;
71    const mission = missions.find(m => m.id === id);
72
73    if (!mission) {
74      return res(
75        ctx.delay(500),
76        ctx.status(404),
77        ctx.json({ error: 'Mission not found' })
78      );
79    }
80
81    return res(
82      ctx.delay(700),
83      ctx.status(200),
84      ctx.json(mission)
85    );
86  })
87];
88
89// Initialize service worker
90export const worker = setupWorker(...handlers);

Then in your application's entry file (e.g.,

index.js
):

1// index.js
2import React from 'react';
3import { createRoot } from 'react-dom/client';
4import App from './App';
5
6// Conditionally initialize mock API only in development environment
7if (process.env.NODE_ENV === 'development') {
8  const { worker } = require('./mockServiceWorker');
9  worker.start();
10}
11
12const root = createRoot(document.getElementById('root'));
13root.render(<App />);

Summary

Fetching data from APIs is a fundamental part of most React applications. The key to success is:

  1. Code organization - separating API logic from components
  2. Custom hooks - creating abstractions for repeatable data fetching operations
  3. Handling all states - loading, success, error, offline
  4. Resource cleanup - canceling requests, preventing state updates after unmount
  5. Retry strategies - automatically retrying failed requests
  6. Testing - simulating APIs during development and tests

Remember that good data fetching handling significantly impacts user experience. Even if the API is slow, a well-designed interface with loading state information, animations, and error handling can make the application feel faster and more reliable.

Go to CodeWorlds