More on useEffect()
Tags: react, javascript
Published: 2020-09-07 16:54:00

The useEffect hook can be confusing so hopefully this post will clear some things up. The purpose of the useEffect hook is to allow your code to react to state changes. To anyone familiar with stateful components, it replaces `componentDidMount()` and `componentDidUpdate()`.
​​
*TLDR*
The `useEffect()` hook allows your program to react to changes in state. It replaces `onComponentDidMount()`, `onComponentDidUpdate()`, and `onComponentWillUnmount()`. Also, you can't directly pass an async function to `useEffect()`, because it is not allowed to return a promise.
​
Sandbox featuring a live search demo: [https://codesandbox.io/s/live-search-demo-dvzbz?file=/src/App.js](https://codesandbox.io/s/live-search-demo-dvzbz?file=/src/App.js)
​
## Scenario
Let’s say we want to create live search/filter for our website - i.e. you type things into the search bar and the results below will automatically change without pressing a button. Initially our useEffect hook could look something like this:
```react
const [query, setQuery] = useState('');
const [data, setData] = useState([]);
// other code ...
useEffect(() => {
    fetchData().then(data => {
        setData(data);
        console.log(data);
    }
});
```
Note that I used plain promises instead of async-await more on that later.
​
This approach will "work", but there's a problem. If you check the console, it will be spammed with constant API calls. This is because by default, `useEffect()` will run every time the component re-renders aka every time state changes. As you can see that leads to an infinite loop where our `useEffect()` updates the state and the change of state triggers `useEffect()`. At best this leads to poor performance, and at worse could get your API key revoked.
​
The same problem existed with stateful components, and was often solved by checking to see which state changed, and then only updating when necessary.
​```react
componentDidUpdate(prevProps, prevState) {
    if (prevState.query !== this.state.query) {
        // do some update here
    }
    // otherwise do nothing
}
```
​
With hooks we don't have access to `prevProps` but thats where the second argument for `useEffect()` becomes useful. Generally, there are three things we can pass.
​
 1. Nothing `undefined`. This means that `useEffect()` will run on every re-render of the component. Analogous to implementing `componentDidMount()` and componentDidUpdate with no checks .
 2. An empty array `[]`. This means `useEffect()` will only run a single time. Analogous to implementing `componentDidMount()` .
 3. An array with value(s). React will check to see if the value(s) you passed in changed since the last render, and will fire `useEffect()` accordingly. Analogous to `componentDidMount()`  and `componentDidMount()`  with checks to see if the state property matches the `prevState` property.
​
By passing the query as the second argument, our `useEffect()` hook will only run when necessary.
```react
useEffect(() => {
    fetchData().then(data => {
        setData(data);
        console.log(data);
    }
}, [query]);
```
​
Full Sandbox: [https://codesandbox.io/s/live-search-demo-dvzbz?file=/src/App.js](https://codesandbox.io/s/live-search-demo-dvzbz?file=/src/App.js)
​
## Async in JS
There are three ways to handle asynchronous functions in Javascript. 
​
 1. Callbacks - es5
    ```react
    fetchData(query, function(err, data) {
        if (err) {
            console.log(err);
        }
        setData(data);
    });
    ```
 2. Promises - es6
    ```react
    fetchData(query)
        .then(data => setData(data))
        .catch(error => console.log(error));
    ```
 3. Async-await - es7
    ```react
    try {
        const data = await fetchData(query);
        setData(data);
    catch (err) {
        console.log(err);
    }
    ```
As you can see, using promises and async await presents a much cleaner way to handle asynchronous operations. Personally I like async-await the most, because it allows you to write synchronous looking code. Internally JS uses generators to pause execution until whatever is "awaited" has finished execution before continuing on. There's a catch though, you can only use the `await` keyword within of an `async` function.
​```react
async function loadData() {
    const data = await fetchData(query);
    setData(data);
}
​
// also works with arrow functions
const loadData = async () => {
    const data = await fetchData(query);
    setData(data);
}
```
​
## Async functions and useEffect()
React's `useEffect()` hook doesn't allow for async functions to be directly passed to it.
​```react
// NOT ALLOWED
useEffect(async () => {
    const data = await fetchData(query);
    setData(data);
});
```
​
This is because you have the option of returning a clean up function from the function passed to `useEffect()`. This is analogous to implementing `componentWillUnmount()` for class based components.
​```react
useEffect(() => {
    // do stuff...
    return () => {
        // do some kind of clean up
        someAPI.unsubscribe();
    }
});
```
​
The problem with async functions is that instead of returning a clean up function, or `undefined`, it will return a promise. There are two ways to get around this limitation:
​
1. Abstract it into an async function and call it
    ```react
    useEffect(() => {
        async function loadData() {
            const data = await fetchData(query);
            setData(data);
        }
        loadData();
    });
    ```
2. IIFE - declares a function and immediately executes it
    ```react
    useEffect(() => {
        (async () => {
            const data = await fetchData(query);
            setData(data);
        })();
    });
    ```
​
*Further Readings and Sources*
* [https://reactjs.org/docs/hooks-effect.html](https://reactjs.org/docs/hooks-effect.html)
* [https://medium.com/codebuddies/getting-to-know-asynchronous-javascript-callbacks-promises-and-async-await-17e0673281ee](https://medium.com/codebuddies/getting-to-know-asynchronous-javascript-callbacks-promises-and-async-await-17e0673281ee)
* [https://medium.com/javascript-in-plain-english/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435](https://medium.com/javascript-in-plain-english/how-to-use-async-function-in-react-hook-useeffect-typescript-js-6204a788a435)