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 - ErrorBoundarycomponent)
- the promise is in a pending state (dealt by - Suspense component)
So this is how our code will look at a high level:
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:
function BlogPost({resource:{read}}) {
  const {title,body} = read();
  // ...
where resource is an object created by function createResource:
const resource1=createResource(fetchPosts(0))
Let's look at the definition of createResource:
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:
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:
  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:
function Share() {
  useEffect(() => {
    console.log("Effect Share");
    return () => {
      console.log("Cleanup Share");
    };
  });
  console.log("Render Share");
  return (
    <div>Share: 
      <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:
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:
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. 
 


 
    
Top comments (0)