React Hooks review #2: useEffect

This is the second chapter of the React Hooks series. The goal is simple: summarize all the eleven official React hooks:

useEffect

useEffect is an async encapsulated area to affect or connect to elements outside the component.

Syntax:

useEffect(effect [, dependencies]);
  • Effect: The function that performs the effect.
  • Dependencies: An array of variables that triggers the effect.

Since the main component is stateless and is need rerendered, executing async or external code will have inconsistent effects. useEffect is built to run only when you need to execute something outside the component.

useEffect gets executed only after the browser has painted the component.

Dependency list

The second value in the useEffect is the dependency list. The useEffect listens to the values in the list, and on any value change, it re-execute the Hook.

Let’s say we have a component with two simple counters:

const Counter: React.FC<{}> = ()=>{
    const [counter,setCounter] = React.useState(0);
    const [other,setOther] = React.useState(0);

    return (
        <div>
            <p onClick={()=>{setCounter(counter + 1)}}>Add number</p>
            <p onClick={()=>{setOther(other + 1)}}>Add other value</p>
            <Number number={counter} otherValue={other} />
        </div>
    )
}

No dependency list – execute on each re-render

To see how it triggers on each re-rendering, we can add it to the useState counter and see the log on each counter increment:


const Number: React.FC<NumberProps> = ({number}) => {

    React.useEffect(()=>{
        console.log('Will be seen only once');
    });

    return <div>{number}</div>
}

Empty dependency list – execute only after the first render

In this snippet, since the list is empty, the effect will run once on the first component mounting.

const Number: React.FC<NumberProps> = ({number}) => {

    React.useEffect(()=>{
        console.log('Will be seen only once');
    },[]);

    return <div>{number}</div>
}

Adding dependency items

If we want the effect to be triggered only on a specific mutation, you can control when to invoke the useEffect by listing the dependencies in the dependencies list:

const Number: React.FC<NumberProps> = ({number, otherValue}) => {
    React.useEffect(()=>{
        console.log('effect');
    },[number])

    return <div>Number: {number}, Other: {otherValue}</div>
}

In this snippet, you can see that clicking the “add number”, will execute the useEffect since it changes the value inside the track dependencies list. But clicking the “Add other value”, though it will rerender the component, will not trigger the effect.

Note that mutating values inside the dependency list will create an endless rendering loop:

const EndlessLoop: React.FC = ()=>{
    const [counter,setCounter] = React.useState(0);
    React.useEffect(()=>{
        setCounter(counter + 1);
    },[counter]);

    return <p>Ops {counter}</p>
}

Different types of side effects

As mentioned, the useEffect is not built for props or inner component actions, but for side effects as fetch requests, DOM manipulating, timer functions, and more.

useEffect(()=>{
    document.title = 'My doc';
,[])

Fetch values each time a prop had changed:

const Page: React.FC<PageProps> = ({pageId}) => {
    const [page,setPage] = React.useState(null);

    React.useEffect(()=>{  
        const data = await fetch("https://www.get.some/"+pageId
  );
        setPage(data);
    },[pageId])

    return !page?<div>Loading...</div>:<div>{page.title}</div>
}

Time intervals:

    React.useEffect(()=>{  
        setTimeout(() => {
            setMessage("I'm waiting");
        }, 1000);
    },[])

Cookie or browser storage:

    React.useEffect(()=>{  
        const userName = window.localStorage.getItem("name");
    },[])

Subscribing to some listener

    React.useEffect(()=>{  
        someService.subscribe(userId);
    },[userId])

Or adding event to the DOM

    React.useEffect(()=>{  
        window.addEventListener('resize', handleResize);
    },[])

Clean up

useEffect has a callback function to clean up when the component is unmounted or cleanup before re-executing the component and its effect.

const Number: React.FC<{}> = () => {
    React.useEffect(()=>{
        someService.subscribe();
        return () => {
            someService.unSubscribe();
        }
    },[])

    return <div>...</div>
}

Remove time intervals:

const Interval: React.FC = () => {
    React.useEffect(() => {
      const intervalId = setInterval(() => {
        console.log('Interval Plooped');
      }, 500);
      return () => { 
          clearInterval(intervalId);
          console.log('The end of the Interval plooping')
      }
    }, []);
  
    return <div>Interval</div>;
}

const ChangeComponent: React.FC = () => {
    const [component,setComponent] = React.useState(false);
    React.useEffect(()=>{
        setTimeout(()=>{
            setComponent(true)
        },3000)
    },[]);

    return component? <></>: <Interval />
}

ReactDom.render(
    <ChangeComponent />,
    document.querySelector('#app')
);

Remove DOM event listeners

    React.useEffect(()=>{  
        window.addEventListener('resize', handleResize);
        return ()=> {
            window.removeEventListener('resize', handleResize); 
        }
    },[])

Not: the cleanup functions gets invoked every time before the execution of the next scheduled effect. not only before destroying the React component.

Async functions inside useEffect

Since the useEffect returns the cleanup method, it is not possible to use the hook as an async function:

useEffect(async () => {
    const data = awite fetch('https://someUrl.con');
    doSomeStuff(data);
},[])

The two options are to use the JS Promises chaining:

useEffect(()=>{
    fetch('https://someUrl.con')
        .then((data)=>{
            doSomeStuff(data);
        })
})

Or to execute an external async function:

async function fetchAndDo(){
    const data = awite fetch('https://someUrl.con');
    doSomeStuff(data);
}

useEffect(() => {
    fetchAndDo();
},[])

Multiplue useEffects

You can have and sould have as meny useEffects as you want in the same component. So you sould split the effect to diffeten methods if:

  • Each dependency value had a different code to execute.
  • The effect block had more than one “effect” in the same method.