Remove boilerplate handling loading, error, and result state of Promise!
using npm:
npm install @traveloka/react-load --save
using yarn:
yarn add @traveloka/react-load
“The best products don’t focus on features, they focus on clarity.” — Jon Bolt
Providing loading state, error, and result callback of a Javascript Promise is common in any application.
In ES6, there's a Promise that do some async function. Lot of duplicate code being used to get the state before the async function executed, and state of Promise error / Promise success state when async function is done.
This library will give a property state before and after execution of async function.
const {
isLoading: boolean,
isError: boolean,
error: Error | null,
retry: () => void,
result: T | null,
trigger: (...args: any[]) => Promise<T>
} = useLoad<T>((...args: any[]) => Promise<T>)
Example
import React, { useEffect } from 'react';
import { useLoad } from '@traveloka/react-load';
export default function UserListPage(props) {
const { isLoading, isError, error, retry, result, trigger } = useLoad(fetchUserList);
useEffect(() => {
trigger(); // call the fetchUserList that wrapped with load attributes
}, []); // only trigger componentDidMount
if (isLoading) return <LoadingPage />
if (isError) return <ErrorPage error={error} retry={retry} />
return (
....
);
}
- With decorator
import React from 'react';
import { load } from '@traveloka/react-load';
@load()
export default class UserListPage extends React.Component {
@load()
componentDidMount() {
return fetchUserList(); // Note: must return a Promise
}
render() {
const { load: { isLoading, isError, error, retry, result} } = this.props;
if (isLoading) return <LoadingPage />
if (isError) return <ErrorPage error={error} retry={retry} />
return (
....
);
}
}
- Without decorator
import React from 'react';
import { load, decorate } from '@traveloka/react-load';
class UserListPage extends React.Component {
componentDidMount() {
return fetchUserList();
}
render() {
const { load: { isLoading, isError, error, retry, result} } = this.props;
if (isLoading) return <LoadingPage />
if (isError) return <ErrorPage error={error} retry={retry} />
return (
....
);
}
}
decorate(UserListPage, {
componentDidMount: load(),
});
export default load()(UserListPage);
Property | Type | Default Value | Description |
---|---|---|---|
load.isLoading |
boolean | false | |
load.isError |
boolean | false | |
load.error |
Exception | null | |
load.result |
any | null | |
load.retry |
function | () => {} |
Using ES.next decorators is optional. This section explains how to use them, or how to avoid them.
Advantages of using decorators:
- Minimizes boilerplate, declarative.
- Easy to use and read. A majority of the MobX users use them.
Disadvantages of using decorators:
- Stage-2 ES.next feature
- Requires a little setup and transpilation, only supported with Babel / Typescript transpilation so far
You can approach using decorators in two ways:
- Enable the currently experimental decorator syntax in your compiler (read on)
- Don't enable decorator syntax, but leverage the built-in utility decorate to apply decorators to your classes / objects.
Using decorator syntax:
import * as React from 'react';
import { load } from '@traveloka/react-load';
@load()
class Timer extends React.Component {
@load()
componentDidMount() {
/* ... */
}
render() {
const { load: { isLoading, isError, error, retry, result} } = this.props;
/* ... */
}
}
Using the decorate
utility:
import compose from 'lodash/fp/compose';
import { load, decorate } from '@traveloka/react-load';
class Timer extends React.Component {
componentDidMount() {
/* ... */
}
render() {
/* ... */
}
}
// decorate method
decorate(Timer, {
componentDidMount: load(),
});
export default load()(Timer);
To enable support for decorators, follow the following steps. Install support for decorators: npm i --save-dev babel-plugin-transform-decorators-legacy
. And enable it in your .babelrc
file:
{
"presets": [
"es2015",
"stage-1"
],
"plugins": ["transform-decorators-legacy"]
}
Note that the order of plugins is important: transform-decorators-legacy
should be listed first. Having issues with the babel setup? Check this issue first.
For babel 7, see issue 1352 for an example setup.
Written by Jacky Wijaya (@jekiwijaya) at Traveloka.