bhylu.com

Advertisements

React Fetching Data

Treat a fetched result as asynchronous work whose lifetime may end before it finishes.

How it works

A fetch effect needs more than a success branch. Check response status, validate the data shape, display loading and failure states, and stop an obsolete request from replacing a newer choice. This lab requests same-origin JSON fixtures served by Vite. North is intentionally slower in the review so switching to East tests a real ordering boundary. AbortController cancels the earlier request; the active flag also guards completion after cleanup.

Run the code

Use a separate empty lab folder with Node 22.12+ in the 22 series. Save the files below using their displayed paths; browsers may download only the basename, so create src, public or tests folders as listed. The pinned project uses React and React DOM 19.3.1 with Vite 7.3.1. Run npm install, then npm run dev and open the local address Vite prints. React examples do not run in Bhylu’s current playground.

No Apache, MySQL or change to Bhylu’s server configuration is needed. These are local teaching projects, not a publishing package. Keep generated dependencies and lockfiles in your own lab. Review the project setup.

Example

src/main.jsx

import React, { StrictMode, useState, useEffect } from 'react';
import { createRoot } from 'react-dom/client';
import './style.css';

function App() {
  const [desk, setDesk] = useState('north');
  const [result, setResult] = useState({ status: 'loading', items: [] });
  useEffect(() => {
    const controller = new AbortController();
    let active = true;
    setResult({ status: 'loading', items: [] });
    fetch('/stock/' + desk + '.json', { signal: controller.signal })
      .then((response) => {
        if (!response.ok) throw new Error('Unavailable');
        return response.json();
      })
      .then((data) => {
        if (!Array.isArray(data) || !data.every((row) => typeof row.name === 'string'))
          throw new Error('Invalid stock');
        if (active) setResult({ status: 'ready', items: data });
      })
      .catch((error) => {
        if (active && error.name !== 'AbortError')
          setResult({ status: 'error', items: [] });
      });
    return () => {
      active = false;
      controller.abort();
    };
  }, [desk]);
  return (
    <main>
      <h1>Neighbourhood lending desk</h1>
      <label>
        Desk
        <select value={desk} onChange={(event) => setDesk(event.target.value)}>
          <option value="north">North</option>
          <option value="east">East</option>
          <option value="missing">Unavailable fixture</option>
        </select>
      </label>
      <output aria-live="polite">{result.status}</output>
      {result.status === 'error' && (
        <p role="alert">Stock could not be loaded. Choose another desk.</p>
      )}
      <ul>
        {result.items.map((row) => (
          <li key={row.id}>{row.name}</li>
        ))}
      </ul>
    </main>
  );
}

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
);

Expected result

East shows its basket record; the unavailable fixture shows a recoverable error. An older North response must not replace East.

Save the supplied JSON files in public/stock. Vite serves them as local HTTP resources; missing.json intentionally does not exist. These are fixture records, not live inventory. Loading and error states clear stale rows in this design. For larger applications, a framework data layer or query cache can coordinate requests, caching and waterfalls more effectively than repeating this manual pattern.

Supporting files for this lab

public/stock/north.json

[{ "id": "n-stool", "name": "North stool" }]

public/stock/east.json

[{ "id": "e-basket", "name": "East basket" }]

For API contracts and version differences, use React’s official documentation.

Advertisements