React Suspense And Fetching Approach

React Suspense And Fetching Approach

How React suspense work, How to trigger the suspense fallback manually


Baraa Abuzaid
Baraa Abuzaid
@baraaabuzaid
React Suspense And Fetching Approach


When it comes to fetching and rendering content in a React App, there are a few key strategies to consider: Fetch on render, Fetch then render, fetch as you render. Of these, the most effective approach is Fetch as You Render, which has been made possible thanks to the introduction of React Suspense. By utilizing Suspense, your components can patiently await async operations as they are dynamically rendered on the screen. This allows you to clearly express loading state declaratively and eliminate any potential race conditions.

Fetch On Render

This is known as waterfall. Each competent won’t start fetching data until its parent request resolve. This because fetching only start after the component render on the screen.

react fetch on render image

Fetch Then Render

Unlike fetch-on-render, we managed to send all our requests in parallel and avoid having to wait before sending each request, nevertheless with promise.all we are bottleneck by our longest query. Thus, waiting for all requests to resolve before starting with any render.

React Fetch Then Render image

Render As You Fetch

With render-as-you-fetch implemented with help of Suspense we don’t wait for the response. We can can start rendering immediately.

react fetch as you render image

#How to Trigger Suspense fallback manually (Loading)

If you are using any fetching library like Relay or React Query. You would see that by wrapping your component with a Suspense, it magically fall into loading state whenever the component start to make a fetch.
What have happened internally is that during loading state the fetching library throws unresolved promise to trigger the Suspense state (loading state) in React.
to make this happen we can create a helper function, and call it a promise wrapper, that take a promise as an argument and resolved on request success or throw a promise while it is on pending state.

// resources.ts
type WrapPromiseReturn<T> = {
  read: () => T | never;
};

const wrapPromise = <T extends Record<string,never>,>(promise: Promise<T>): WrapPromiseReturn<T> => {
  let status: 'pending' | 'error' | 'success' = 'pending';
  let result= {};
  const suspender = promise
    .then((data) => {
      result = data;
      status = 'success';
    })
    .catch((error) => {
      result = error;
      status = 'error';
    });

  return {
    read(): T {
      if (status === 'pending') throw suspender;
      else if (status === 'error') throw result;
      else return result as T;
    },
  };
};

We can stub out some fake api requests to use with wrapPromise

// resources.ts
const getUser = (): Promise<string> =>
  new Promise(resolve => setTimeout(() => resolve('Fake User 1'), 10800));
const getOrders = (): Promise<string[]> =>
  new Promise(resolve =>
    setTimeout(() => resolve(['Fake order 1', 'Fake order 2']), 8000),
  );
const getCart = (): Promise<string> =>
  new Promise(resolve => setTimeout(() => resolve('Fake item on cart'), 1000));

type WrapPromiseReturn<T> = {
  read(): T;
};

Let’s create our resources using wrapPromise.

// resources.ts
export const getResources = () => {
  return {
    user: wrapPromise(getUser()),
    orders: wrapPromise(getOrders()),
    cart: wrapPromise(getCart()),
  };
};

Using this will be as easy as importing it.

import React from 'react';
import {Text, View, StyleSheet} from 'react-native';
import {getResources} from './resources';

const resources = getResources();

export const UserProfile: React.FC<{}> = () => {
  return (
    <>
      <View>
        <Text style={style.title}>User Profile</Text>
        <Text style={style.text}>{resources.user.read()}</Text>
      </View>
    </>
  );
};

export const OrdersView: React.FC<{}> = () => {
  return (
    <>
      <View>
        <Text style={style.title}>My Orders</Text>
        <View>
          {resources.orders.read().map(item => (
            <Text style={style.text}>{item}</Text>
          ))}
        </View>
      </View>
    </>
  );
};

export const CartView: React.FC<{}> = () => {
  return (
    <View>
      <Text style={style.title}>My Cart</Text>
      <Text style={style.text}>{resources.cart.read()}</Text>
    </View>
  );
};

const style = StyleSheet.create({
  title: {
    fontSize: 22,
    fontWeight: '800',
    color: 'tomato',
  },
  text: {
    fontSize: 14,
    fontWeight: '400',
  },
});
import {CartView, OrdersView, UserProfile} from './src/components';
const App = (): JSX.Element => {
  return (
      <ScrollView>
        <Suspense fallback={<Text>Loading User...</Text>}>
          <UserProfile />
        </Suspense>
        <Suspense fallback={<Text>Loading Orders...</Text>}>
          <OrdersView />
        </Suspense>
        <Suspense fallback={<Text>Loading Cart...</Text>}>
          <CartView />
        </Suspense>
      </ScrollView>
  );
};

#But wait, why this works

Fetch as you render is the best approach because we kickoff all our requests early with
resources = getResources(); yet we didn’t wait for the response to start rendering. Meanwhile, we rely on React to suspense then retry rendering our component as the data streamed back from our initial request.

Show Comments (0)

Comments

Related Articles

Valid Sudoku
General

Valid Sudoku

So this article is about validating a fun logic game called sudoku. Sudoku board is consists of 9×9 gird, which contains 3×3 subgrids.The objective is to fill a 9×9 grid...

Posted on by Baraa Abuzaid
Set a new user with password login on AWS EC2 linux Instance
AWS

Set a new user with password login on AWS EC2 linux Instance

Login to your elastic compute instance with a private-key each time isn’t quite convenient. So we will change that by creating a new user, set ssh config and enabling...

Posted on by Baraa Abuzaid