Skip to content

Fetching in react

  • 1) Fetch-On-Render : UseEffect with useFetch hook (contained data, isLoading, error states) with an Abort controller (See Ryan Florence tip: doc in progress).

ISSUE : it doesn't start fetching until after the component was render on screen -> Lead problem to waterfall loading (component wait the previous, one per one)

  • 2) Fetch-Then-Render : with Relay, React-query, Swr, Redux RTK Query : kick off Fetching early as possible (put fetch hook from parent)

ISSUE : it solve waterfall but all component render wait that all was fetched

  • 3) Render-As-You-Fetch : With Concurrent/Suspense (react 17-18.1).

NO MORE ISSUE: Here we dont wait to the response to come back before we start rendering. But it need to work with React 18 new render.

react-fetch is experimental, in progress, not ready for prod

2) 'Fetch-Then-Render' with legacy mode

  • Create Fetch functions : fetchUser, FetchPosts ... which return a fetch Promise
const fetchUser = () => {
    console.log('Fetching user');
    return fetch('your-url-user-fetch')
        .then( response => response.json())
        .then( data => data)
        .catch( error => console.error(error))
}
  • Create a Wrap Promise function :
const wrapPromise = (promise) => { // promise is the fetch return
    let status = 'pending';
    let result;
    // it execute the promise
    let suspender = promise.then(
        response => {
            status = 'success';
            result = response;
        },
        error => {
            status = 'error';
            result = error;
        }
    );
    return {        // return an object with read function
        read() {
            switch (status) {
                case 'pending'
                    throw suspender; // it run the fetch
                break;
                case 'success'
                    throw result; // store the data
                break;
                case 'error'
                    throw result; // return the error
                break;
                default:
                    throw new Error('Forbidden call');
            }
        }
    }
}
  • Create a FetchData function which call fetchUser, FetchPosts functions in a const and return them wraped in the Wrap Promise function :
export const fetchData = () => {
    const userPromise = fetchUser();
    const postsPromise = fetchPosts();
    return {
        user: wrapPromise(userPromise),
        posts: wrapPromise(postsPromise)
    }
}
  • Call the component in this way:
const resource = fetchData();

export ProfilDetail = () => {
    const user = resource.user.read();
    return <div>
            <p> username : {user.name} </p>
            <p> usercity : {user.city} </p>
           </div>
}
  • From the app call the component:
const App = () => (
    <div>
        <React.Suspense fallback={<h4>Loading user ...</h4>}>
            <ProfilDetail />
        </React.Suspense>
    </div>
);

Github project example

3) 'Fetch-Then-Render' with 'Suspend'

GitHub - pmndrs/suspend-react: 🚥 Async/await for React components

import { Suspense } from 'react'
import { suspend } from 'suspend-react'

function Post({ id, version }) {
  const data = suspend(async () => {
    const res = await fetch(`https://hacker-news.firebaseio.com/${version}/item/${id}.json`)
    return await res.json()    
  }, [id, version])
  return (
    <div>
      {data.title} by {data.by}
    </div>
  )
}

function App() {
  return (
    <Suspense fallback={<div>loading...</div>}>
      <Post id={1000} version="v0" />
    </Suspense>
  )
}

suspend-react hackernews - CodeSandbox

Revalidate Live Queries – No cache management, no manual refetches! - YouTube

5) Coding 'Fetch-Then-Render' with 'SWR'

GitHub - vercel/swr: React Hooks for Data Fetching

  • The Coin.jsx component :
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args)
    .then((response) => response.json());

export default function Coins() {
    const { data } = useSWR(
        "https://api2.binance.com/api/v3/ticker/24hr",
        fetcher,
        {suspence : true}
    );

    return (
        <div className="App">
            {data?.map( (coin) => {
                return <h1> {coin.lastprice} </h1>
            })}
        </div>
    );
}
  • The App.jsx :
import Coins from './Coins';
import { Suspense } from 'react';

export default function App() {
    return (
        <div className='App'>
            <h1>Coin List</h1>
            <Suspense fallback={<h1>Loading ...</h1>}>
                <Coins />
            </Suspense>
        </div>
    );
}

swr/examples at main · GitHub

Adding error-boundary

Use react-error-boundary to handle errors in React

with 'react-error-boundary'

import * as React from 'react'
import ReactDOM from 'react-dom'
import {ErrorBoundary} from 'react-error-boundary'

function ErrorFallback({error}) {
  return (
    <div role="alert">
      <p>Something went wrong:</p>
      <pre style={{color: 'red'}}>{error.message}</pre>
    </div>
  )
}

function Greeting({subject}) {
  return <div>Hello {subject.toUpperCase()}</div>
}

function Farewell({subject}) {
  return <div>Goodbye {subject.toUpperCase()}</div>
}

function App() {
  return (
    <div>
      <ErrorBoundary FallbackComponent={ErrorFallback}>
        <Greeting />
        <Farewell />
      </ErrorBoundary>
    </div>
  )
}

Built-in with React error boundaries

React doc

  • Create a component class error :
class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error) {
    // Update state so the next render will show the fallback UI.
    return { hasError: true };
  }

  componentDidCatch(error, errorInfo) {
    // You can also log the error to an error reporting service
    logErrorToMyService(error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      // You can render any custom fallback UI
      return <h1>Something went wrong.</h1>;
    }

    return this.props.children; 
  }
}
  • then use it with :
<ErrorBoundary>
  <MyWidget />
</ErrorBoundary>

Exemple

Transition API

React Transition official Doc

  • Create the transition
  const [isPending, startTransition] = useTransition();
  • use it for exemple a fetching and setData :
return (
  <>
    <button
      disabled={isPending}
      onClick={() => {
        startTransition(() => {
          const nextUserId = getNextId(resource.userId);
          setResource(fetchProfileData(nextUserId));
        });
      }}
    >
      Next
    </button>
    {isPending ? " Loading..." : null}
    <ProfilePage resource={resource} />
  </>
);

Try it on CodeSandbox

Discussion more on Transition

  • Another exemple code :
const [isPending, startTransition] = useTransition();

function handleClick() {
  startTransition(() => {
    setTab('comments');
  });
}

<Suspense fallback={<Spinner />}>
  <!-- dim the content while update is being worked on  -->
  <div style={{opacity: isPending ? 0.8 : 1 }}>
    {tab === 'photos' ? <Photos /> : <Comments />}
  </div>
</Suspense>