UI Feedback for Loading
When a user clicks a submit button that sends a request, we need to provide a loading UI feedback to inform the user that the backend is processing the request. A simple way to implement this is to have a global overlay loading animation that blocks the user's actions and makes them wait. However, this implementation can make users feel that they are not in control. To improve the user experience, we usually choose to have a button that can turn into a loading state.
The Burden of Repetitive Work
Handling requests and making each button change between loading and enabled states can be quite annoying. Although there are solutions like the useMutation hook in React Query, which can wrap the implementation details of the fetching and return the isLoading state for you to pass to the button props, sometimes we don't want to add another layer of fetching with hooks, or we want to make a pure request using other libraries like Redux or pure axios fetch. In such cases, we can use the following technique to make our lives easier.
Loading Button Component Design
We can create a custom button component with the following code:
import { useState } from 'react';
interface LoadingButtonProps {
isLoading?: boolean;
onClick: () => void | Promise<void>;
text?: string;
}
const LoadingButton = (props: LoadingButtonProps) => {
const { isLoading, onClick, text } = props;
const [isInternalLoading, setIsInternalLoading] = useState(false);
const handleClick = async () => {
setIsInternalLoading(true);
try {
await onClick();
} finally {
setIsInternalLoading(false);
}
};
if (isLoading || isInternalLoading) {
return <button disabled>Loading...</button>;
}
return <button onClick={handleClick}>{text}</button>;
};
We can then use this custom button component as follows:
const ExampleView = () => {
return (
<>
<p>
The Loading Button will change the loading state based on async function calling.
</p>
<LoadingButton
onClick={async () => {
await new Promise((resolve) => setTimeout(resolve, 1000));
}}
/>
</>
);
};
The Loading Button component will change its loading state automatically, and we also have the flexibility to explicitly pass the isLoading state to this component to fulfill other situations.
const ExampleView2 = () => {
const [isLoading, setIsLoading] = useState(false);
return (
<>
<p>
The Loading Button will change the loading state based on the passed props.
</p>
<LoadingButton
isLoading={isLoading}
onClick={() => {
console.log('sync function calling');
}}
/>
</>
);
};
Conclusion
Today I share a simple pattern to make fetch loading handling easier. I hope this little technique can help you to organize your code better and inspire you for other use cases!
Top comments (2)
Simple but useful 🤩
Thx! Hope it help.