useStatus: A Custom React Hook for Managing UI States
Because nobody likes a messy template
Why useStatus?
Handling UI states can start to make your template crowded and littered with variables. A recent talk I heard regarding State Machines inspired me to create a hook that helped me handle some issues that I constantly found myself running into. Keep in mind, the implementation was only loosely inspired by state machines.
What do state machines have to do with user interfaces?
A common pattern that developers implement when fetching data is a follows:
Step 1: When the component mounts, display a loading spinner or skeleton component where the data will be displayed.
Step: 2: When the data is retrieved, kill the loading spinner and replace it with a component that holds the data.
Step 3 (optional): If there is no data to be displayed from the response, show a component or text to let the user know that there is nothing to display.
Step 4: If an error occurs, show another component or button that will let the user know what happened, and potentially let them attempt to refetch the data without having to refresh their page.
If we’re following the State Machine paradigm, then the UI should only ever be in one state while these actions are being made.
- Loading (Request is currently happening)
- Success (Results returned)
- Empty (Successful requests but no results to display)
- Error (Unsuccessful request)
useStatus Hook:
Let’s say we have an application that renders similar components on many pages. We can use the useStatus hook to reuse the logic and render the proper components for those views via slots.
The hook will accept any component as a prop that you want to display for the specific state. Some components you may want to pass in would be:
- <LoadingState />
- <EmptyState />
- <ErrorState />
- <Results />
You would then be able to pass in the components into the Status component like so:
When we begin implementing the asynchronous logic, we make calls to the setState method and pass in the desired state to update the Status component and render the correct view. Those calls include:
- setState({ status: ‘loading’ })
- setState({ status: ‘error’, error })
- setState({ status: ‘empty’ })
- setState({ status: ‘success’, results })
Notice that our slots for error and success both take a callback instead of just a component. Structuring the slot like this allows us to further abstract handling the error and success statuses by allowing our hook to pass that information as well.
We can now think about all of our different UI states for a given component and couple them all together in one folder if necessary. A common use case for this pattern may include an interface that displays a table. Those components could be:
- <TableLoader />
- <TableEmpty />
- <TableError />
- <Table />
We would be able to achieve this very easily with useStatus.
Here is an example of the hook in action:
Hopefully, this hook helps you alleviate some of the pain points of managing component states during asynchronous actions. Please let me know if you have any improvements below!