DEV Community

Cover image for React useEffect Hook: Use cases
Vincent Kipyegon
Vincent Kipyegon

Posted on

React useEffect Hook: Use cases

React useEffect is a react hook function that was introduced in React version 17; used for executing side effects following the mounting of a React component or as a result of changes in state declared variables. The hook expects 2 arguments; A callback function and an optional array of dependencies,it optionally return a function.

Visually, the useEffect function was developed by the React team with its functionality abstracted away and only the business logic (arguments, and return function) accessible to end users.
Here is a React function component we will be using for our use cases:

function BlogPosts() {
  const [posts, setPosts] = React.useState([]);
  const [error, setError] = React.useState("");
  const [pending, setPending] = React.useState(false);
  const textInput = React.useRef(null);
  const form = React.useRef(null);

  const getPosts = async () => {
    let url = `https://jsonplaceholder.typicode.com/posts/`;
    try {
      setPending(true);
      const response = await fetch(url);

      if (response.ok) {
        const payload = await response.json();
        setError(""); //clear previous error message if any
        return payload;
      } else throw new Error("Something went wrong....");
    } catch (error) {
      setError(error.message);
      return error;
    } finally {
      setPending(false);
    }
  };

 /*we will add a useEffect hook here*/

  // render some jsx
  if (pending) return <p>Loading Posts...</p>; // pending state

  if (error.length > 0) return <p>{error}</p>; // rejected
  // you can create a <PostsComponent/>  of your own
  if (posts.length > 0) return <h3>Received {posts.length} posts.</h3>; // resolved

  return null;  /* just before we fetch posts show blank screen*/


}
Enter fullscreen mode Exit fullscreen mode

Use cases:

  1. Performing side effects
  2. Tracking dependencies
  3. Setup and tear down processes
  4. Manipulating the DOM
  5. UseEffect can NOT be converted to async function.

1. Performing side effects

The hook can be used to perform executions after a component is mounted. This includes: network requests by fetch data from a rest API, initializing animations,setting up app related event listeners, setting timeouts and intervals.

Add this piece of code below the getPosts function

React.useEffect(() => {
    // fetch data
     getPosts().then((res) => {
      setPosts(res);
    })


    // start timers
    setTimeout(() => {
      console.log("I am executed after 5 seconds");
    }, 5000);
    setInterval(() => {
      console.log("I am executed after every 5 seconds");
    }, 5000);
    // app event listeners
    addEventListener("resize", () => {
      console.log("Window resized");
    });
    addEventListener("offline", () => {
      console.log("App is offline");
    });
    addEventListener("online", () => {
      console.log("App is back online");
    });

  });
Enter fullscreen mode Exit fullscreen mode

2. Tracking Dependencies

Our Our useEffect function up there runs every time there is a render introducing something that looks like a bug.

UseEffect hook runs everytime the UI is rendered and when the component unmounted. The UI is always re-rendered whenever a state variable (variable declared by useState hook) changes.
Sometimes, though, you only want to run useEffect once or when a particular state variable changes.

The second optional argument supplied to useEffect is an array of dependencies, it determines how the useEffect hook is called; an empty array triggers the useEffect during initial render and when the component is unmounted while an array with elements will trigger the hook whenever there are changes are in one of the array elements.

we add an empty array as our second argument so that our hooks runs once

React.useEffect(() => {
    // fetch data
     getPosts().then((res) => {
      setPosts(res);
    })


    // start timers
    setTimeout(() => {
      console.log("I am executed after 5 seconds");
    }, 5000);
    setInterval(() => {
      console.log("I am executed after every 5 seconds");
    }, 5000);
    // app event listeners
    addEventListener("resize", () => {
      console.log("Window resized");
    });
    addEventListener("offline", () => {
      console.log("App is offline");
    });
    addEventListener("online", () => {
      console.log("App is back online");
    });

  },[]);


Enter fullscreen mode Exit fullscreen mode

Primitive data types such as numbers,strings and booleans, UseEffect can easily track their changes while non-primitives such as as objects and arrays are mutable data types stored by reference, useEffect has a problem tracking their changes since “non-primitives refer to the same instance” that is they always look identical.

To make useEffect track changes in non primitivies:

  1. UseMemo hook can be used in conjuction with useEffect to track changes in an array. It calcualates changes between elements previous array and the next array through memoization.
// useMemo will trigger useEffect if posts array has changed
Const _postChanges=React.useMemo(()=>posts,[posts]) /*return the posts array inside useMemo hook*/
/* now the useEffect will be able to listen to changes on posts array by tracking _postChanges array from useMemo */
React.useEffect(()=>{
console.log('you have changes in your array')
},[_postChanges])

Enter fullscreen mode Exit fullscreen mode
  1. Tracking an object property instead, we assume every object always has a unique property mostly id property.
const [post,setPost]=React.useState({id:1,title:"UseEffect hook"})
const nextPost={id:2,"UseReducerHook"}

React.useEffect(()=>{
console.log(`The new post id is ${post.id}`)
},[post?.id]) // runs whenever post id changes

return(<div>
<p>The current post is {post?.title} </p>
<button onClick={()=>setPost(nextPost)}>Next Post</button> </div>)
Enter fullscreen mode Exit fullscreen mode

3. Setup and tear down processes

UseEffect hook comes in handy when setting up processes that can be used when the components mounts and removed when it unmounts, e.g setTimeout, setInterval, adding and removing event listeners.
The hook returns a function that can be used to perform these tear down processes.

_ we now declare two new variables for clearing setInterval and setTimeout, we then return a teardown function_

React.useEffect(() => {
    // fetch data
     getPosts().then((res) => {
      setPosts(res);
    })
let timeout,interval; // for clearing intervals and timeout

    // start timers
    timeout=setTimeout(() => {
      console.log("I am executed after 5 seconds");
    }, 5000);
   interval= setInterval(() => {
      console.log("I am executed after every 5 seconds");
    }, 5000);
    // app event listeners
    addEventListener("resize", () => {
      console.log("Window resized");
    });
    addEventListener("offline", () => {
      console.log("App is offline");
    });
    addEventListener("online", () => {
      console.log("App is back online");
    });
return function(){
// tear down events and intervals
 if(timeout)clearTimeout(timeout);
if(interval)clearInterval(interval)

removeEventListener("resize", () => {
      console.log("Window event removed");
    });

}


  },[]);
Enter fullscreen mode Exit fullscreen mode

4. Manipulating the DOM

React implements a virtual DOM before reconcilling changes to the real DOM on the browser. Most a times you want to access to a DOM element such as form or form input elements. We can gain access to the DOM using useRef hook then manipulate it inside the useEffect hook. Sometimes you can use DOM API methods such as document.getElementById() since we know the DOM is always mounted. It is not advisable though; go for useRef hook.

React.useEffect(() => {
    // fetch data
     getPosts().then((res) => {
      setPosts(res);
    })
let timeout,interval; // for clearing intervals and timeout

    // start timers
    timeout=setTimeout(() => {
      console.log("I am executed after 5 seconds");
    }, 5000);
   interval= setInterval(() => {
      console.log("I am executed after every 5 seconds");
    }, 5000);
    // app event listeners
    addEventListener("resize", () => {
      console.log("Window resized");
    });
    addEventListener("offline", () => {
      console.log("App is offline");
    });
    addEventListener("online", () => {
      console.log("App is back online");
    });
/*MANIPULATE THE DOM */
 // access the input on the DOM and make put placeholder after 2 seconds
    setTimeout(
      () => {
        if (textInput.current) {
          textInput.current.setAttribute("placeholder", "Enter post title....");
          textInput.current.focus();
        }
      },

      3000
    );
    // manipulate the DOM, give some time out to make sure its there
    setTimeout(() => {
      const formWrapper = document.getElementById("form-wrapper");
      if (formWrapper) formWrapper.style.backgroundColor = "red";
    }, 100);
    // use DOM API to change background to light gray
  }, []);
/*CLEAN UP THE COMPONENT*/
return function(){
// tear down events and intervals
  if(timeout)clearTimeout(timeout);
if(interval)clearInterval(interval)
removeEventListener("resize", () => {
      console.log("Window event removed");
    });

}
   // Also update the jsx returned
  let style = { display: "block", width: 200, padding: 5 };
  let btnStyle = {
    ...style,
    background: "hotpink",
    padding: 5,
    borderRadius: 5,
    marginTop: 10,
    color: "white",
  };
// render components
  if (pending) return <p>Loading Posts...</p>; // pending state

  if (error.length > 0) return <p>{error}</p>; // rejected 

  return (
    <div style={{ padding: "1rem" }} id="form-wrapper">
      <form onSubmit={handleSubmit} ref={form}>
        {posts.length > 0 && <h3>Received {posts.length} posts.</h3>}
        <div>
          <label htmlFor="username" style={style}>
            Enter Post Title
          </label>
          <input id="username" style={style} ref={textInput} />
        </div>

        <button style={btnStyle}>Submit</button>
      </form>
    </div>
  );

  },[]);
Enter fullscreen mode Exit fullscreen mode

5. UseEffect can NOT be converted to async function.

React hook has few special rules;
1.) React hooks only work inside React components.
2.) React hooks can only be declared on top level scope inside a react component. They cannot be declared conditionally.

Const [deadline,setDeadline]=React.useState(true);
// DON’T
if(deadline){

useEffect(()=>{},[])
}
// DO
useEffect(()=>{
if(deadline){
// do something about it
}
},[])

Enter fullscreen mode Exit fullscreen mode

Most React beginners are always eager break these rules with ease.
There is another unwritten rule that React frowns upon; making the callback provided to useEffect as async/await function.

// DON'T
React.useEffect(async()=>{
const payload=await getPosts();
setPosts(payload);
},[])

Enter fullscreen mode Exit fullscreen mode

When callback used as an async/await function it becomes a promise that blocks the main thread until it settles instead of simply running effects and returning a function.

We run async functions inside useEffect this way:


React.useEffect(()=>{
 getPosts().then(res=>setPosts(payload)).catch(err=>console.error(err.message));
},[])

Enter fullscreen mode Exit fullscreen mode

Alternatively we can use an immediately invoked function expressions (IIFE) inside useEffect like so:

React.useEffect(()=>{
(async()=>{
const payload=await getPosts();
if(payload) setPosts(payload)
})()

},[])
Enter fullscreen mode Exit fullscreen mode

Top comments (3)

Collapse
 
miketalbot profile image
Mike Talbot ⭐ • Edited

The reason not to make a useEffect async is nothing to do with blocking the main thread. A useEffect returns a function to be called when it is unmounted/no longer the current dependency array value - as an async function always returns a Promise it isn't correctly returning the termination function.

Collapse
 
kipyegonline profile image
Vincent Kipyegon

Alright, great point there..noted..

Collapse
 
brense profile image
Rense Bakker

A small note, whether async/await or promisses block the main thread depends on what happens inside the promise. If you execute a fetch request, or set a timeout for example, it won't block the main thread.