Side Effects and Data Fetching

✅ Objectives

  • Explain what a side effect is
  • Observe how React manages side effects with the useEffect hook
  • Observe how to use the useEffect hook to fetch data on page load
  • Observe how to send a POST request via form
  • Review changing parent state

Deliverables

    1. Persist the new project upon the ProjectForm submission
    1. Implement useEffect in App component to load projects
    1. Demonstrate the order of operations between rendering a component and running the side effect
    1. Demonstrate the unmounting and cleanup phase of a component through useEffect

What to know before discussing side effects?

  • React components are pure function. This means that given an input, such as props, the function’s return is 100% predictable. It does not rely on any external state or data, like side effects.
const Greeting = ( { name }) => {

  return <h1>{name}</h1>

}
  • When the Greeting component gets invoked, we can predict the result, regardless of the prop value.

What is a side effect?

  • “We perform a side effect when we need to reach outside of our React components to do something. Performing a side effect, however, will not give us a predictable result.”

  • useEffect blog

  • Side effects are necessary for many real-world applications, such as handling user input, fetching data from APIs, or updating user interfaces.

Examples of React component side effects

  • Fetching data from a server
  • Interacting with a browser API like the document or window
  • Utilizing interval timers such as setInterval or setTimeout

useEffect() hook

  • useEffect() runs both upon the first render and then with every re-render after but only based on the rules set inside the dependency array.

  • useEffect()’s purpose is to inform React that component needs to take a new action, AFTER the component’s initial render.

Anatomy of useEffect()

  useEffect(() => { //callback function
                    fetch('API_URL')
                      .then((response) => response.json())
                      .then((data) => setData(data))
                  }, []) //dependency array

useEffect() takes in two arguments:

  • A callback function defining the logic to be executed as a side effect( the effect)
  • A dependency array
    • case 1️⃣: No dependency array
    • case 2️⃣: [] An empty array
    • case 3️⃣: [data] value in the dependency array

1️⃣ useEffect() hook without a dependency array

useEffect(() => {
  //callback function logic
}) // no dependency array
  • In this example, only 1 argument is passed to useEffect.
  • The dependency array which is the optional second argument is left out.
  • That means that upon initial render(mounting) and re-render(updating), which might lead to infinite loops or unnecessary re-execution of side effects.

2️⃣ useEffect() hook with an empty dependency array

useEffect(() => {
  //some effect to occur
}, []) // an empty array as the dependency array
  • The dependency array is passed as the second argument.
  • The dependency array is empty with no provided value.
  • This set up ensures that the side effect will only run one time, upon the initial render of the component and no more after that. Even if the component is re-rendered.

3️⃣ useEffect() hook with a value provided to the dependency array

useEffect(() => {
  //some effect to occur
}, [data]) //  the dependency array contains a single is piece of data
  • In this example, the dependency array contains a single piece of data.
  • This data can be prop or a state variable.
  • The side effect will run once upon the initial render, will re-render when the data in the dependency array gets updated with a new value.

Cleaning up

  • There will be some code that is necessary to clean up after the component is no longer being mounted on the DOM. AKA turning off our side effects.

  • Why? To avoid ‘memory leaks’, which means using memory for data that is no longer necessary.

  • examples: timeouts, subscriptions, event listeners, websocket

  • Happens on a couple of occasions:

    • when the component initially mounts, it will run the cleanup function before the effect function inside useEffect()
    • after a re-render but before the effect function runs again: if there is any cleanup defined, it will run this first.

Cleanup example


const Timer = () => {

  const [ count, setCount ] = useState(0)

  useEffect(() => {
    // Effect logic: set up a timer that increments the count every second
    const timer = setTimeout(() => {
      setCount((prevCount) => prevCount + 1);
    }, 1000); //second

    // Cleanup function: clear the timer when the component is unmounted
    return () => {
      clearTimeout(timer);
    };
  }, []);

  return <h1> {count} times! </h1>
}
  • When the component unmounts from the DOM or mounts for the first time, it is ensured that the timeout has been cleared.

💡💡 Conclusion 💡💡

  • Side effects are going to happen after the component has initially rendered to the DOM.

  • We can utilize the built-in React hook useEffect().

  • The first argument is a callback function.

  • The second argument is the dependency array.

  • We use side effect for fetching data, interacting with user input, setInterval or/and setTimeout.

Legacy code

  • React’s Lifecycle Methods: Prior to React Hooks, class components used lifecycle methods like componentDidMount and componentDidUpdate to handle side effects.

  • With functional components and Hooks, we can use the useEffect hook to perform side effects after the component has rendered.

  • When we see lifecycle methods, we know that now we use useEffect hook.