React in 2019 Part 2: Ready to Render
25 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
Before we can safely render received data from an API, there is a lot to take into account:
- Did the request finish? Or are we still waiting on the API?
- Was the request itself a success or do we have errors?
- If there was an error, what type? In the response, or in setting up the request?
- Does the response have the expected data?
- Was this expected data safely sanitized/transformed (for example snake_case to camelCase or fallbacks per field)?
I will only cover parts of 1 and 2 in this article. The scope will stay within the React component. Error handling, HTTP requests and data transformation + validation will be covered in another article.
Y'all ready for this?
Before rendering my data...
For my React component, I can keep it a bit simpler than above. In a component, I want to know 3 things:
- Did the request finish? Or are we still waiting on the API?
- Are there any errors? If so, which?
- After the request completes, do I even have data?
The reason why this is more straightforward is because the more detailed points above should've already been dealt with. Where? In your HTTP requests, data validation + transforms and the code for your error object. (See the 'Separation of concerns' part in my The Custom useApi Hook article)
No data?
The third point might not be clear, so let me clarify. This might trigger you though! Ever did some troubleshooting with console.log(apiData)
and saw this []
or this {}
staring you in the face?
Empty objects and arrays are no fun, and honestly, you could (and probably should) prevent this from happening on a higher level in your code architecture: The part in your code where you do data transformations from the API. But you probably have worked with empty objects and arrays because of other mistakes, like:
- It being the default/initial value in your state ecosystem (in your redux reducers, mobx oberservables or wherever). By the way, this isn't always bad, but COULD be.
- The API giving you empty objects/arrays as if the request was successful...
- Helper functions returning empty stuff
So that's why we're also going to check for empty data 😁.
On the other hand. An empty object or array isn't always wrong. In a lot of cases, it's a valid state. All I'm trying to say is, do something with it. A valid state for your use case or not: Be ready
Let's get ready to render!
I could check the 3 points explained above with the following helper function:
DataIsReady helper function
export const dataIsReady = ({ isFetching, hasErrors, data }) =>
!isFetching && !hasErrors && !isEmpty(data);
As you can see this function only returns true
or false
.
The isEmpty function is from You-Dont-Need-Lodash-Underscore.
We can use the function in our UsersPage like this:
const UsersPage = () => {
const [usersResponse, getUsers] = useApi(fetchUsers);
React.useEffect(() => {
getUsers();
}, [getUsers]);
// dataIsReady expects { isFetching, hasErrors, data }
const readyToRender = dataIsReady({ ...usersResponse });
if (!readyToRender) {
return null
};
return (
<>
<ul>
{users.map(user => <li key={user.id}>{user.name}<li>)}
</ul>
<button onClick={createUser}>Submit</button>
</>
);
I'm passing everything from the environmentResponse object to dataIsReady since the keys I need in the object have the exact same name (isFetching
, hasErrors
and data
). Just like in my previous article, I am not demonstrating the error handling but just have the hasErrors
flag here.
You can also check the useApi hook in Modern React App Part 1: The Custom useApi Hook for more information about this custom hook.
Note: Whenever I write readyToRender, I always imagine Michael/Bruce Buffer shouting 'Let's get ready to renderrrrrrrrrr!' like in the following video: Michael Buffer - Let's Get Ready To Rumble!!!
Further customizing
I've just demonstrated the principle of checking if we can render data. However, we might also want:
- to render the errors (won't cover that here)
- Do more specific coding for empty data.
- show a loading indicator while we wait for the response.
- Tweak what can be rendered without a successful response
Loading indicator example:
if (usersResponse.isFetching) {
return <>Loading...</>;
}
Another thing to think about is what data we actually need before we can render a component or part of a component. In a lot of cases, we only want to prevent the rendering of a certain elements.
For example:
return (
<>
{usersResponse.isFetching ? "Fetching users..." : (
<ul>
{users.map(user => <li key={user.id}>{user.name}<li>)}
</ul>
)}
<button onClick={createUser}>Submit</button>
</>
);
In above example, the button can still be rendered even if we are still waiting for the response.
It's better to move the logic inside a separate function/reusable component. Even more so because we want to code for more specific use cases, like rendering the errors and prevent the rendering of <ul>
when there is no data at all. You could do something like !users.length ? "No users found" : //render users
but this can cause your JSX it be filled with too much logic. Rather, let a function or component handle that. I'll show you how in the next part of this series: The Render Status Component