Loadables - a simple way to load data in React

I’m a software ninja 🥷 who loves abstractions and spread functional programming everywhere. I've over 2 years of experience and I'm based in Delhi, India🇮🇳
Search for a command to run...

I’m a software ninja 🥷 who loves abstractions and spread functional programming everywhere. I've over 2 years of experience and I'm based in Delhi, India🇮🇳
No comments yet. Be the first to comment.
Series contains React.js articles which directly from learning at job as software enigineer.
State management is always a problem to solve in big applications. Many design patterns and libraries emerge to solve some issues by proposing different ways of doing things. In this article, our main focus is on the philosophy of managing your state...
Building a Layered Component Architecture with MUI
Layered Architecture Guidelines

When you are building a text editor for general-purpose writing, sooner or later you will need to support emoji’s for your users. In this blog post, we will learn to create an emoji picker for your tiptap-based editor. Let’s define the EmojiData Mod...

State management is always a problem to solve in big applications. Many design patterns and libraries emerge to solve some issues by proposing different ways of doing things. In this article, our main focus is on the philosophy of managing your state...

Availability and reliability are important parts of systems; if you are exposing an API, it should be available and reliable to consumers. You must have experienced this when you grew your API endpoints and received more traffic. It is okay as the bu...

The real-world app involves data loading via some API and showing UI based on the states of the API. For example, while data is loading, you may show a loader animation, but on error, you may show an error UI. This fairly simple-looking task ends up getting complex super fast and is more difficult to maintain with all the spaghetti code for UI synchronization. So here I propose the loadable pattern to simplify data loading and synchronize the UI with it.
In this example, we are going to load a list of todos. Here we are using react-redux as a state management solution. Below we will see how to create a store and reducer with react-redux. However, you can directly skip to "loadables" if you familiar with react-redux-context store.
Let's start by creating a react-redux-context-store for storing our todos. The following sample is taken from react-redux.
// [filename: todo.store.jsx]
import React from 'react'
import {
Provider,
createStoreHook,
createDispatchHook,
createSelectorHook,
from "react-redux";
import { createStore } from "redux";
// reducer for the state
import { reducer } from "./store.reducer"
// react context store
const TodoContext = React.createContext(null)
// create redux state selector and dispatch from context
export const useTodoStore = createStoreHook(TodoContext)
export const useTodoDispatch = createDispatchHook(TodoContext)
export const useTodoSelector = createSelectorHook(TodoContext)
// create redux store from the reducer
const todoStore = createStore(reducer)
// create store provider wrap subtree
export function TodoStoreProvider({ children }) {
return (
<Provider context={TodoContext} store={todoStore}>
{children}
</Provider>
)
}
After creating a store provider we are going to create store.reducer.js where we define the reducer and actions for the store.
// [filename: todo.reducer.js]
export const loadNext = () => ({ type: 'load_next' });
export const addTodos = ({ todos, total }) => ({ type: 'add_todos', payload: { todos, total } });
export const setLoading = (loading) => ({ type: 'set_loading', payload: { loading } });
const InitState = {
status: 'idle', // idle | pending | resolve | reject
todos: [],
total: 0,
skip: 0,
limit: 10
};
export const reducer = (state = InitState, action) => {
switch (action.type) {
case 'load_next': {
if (state.todos.length < state.total && state.status !== 'pending') {
return {
...state,
status: 'pending'
};
}
return state;
}
case 'add_todos': {
return {
...state,
status: 'resolve',
todos: [...state.todos, ...action.payload.todos],
total: state.total + action.payload.todos.length
};
}
case 'set_loading': {
return {
...state,
status: action.payload.loading
};
}
default: {
return state;
}
}
};
Loadables are react components that wrap all data loading logic in it and update the store.
// [filename: App.js]
const App = () => (
<div>
<TodoStoreProvider>
{/* Loadable holds all data loading logic*/}
<TodoLoadable>
{/* Render todos */}
</TodoLoadable>
</TodoStoreProvider>
</div>
);
Now let's create a loadable:
// [filename: Todo.loadable.js]
function TodoLoadable(props) {
// react-redux state slice selector
const skip = useTodoSelector((state) => state.skip);
const limit = useTodoSelector((state) => state.limit);
const todoDispatch = useTodoDispatch();
// load data
useEffect(() => {
todoDispatch(setLoading('pending'));
api({ skip, limit })
.then((res) => todoDispatch({ todos: res.todos, total: res.total }))
.catch((e) => todoDispatch(setLoading('reject')));
}, [skip, limit]);
// render child
return <>{props.children}</>
}
The point to note here is that the loading logic is completely placed inside the loadable and the children can utilize the store to sync UI state accordingly. IsVisible is a utility component that can be used to render things conditionally.
// [filename: IsVisible.utility.jsx]
function IsVisible({ visible, unmountOnExit, ...props }) {
if (unmountOnExit && !visible) {
return null;
}
return <div {...props} style={{ ...props.style, display: visible ? 'flex' : 'none' }} />
}
We can use the IsVisible utility component to create state synced UI.
// [filename: Todo.jsx]
const Error = () => <div><h1>Error</h1></div>;
const Loader = () => <CircularProgress size="small" />
const Todos = () => {
const todos = useTodoSelector((state) => state.todos);
return <div>{todos.map((todo) => <h1>{todo}</h1>)}</div>
}
function IsErrorVisible(props) {
const isError = useTodoSelector((state) => state.status === 'reject');
return <IsVisible {...props} visible={isError} />
}
....more IsVisible for all API status 'reject' | 'resolve' | 'pending' | 'idle'
Now with the help of this IsVisible, we can render UI according to the state of API.
// [filename: App.js]
const App = () => (
<div>
<TodoStoreProvider>
{/* Loadable holds all data loading logic*/}
<TodoLoadable>
<IsErrorVisible><ErrorUI /></IsErrorVisible>
<IsTodoVisible><Todos /></IsTodoVisible>
<IsLoaderVisible><Loader /></IsLoaderVisible>
</TodoLoadable>
</TodoStoreProvider>
</div>
);
This is how loadable along with IsVisible utility makes it super easy to load data in react and make a code simple to write and understand. Here is a link to demo Codesandbox.