Hooks are coming in React 16.7.

Introduction

React Hooks are a new feature coming in React 16.7. It allows us to write stateful function components that were impossible before without class components. The official docs are must-read, so check them out if you haven’t.

Besides stateful function components, this new feature allow us to build a custom hook to share logic between components. This has been possible with High-order Components (HoCs), and although there’s technically no difference what hooks can achieve, hooks simplify it a lot and reduce so called “wrapper hell”. This simplicity encourages building custom hooks, and this trend reminds me of npm’s early days.

This article explains how to write a custom hook and shows how it can share logic even if it’s a few lines of code. We take a simple data fetching (Fetch API) as an example.

The goal

Given the readers already learned the basis of React Hooks, we’d start with the goal example how a custom hook we develop would be used.

const MyComponent = () => {

const { error, loading, data } = useFetch('http://...');

if (error) return <Err error={error} />;

if (loading) return <Loading />;

return (

<DataView data={data} />

);

};

Here, useFetch is the custom hook. Isn’t it intuitive? MyComponent is a “stateful” function component whereas DataView is can be a stateless function component which should be more reusable.

Writing a custom hook

Let’s see how we write a custom hook to realize the above goal. A custom hook is just a function that uses some other hooks. We define a function first.

const useFetch = (url) => {

// ...

};

This hook returns three values, so we define three state values. Note that we don’t need to combine them in one state object.

const useFetch = (url) => {

const [error, setError] = useState(null);

const [loading, setLoading] = useState(true);

const [data, setData] = useState(null);

// ...

return { error, loading, data };

};

Now, the main part is defined in a useEffect hook. Notice, we pass url in the input array.

const useFetch = (url) => {

const [error, setError] = useState(null);

const [loading, setLoading] = useState(true);

const [data, setData] = useState(null);

useEffect(() => {

(async () => {

setLoading(true);

const response = await fetch(url);

const data = await response.json();

setData(data);

setLoading(false);

})();

}, [url]);

return { error, loading, data };

};

We haven’t implemented error handling. Let’s add it.

const useFetch = (url) => {

const [error, setError] = useState(null);

const [loading, setLoading] = useState(true);

const [data, setData] = useState(null);

useEffect(() => {

(async () => {

setLoading(true);

try {

const response = await fetch(url);

if (response.ok) {

const data = await response.json();

setData(data);

} else {

setError(new Error(response.statusText));

}

} catch (e) {

setError(e);

}

setLoading(false);

})();

}, [url]);

return { error, loading, data };

};

This is the basic custom hook to achieve our goal. It’s reusable in several components. You can check out the working example in this codesandbox.

https://codesandbox.io/s/github/dai-shi/react-hooks-fetch/tree/master/examples/01_minimal

Extending the custom hook

Our hook is still basic, and we might want to support POST method or non-JSON data. There could be various ways to extend this hook. Let’s try a simple way to keep Fetch API as much as possible.

const defaultOpts = {};

const useFetch = (input, opts = defaultOpts) => {

const [error, setError] = useState(null);

const [loading, setLoading] = useState(true);

const [data, setData] = useState(null);

const {

readBody = body => body.json(),

...init

} = opts;

useEffect(() => {

(async () => {

setLoading(true);

try {

const response = await fetch(input, init);

if (response.ok) {

const body = await readBody(response);

setData(body);

} else {

setError(new Error(response.statusText));

}

} catch (e) {

setError(e);

}

setLoading(false);

})();

}, [input, opts]);

return { error, loading, data };

};

One of the difficulties of this extended custom hook is that opts is passed in the input array of useEffect . Unless users carefully understand how it works, it may cause calling fetch es infinitely.

The following is one example how to use this custom hook.

const PostRemoteData = () => {

const opts = useMemo(() => ({

method: 'POST',

body: JSON.stringify({

title: 'foo',

body: 'bar',

userId: 1,

}),

readBody: body => body.text(),

}), []);

const { error, loading, data } = useFetch('http://...', opts);

if (error) return <Err error={error} />;

if (loading) return <Loading />;

return (

<span>Result: {data}</span>

);

};

The library

Although this is just a tiny custom hook, I made it as a library and publish it in npmjs.com.

Final notes

Actually, I’m not quite sure if Fetch API is an appropriate topic to explain building a custom hook. People might prefer data fetching layers isolated from view layers. Nevertheless, I hope this article explains how to develop a custom hook, and helps noticing its reusability.