At some point in your React career you might need to migrate a class based component to a function component. The old component may have lifecycle methods performing operations for the component, and you might think how on earth can all that go into this function component??
Let’s look at some examples, in your old component you might see something like:
|
1 2 3 |
componentWillUnmount() { document.removeEventListener('click', someFuncRef) } |
So it is clear that in our new component we need to run this when the component unmounts, and this could be considered cleanup. Let’s take a look at the definition of useEffect. We can see that the useEffect hook does indeed have the option to invoke a cleanup operation if provided. How do we provide it, let’s have go:
|
1 2 3 4 |
useEffect(() => { // some things happening on every render return () => document.removeEventListener('click', someFuncRef) }); |
We always provide a callback to useEffect, and when we want to invoke a cleanup, we return an additional callback from the one provided as a param, and when the component unmounts, it will be invoked.
Let’s take a look at another example, in your old component you might see this:
|
1 2 3 4 5 |
componentDidUpdate(prevProps) { if (this.props.thing !== prevProps.thing) { // do something } } |
In this scenario we are evaluating something everytime the component changes, or to put it another way, when props change. In this scenario we need to know what previous props are in order to evaluate. There is nothing built in to useEffect that will record previous props for us, but we can still use it with a little help for our friend – useRef. Let’s have go.
|
1 2 3 4 5 6 7 8 9 10 11 |
const prevProps = useRef<Props>(props) const { thing } = props const { previousThing } = prevProps.current useEffect(() => { if (thing !== previousThing) { // do something } prevProps.current = props }, [thing]); |
So how does this work? Well when the component first mounts, we instantiate the prevProps ref with the first ever version of props. After that, when the thing prop is updated, this will trigger a rerender of the component, and the useEffect will be invoked as a side effect of that render, at this time we can run our required evaluation, call any logic we need based on outcome, and finally update the ref to contain the new form of prevProps for the next time, and because mutating refs do not trigger a rerender, we won’t end up stuck in a loop.
If you find you are using this pattern a lot, why not consider turning it into a hook? See a great example here.
Let’s look at one more example, in our old component we see this code:
|
1 2 3 4 5 |
componentDidMount() { if (this.props.thing) { // do something } } |
This one is probably the most basic one to solve. We can see based on the name of the method that we want to do something when the component mounts. This can be easily achieved with useEffect, because useEffect always runs as a side effect of a render, including the very first render. Now this isn’t exactly the same as running on mount, but for now it will suffice…more on that later.
|
1 2 3 4 5 6 |
const { thing } = props; useEffect() { if (thing) { // do something } }, []) |
There is something important to note here, specifically the second parameter we passed to useEffect here – the empty array. This is important because, this holds the dependancies, and if we did not pass anything here this useEffect would run on every single render, and there is no need for that, because we only cared about the first one. You could also track first render with a ref, but that seems overkill for this scenario.
Let’s now consider the same code, but imagine that our // do something operation is one that changes the layout of the page. The problem with using useEffect in the place of componentDidMount is that we might now see a flicker. This happens because useEffect actually runs a little bit later than componentDidMount does. There are some hidden smarts in componentDidMount that pool together the first render and any invoked by itself, this results in a smooth transition. In many scenarios a simple useEffect will suffice, but if you are updating visuals and end up with a flicker you can use useLayoutEffect instead.