How to fetch data in React with Suspense and ErrorBoundary
Tags: react, suspense, errorboundary, fetch
Published: 2022-04-11 15:48:22

When we want to fetch data, we are dealing with a promise, so we have three possible scenarios:

- the promise resolves and it's a success

- the promise resolves and it's a failure (dealt by `ErrorBoundary` component)

- the promise is in a pending state (dealt by `Suspense component`)

So this is how our code will look at a high level:

```javascript
function App() {
  return (
    <>
    <ErrorBoundary>
      <Suspense fallback={<h1>Loading blog...</h1>}>
        <BlogPost resource={resource1} />
        <Share />
      </Suspense>
      </ErrorBoundary>
    </>
  );
}
```

`BlogPost` it's a component which reads on the state of a promise:

```javascript
function BlogPost({resource:{read}}) {
  const {title,body} = read();
  // ...
```

where `resource` is an object created by function `createResource`:

```javascript
const resource1=createResource(fetchPosts(0))
```

Let's look at the definition of `createResource`:

```javascript
function createResource(promise){
    let error,response
    promise.then(r=>response=r).catch(e=>(error=e))
    return {
        read(){
            if(error)throw error
            if(response) return response
            throw promise
        }
    }
}
```

As you see the `read` function can throw a promise, an error or return a successful response. It's like if the promise would be catch by the `Suspense` component and renders the fallback prop. The error would be catch by the `ErrorBoundary` component, which we are going to see in a sec, and will render an `ErrorScreen` component. When the promised is resolved successfully, the children of `Suspense` will be rendered.

This is the `ErrorBoundary` component:

```javascript
import {Component} from 'react'

function ErrorScreen({error:{message}}){
    return <div>
        Something went wrong: {message}
        </div>
}

class ErrorBoundary extends Component{
    state={error:null}
    static getDerivedStateFromError(e){
        return {error:e}
    }
    render(){
        const {children,fallback}=this.props
        const {error}=this.state
        if(error&&!fallback)return <ErrorScreen error={error} />
        if(error)return <fallback error={error} />
        return children
    }
}

export default ErrorBoundary
```

As you see it works similarly to the `Suspense` one, accepting a `fallback` property, which in this case defaults to the render of `ErrorScreen` component.

Let's see at the definition of `fetchPosts()` function:

```javascript
 
  let blogPosts = [
    {
      id: 1,
      title: 'qui est esse',
      body: 'est rerum tempore vitae\nsequi sint nihil reprehenderit'
    },
  ];
  
  export function fetchPosts(id) {
    let post = blogPosts[id];
    return new Promise((resolve) => {
      setTimeout(() => {
        console.log("fetched blogs");
        resolve(post);
      }, 2000);
    });
  }
```

As we can see, it returns a promise.

For completion, let's see at the definition of `BlogPost` and `Share` components:

```javascript
function Share() {
  useEffect(() => {
    console.log("Effect Share");

    return () => {
      console.log("Cleanup Share");
    };
  });

  console.log("Render Share");
  return (
    <div>Share:&nbsp;
      <span> twitter</span>
      <span> reddit</span>
    </div>
  )
}

function BlogPost({resource:{read}}) {
  const {title,body} = read();

  useEffect(() => {
    console.log("Effect BlogPost");
    return () => {
      console.log("Cleanup BlogPost");
    };
  });

  return (
    <div>
      <h1>Blog Post</h1>
      <h3>{title}</h3>
      <span>{body}</span>
    </div>
  );
}
```

And this is the output on the console when executing the app:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/95zyzc3mfq3ej485wi8x.png)

As you can see, with react 18 the `Share` component doesn't render until the `BlogPost` is ready, because they all belong to the children of the `Suspense` component. If we used react 17 instead, this would be the output in the console:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0jxs4a48y3pc4tf7brmi.png)

As you can see, it renders first the `Share` component before resolving the promise, and then, after the promise has been resolved successfully, it renders the `BlogPost` component.