React in 2019 Part 3: Render Status Component

26 september 2019

This read is part of the series 'React in 2019'. This series is a combination of examples how I've recently coded. These can mostly be read and understood without knowledge of other parts

Previously, I covered a simple principle for knowing when data is ready to render:

  1. Data is done fetching (done 'loading')
  2. Data has no errors
  3. There is data (data object/array isn't empty)

In this article, I just want to demonstrate how you can separate your React Component into two parts:

  • The one that is ready to render
  • The one that isn't

I will explain the how before the why this time.

Ok, How?

We simply have a separate component which renders errors or a loading indicator.

The component

const RenderStatus = ({ hasErrors, isLoading }) => {
  if (isLoading) {
    return <>Loading...</>; // place your own spinner/loading screen here
  }

  if (hasErrors) {
    return (
      // Customize this later to actually render the errors
      <>An Error has occurred!</>
    );
  }

  return null;
};

export default RenderStatus;

Usage in your page

// dataIsReady expects { isFetching, hasErrors, data }
const readyToRender = dataIsReady({ ...usersResponse });

if (!readyToRender) {
  return (
    <MainLayout>
      <RenderStatus {...usersResponse} />
    </MainLayout>
  );
}

You can read Modern React App Part 2: Ready to Render for the dataIsReady explanation.

Ok, why?

  • Cleaner/ better separation of concerns
  • A reusable way to show your errors and loading
  • Independent of the way you handle your state or data

Example of rendering errors

This should be specific for the API you are working with, and the way your error object looks. I'll show you an example of mine, but you should be aware that you have to research on your own what's best in your use case.

In my case, my errorObject initializes like this:

const errorObject = {
  data: null,
  message: null,
};

And after an API call, it can look like this:

const errorObject = {
  data: {
    usersettings: [
      'Username must be longer than 3 characters',
      'Password must have at least one special character',
    ],
  },
  message: '409: Conflict',
};

Here, the response data is an object. But I've also had cases with this particular API in which I simply got a plain/text back:

const errorObject = {
  data: "You are not authorized"
  message: '403: Forbidden',
};

So I have to basically expect both objects and strings:

const RenderStatus = ({ errorData, errorMessage, loading }) => {
  if (loading) {
    return <>Loading...</>;
  }

  if (errorMessage) {
    const renderErrors = (errData) => {
      // check if errorResponse is object because API sometimes returns plain text.
      if (!isObject(errData)) {
        return errorData;
      }
      return Object.entries(errData).map(([errorKey, errorMessages]) => (
        <div key={errorKey}>
          <h3>{errorKey}</h3>
          {errorMessages && (
            <ul>
              {errorMessages.map((errMessage, i) => (
                <li key={i}>{errMessage}</li>
              ))}
            </ul>
          )}
          <br />
        </div>
      ));
    };
    return (
      <div>
        <h2>{errorMessage}</h2>
        {errorData && renderErrors(errorData)}
      </div>
    );
  }

  return null;
};

export default RenderStatus;

the isObject function:

export const isObject = (value): boolean => {
  return value && typeof value === 'object' && value.constructor === Object;
};

Again, this is just an example. You'll need to figure out the best way to build and display your own error data. Be sure what you can expect from the API, figure out the edge cases and how you want to render your error data.

Optional: Give it children

You might want to wrap the Render status component around your page or component.

const RenderStatus = ({ hasErrors, isLoading, children }) => {
  if (isLoading) {
    return <>Loading...</>; // place your own spinner/loading screen here
  }

  if (hasErrors) {
    return (
      // Customize this later to actually render the errors
      <>An Error has occurred!</>
    );
  }

  return <>{children}</>;
};

export default RenderStatus;