DEV Community

Cover image for How React’s useEffect Hook Changed My Life
William Bojczuk
William Bojczuk

Posted on

How React’s useEffect Hook Changed My Life

Oh my God, why can’t my JavaScript find my React Component. WhAt Am I DOiNg WronGGg!!!!?!?

Is probably something every developer starting out with React has said at some point. Luckily the creators of React gave us a hook to combat this.

Any code that alters a component after the component has been mounted is called a “side effect”. To accomplish this, us developers are left with the useEffect hook.

In the below example, we are attempting to replace the textContent of a paragraph element with new text. But with this setup, an error will be thrown and no changes will be made. WHY?

function Content(){

  document.getElementById("content").textContent = "New Text";

  return(
    <p id="content">Replace This Text</p>
  )
}
Enter fullscreen mode Exit fullscreen mode

I’ll tell you why, components in React are still JavaScript objects until they have been mounted/rendered. In this example the statement to change the textContent of the paragraph is actually executed BEFORE the React Component has been returned, much less mounted.

So what can you do to access the component AFTER it’s been rendered to the DOM as HTML?

Now we come to it, the how. If we update our example to use the useEffect hook to get the job done, then it does what it sounds like, it gets the job done!

Now keep in mind that you DO NOT put your code directly into the useEffect function’s arguments. useEffect takes a function which is executed upon triggering. The simplest way for me is to pass an anonymous function containing all my JavaScript to the hook like in the example below. But you could include a named function instead.

function Content(){

  React.useEffect(()=>{
    document.getElementById("content").textContent = "New Text";
  }); 

  return(
    <p id="content">Replace This Text</p>
  )
}
Enter fullscreen mode Exit fullscreen mode

Now Guess What? The code works and will now change the textContent of our paragraph to “New Text”.

What about async data????

If we have a value that we are fetching from an API. We kind of need to update the part of our application that relies on that data once we receive it right? RIGHT.

In the below example, I’ve altered our Content component to rely on some data from an API. Using useEffect, we define and use an async function to fetch our data and set the state of our text to the response text.

function Content(){

const [contentText, setContentText] =  React.useState("Replace This Text");

React.useEffect(()=>{
  getData();
  async function getData(){
    const fetchData = await fetch("https://urltoapi.com");
    const textData = await fetchData.text();
    setContentText(textData);
  }
}); 

  return(
    <p id="content">{contentText}</p>
  )
}
Enter fullscreen mode Exit fullscreen mode

React does not allow for the function passed to useEffect to be asyncronous. So you have to define it INSIDE of the passed function and execute it there.

But William, my code runs every single time the component is updated and it’s breaking my code!

Yes, yes it does. This is by design. By default, useEffect is run every time the component needs to update. For those of us that need to limit this like when you’re developing a SPA (single page application) and you need to manually trigger useEffect at a certain time, luckily, again we are saved by the React creators.

useEffect takes a second optional argument. An array of dependency states/values. Whenever one of these state’s values changes is when the useEffect hook is re-triggered. Whether this be on the component updating or any time in between.

Below I’ve altered our Content component to trigger useEffect every time the “Trigger useEffect” button is clicked, which changes the triggerCount state, and that state is a value in the useEffect’s dependency array.

function Content(){

  const [triggerCount, setTriggerCount] = React.useState(1);

  function handleClick(){
    setTriggerCount((oldval) => ++oldval);
  }

  React.useEffect(()=>{
    console.log(`useEffect has been triggered ${triggerCount} time(s)!`);
  }, [triggerCount]);

  return(
    <button onClick={handleClick}>Trigger useEffect</button>
  )
}
Enter fullscreen mode Exit fullscreen mode

But I only want it to trigger ONCEEE!!!

Well, if you give useEffect an empty array as the dependency array, the hook will only trigger once, and this is when the component has been mounted/rendered. This is because when you provide a dependency array, useEffect waits for one of those values to change before it re-triggers. But if you don’t provide any values, they cannot change, so it will not re-trigger.

So for those who learn visually, here’s an alteration of the first example where useEffect only triggers once.

function Content(){

  React.useEffect(()=>{
    document.getElementById("content").textContent = "New Text";
  }, []); 

  return(
    <p id="content">Replace This Text</p>
  )
}
Enter fullscreen mode Exit fullscreen mode

So that’s it for this article. I really hope you learned something new and enjoyed it in the process. I did spend a bit of time writing this so if you did enjoy reading it, let me know :D.

For more tips, tricks and tutorials follow me on YouTube or Instagram.

Top comments (2)

Collapse
 
brense profile image
Rense Bakker • Edited

You can make it even better by using a ref!

function Content(){
  const contentRef = React.useRef();

  React.useEffect(()=>{
    contentRef.current.textContent = "New Text";
  }, []); 

  return (
    <p ref={contentRef}>Replace This Text</p>
  );
}
Enter fullscreen mode Exit fullscreen mode
Collapse
 
wbojczuk profile image
William Bojczuk

Great point!