Solutions to frustrations with React Hooks
Tags: react, javascript, webdev
Published: 2019-11-04 14:28:04

---
title: Solutions to frustrations with React Hooks
published: true
date: 2019-11-04 14:00:34 UTC
tags: react,javascript,webdev
canonical_url: https://blog.logrocket.com/solutions-to-frustrations-with-react-hooks/
cover_image: https://thepracticaldev.s3.amazonaws.com/i/jb8hcuijryc008qx7ff7.png
---
**Written by [Paul Cowan](https://blog.logrocket.com/author/paulcowan/)**✏️

One of my previous posts, [Frustrations with React Hooks,](https://dev.to/bnevilleoneill/frustrations-with-react-hooks-36n9) got an incredible amount of views and topped [hacker news](https://news.ycombinator.com/) at one point. The post also got lots of comments, some of which have changed how I view Hooks and given me a completely new and positive way of viewing them.

The [last post](https://dev.to/bnevilleoneill/frustrations-with-react-hooks-36n9) cited a `useFetch` example that abstracts away the common code for calling a remote API endpoint. A `fetch` abstraction is the sort of thing I expect to be made reusable by Hooks. I want loading and error states all wrapped up in a Hook just like we used to do with Redux middleware. Below is an example of what I want to write for the client code:

```jsx
const asyncTask = useFetch(initialPage);
useAsyncRun(asyncTask);

const { start, loading, result: users } = asyncTask;

if (loading) {
  return <div>loading....</div>;
}

return (
  <>
    {(users || []).map((u: User) => (
      <div key={u.id}>{u.name}</div>
    ))}
  </>
);
```

I cited an example based on [react-hooks-async](https://github.com/dai-shi/react-hooks-async) which has a `useFetch` Hook.

Here is a [CodeSandbox](https://codesandbox.io/s/dependency-array-cxowi?from-embed) containing the scaled-down example:

{% codesandbox cxowi %}

And here is a code listing:

```jsx
const createTask = (func, forceUpdateRef) => {
  const task = {
    start: async (...args) => {
      task.loading = true;
      task.result = null;
      forceUpdateRef.current(func);
      try {
        task.result = await func(...args);
      } catch (e) {
        task.error = e;
      }
      task.loading = false;
      forceUpdateRef.current(func);
    },
    loading: false,
    result: null,
    error: undefined
  };
  return task;
};

export const useAsyncTask = (func) => {
  const forceUpdate = useForceUpdate();
  const forceUpdateRef = useRef(forceUpdate);
  const task = useMemo(() => createTask(func, forceUpdateRef), [func]);

  useEffect(() => {
    forceUpdateRef.current = f => {
      if (f === func) {
        forceUpdate({});
      }
    };
    const cleanup = () => {
      forceUpdateRef.current = () => null;
    };
    return cleanup;
  }, [func, forceUpdate]);

  return useMemo(
    () => ({
      start: task.start,
      loading: task.loading,
      error: task.error,
      result: task.result
    }),
    [task.start, task.loading, task.error, task.result]
  );
};
```

Many comments mentioned the complexity of this approach and the most telling comments mentioned that this implementation is not very declarative.

[![LogRocket Free Trial Banner](https://i0.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/f760c-1gpjapknnuyhu8esa3z0jga.png?resize=1200%2C280&ssl=1)](https://logrocket.com/signup/)

## Hooks are for reusable lifecycle behavior

Without question, the best comment in the comments section was from [Karen Grigoryan](https://twitter.com/KaaFury) who pointed out that Hooks are a place for reusable lifecycle behavior.

[react-hooks-async](https://github.com/dai-shi/react-hooks-async) and the example in the [CodeSandbox](https://codesandbox.io/s/dependency-array-cxowi?from-embed) uses the `useAsyncRun` function to kick start the lifecycle change event:

```jsx
export const useAsyncRun = (asyncTask,...args) => {
  const { start } = asyncTask;
  useEffect(() => {
    start(...args);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [asyncTask.start, ...args]);
  
useEffect(() => {
    const cleanup = () => {
      // clean up code here
    };
    return cleanup;
  });
```

React is often touted as being a declarative framework, and one of the reasons I fell in love with React is the one-way data flow story. `useAsyncRun` feels more imperative than declarative.

## The tao of React

How React works best is that we change props or state, and a component reacts naturally.

[Karen](https://twitter.com/KaaFury) kindly created this [CodeSandbox](https://codesandbox.io/s/dependency-array-cl99d) that not only simplifies things but also makes things feel much more reacty (yes this is now an actual word) and declarative:

{% codesandbox cl99d %}

`useFetch` now looks like this:

```jsx
const fetchReducer: FetchReducer = (state, action) => {
  switch (action.type) {
    case "FETCH_START": {
      return { data: null, isLoading: true, error: null };
    }
    case "FETCH_SUCCESS": {
      return { data: action.payload, isLoading: false, error: null };
    }

    case "FETCH_ERROR": {
      return { data: null, isLoading: false, error: action.payload };
    }
    default:
      return state;
  }
};

export const useFetch = (initial) => {
  const [state, dispatch] = useReducer(fetchReducer, initialState);

  const getFetchResult = useCallbackOne(
    async (overrides) => {
      dispatch({ type: "FETCH_START" });
      try {
        const result = await api({ ...initial, ...overrides });
        dispatch({ type: "FETCH_SUCCESS", payload: (result as unknown) as T });
      } catch (err) {
        dispatch({ type: "FETCH_ERROR", payload: err });
      }
    },
    [initial]
  );

  return [state, getFetchResult];
};
```

The `useFetch` Hook in the above code returns a `getFetchResult` function. `getFetchResult` uses the `dispatch` function that is returned from `useReducer` to orchestrate lifecycle changes.

Using `useState` and `useReducer` is what we use for triggering changes in effects but in a declarative way. Forcing a re-render is swimming upstream in React and going against the declarative nature of React. I think I have yet again fallen in love with React’s one-way data flow. The one-way data flow is what drew me to React, and it still tames the chaos out of a heavy JavaScript application.

React is supposed to work this way, we change state, and the component knows how to re-render and the `useEffect` blocks of code are executed in response to state changes.

The client code now looks like this:

```jsx
const [fetchResult, getfetchResult] = useFetch<User[]>(initialPage);

  const { data: users, isLoading, error } = fetchResult;

  // to keep reference identity in tact until next remount
  const defaultUsersRef = useRef<User[]>([]);

  // to kick off initial request
  useEffect(() => {
    getfetchResult(initialPage);
  }, [getfetchResult]);

  if (isLoading) {
    return <div>loading....</div>;
  }

  if (error) {
    return <div>error : {JSON.stringify(error)}</div>;
  }

  return (
    <>
      <Users users={users || defaultUsersRef.current} />
      <Knobs onClick={getfetchResult} />
    </>
  );
```

`getFetchResult` can now be used in a `useEffect` when the component is first mounted and also in an event handler.

A big thank you to Karen for this great example.

It is also worth noting that [suspense](https://reactjs.org/docs/concurrent-mode-suspense.html) might be dropping soon and this might be the real fit for a `useFetch` solution.

The observant of you will have noticed that the `getFetchResult` uses `useCallbackOne` from [use-memo-one](https://github.com/alexreardon/use-memo-one). `useCallbackOne` is a safe alternative to `useCallback`. `useCallbackOne` does a shallow check on the values of the dependency array and not the array references. This is still a frustration with React Hooks that we need an external library for this, which brings us on nicely to the stale closure problem.

## The stale closure problem

I’ve always had a fear of closures due to weird and not so wonderful things happening when dealing with closures. Closures are a fact of life when dealing with Hooks. Below is an example that illustrates this phenomenon beautifully:

```jsx
const useInterval = (callback, delay) => {
  useEffect(() => {
    let id = setInterval(() => {
    callback();
  }, 1000);
    return () => clearInterval(id);
  }, []);
};

const App = () => {
 let [count, setCount] = useState(0);

 useInterval(() => setCount(count + 1), 1000);

 return <h1>{count}</h1>;
};
```

This [CodeSandbox](https://codesandbox.io/s/nifty-wozniak-1jjtz) shows this great evil in action:

{% codesandbox 1jjtz %}

What happens is that `useEffect` in the `useInterval` Hook captures the count from the first render with the initial value, which is `0`. The `useEffect` has an empty dependency array which means it is never re-applied and always reference `0` from the first render and the calculation is always `0 + 1`.

If you want to use `useEffect` well, you need to ensure that the dependency array includes any values from the outer scope that changes over time and are used by the effect.

The [react-hooks/exhaustive-deps](https://reactjs.org/docs/hooks-rules.html) linting rule does, for the most part, a good job of highlighting the missing dependencies and it rightly points out that `callback` is missing in the array passed as a second argument to `useEffect`:

```jsx
const useInterval = (callback, delay) => {
  useEffect(() => {
    let id = setInterval(() => {
      callback();
    }, delay);
  
    return () => clearInterval(id);
  }, [callback, delay]);
};

const App = () => {
  let [count, setCount] = useState(0);

  useInterval(() => setCount(count + 1), 1000);

  return <h1>{count}</h1>;
};
```

The problem we have is that the callback passed to `useInterval` is an arrow function which means it is recreated on each render:

```jsx
useInterval(() => setCount(count + 1), 1000);
```

## One solution to stale closures

[Dan Abramov](https://twitter.com/dan_abramov) made a case for storing the callback in a mutable ref in [this post](https://overreacted.io/making-setinterval-declarative-with-react-hooks/).

I have seen the same solution appearing in several packages in various guises based on this theme of storing the callback in a mutable ref. I am taking my example from [formik](https://github.com/jaredpalmer/formik) which provides a `useEventCallback` Hook that takes care of storing the callback in a mutable Hook.

```jsx
function useEventCallback(fn) {
  const ref = React.useRef(fn);

  useEffect(() => {
    ref.current = fn;
  });

  return React.useCallback(
    (...args) => ref.current.apply(void 0, args),
    []
  );
}

function useInterval(callback, delay) {
  const savedCallback = useEventCallback(callback);

  useEffect(() => {
    function tick() {
      savedCallback();
    }

    let id = setInterval(tick, delay);
    return () => clearInterval(id);
  }, [delay]);
}

const App = () => {
  let [count, setCount] = useState(0);

  useInterval(() => {
    setCount(count + 1);
  }, 1000);

  return <h1>{count}</h1>;
};
```

Storing the callback in a mutable ref means the latest callback can be saved in the ref on each render.

This [CodeSandbox](https://codesandbox.io/s/floral-feather-fk9r3) shows `useEventCallback` in action:

{% codesandbox fk9r3 %}

## Conclusion

Hooks are a mind shift, and I think we need to realign our thinking. I was not looking at what they have to offer without wearing React spectacles. Hooks fit nicely into React’s declarative nature, and I think they are a great abstraction where state changes and components know how to react to the state change. Tremendous!
* * * 
**Editor's note:** Seeing something wrong with this post? You can find the correct version [here](https://blog.logrocket.com/solutions-to-frustrations-with-react-hooks/).

## Plug: [LogRocket](https://logrocket.com/signup/), a DVR for web apps
 
![LogRocket Dashboard Free Trial Banner](https://i2.wp.com/blog.logrocket.com/wp-content/uploads/2017/03/1d0cd-1s_rmyo6nbrasp-xtvbaxfg.png?resize=1200%2C677&ssl=1)
 
[LogRocket](https://logrocket.com/signup/) is a frontend logging tool that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
 
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
 
[Try it for free](https://logrocket.com/signup/).
* * *
The post [Solutions to frustrations with React Hooks](https://blog.logrocket.com/solutions-to-frustrations-with-react-hooks/) appeared first on [LogRocket Blog](https://blog.logrocket.com).