loading...

componentDidUpdate Hooks How to mimic componentDidUpdate() with React Hooks

savagepixie profile image SavagePixie ・2 min read

Notes on React (2 Part Series)

1) Fetching data with React Hooks 2) How to mimic componentDidUpdate() with React Hooks

useEffect is a very useful hook. It receives a callback function that executes when the component has mounted and every time it updates. So it works similarly to the old componentDidMount() and componentDidUpdate() methods for class components.

There is a tiny problem, though. Sometimes, we might not want it to work like both componentDidMount() and componentDidUpdate(). Sometimes we want to execute the hook only when the component mounts or only when it updates.

How to keep useEffect from executing on mount

React doesn't really offer a clean way to do it. In future versions, it may be handled by an argument. But right now, the best way I've found is a reference hook.

What is a reference hook?

It's a hook initialised with useRef. It returns a mutable object whose current property gets initialised to the argument we pass when we declare it. It looks something like this:

const refHook = useRef(initValue)

How does it help us?

We can use the reference to check whether the component has just mounted or updated. We initialise it as false and change the state to true on the first render. Then, we use this information to decide whether our useEffect should do something or not.

const App = props => {
  const didMountRef = useRef(false)
  useEffect(() => {
    if (didMountRef.current) {
      doStuff()
    } else didMountRef.current = true
  }
}

This hook will check if didMountRef.current is true. If it is, it means that the component has just updated and therefore the hook needs to be executed. If it's false, then it means that the component has just mounted, so it will skip whatever action it is supposed to perform and set the value of didMountRef.current to true so as to know that future re-renderings are triggered by updates.

Note! it is important to remember that what gets initialised when we define the reference hook is the refHook.current property, not the refHook itself.

Why don't we use a good ol' useState?

I suppose that it would be possible to do something like this:

const App = props => {
  const [ didMount, setDidMount ] = useState(false)
  useEffect(() => {
    if (didMount) {
      doStuff()
    } else setDidMount(true)
  }
}

That, however, comes with a problem. Unless we add some controllers, the change in state will trigger an update of the component immediately after it mounts, as its state will have changed. This, in turn, will trigger the effect hook. So the reference hook helps us avoid all the trouble associated to controlling it through state.

Notes on React (2 Part Series)

1) Fetching data with React Hooks 2) How to mimic componentDidUpdate() with React Hooks

Posted on by:

savagepixie profile

SavagePixie

@savagepixie

Always learning new things. I love web development and coding in general.

Discussion

markdown guide
 

How about converting this to a custom hook?

import React, { useRef, useEffect, useState } from 'react'

// I also made it to support running when specific values update in deps
// The default value for deps will be undefined if you did not pass it
// and will have the same effect as not passing the parameter to useEffect
// so it watch for general updates by default
function useDidUpdate (callback, deps) {
  const hasMount = useRef(false)

  useEffect(() => {
    if (hasMount.current) {
      callback()
    } else {
      hasMount.current = true
    }
  }, deps)
}

// Usage:

function App () {
  useDidUpdate(() => {
    doStuff()
  })
}

// Or:

function AppTwo () {
  const [count, setCount] = useState(0)

  useDidUpdate(() => {
    doStuffIfCountUpdates()
  }, [count])
}

✌️✌️✌️

 

Wow guys that really solved a problem i had.. useeffect doesn't imitate componentDidUpdate even after using prop as the last parameter , i used Hammeds solution with useDidUpdate and i just added a prevprops var . seems to be working fine so far.

 

No, no, no folks don't do this ! It isn't the right way to use useEffect !

To mimic didMount juste pass [] as the second parameter of useEffect. To mimic didUpdate, pass all needed dependencies in the array.

But the best way to use useEffect is to avoid mimicking the old fashion lifecycle methods and understand the best coding practices hooks enables.

 

So, would you care to provide a quick example of how to make useEffect not execute after the first render but execute on subsequent renders?

Or are you suggesting that one should (and indeed can) always find an alternative way that doesn't require to skip the useEffect on the first render?

 

I agree your way doing this works perfectly and you can even wrap it in a custom hook to reuse it.

Having said that (admitting there are cases where you don't want to run a stuff on mount but still on all re-renders !), is this hack improving our code ?

According to React guys, hooks allows us to separate concerns and avoid us to write stuff like :

componentDidMount() {
   // concern 1
   // concern 2
   // concern 3
}

componentDidUpdate(prevProps) {
   if (prevProps.xxx !== this.props.xxx) {
      // concern 2
   }
   if (prevProps.yyy !== this.props.yyy) {
      // concern 3
   }
   // concern 4 (on each re-render because we can :) )
}

which is imperative, redundant and difficult to simplify. Effects are made to be used atomically, to maintain good readability :

useEffect(concern1, [...]);
useEffect(concern2, [...]);
...

If the effect concernX have to be trigged only on updates right after any user action or async stuff, IMO you better should rely on your component scope (props, states, sometimes ref or context, ...). Ex :

const [selectedColor, setSelectedColor] = useState('blank'); // or null or whatever
useEffect(() => {
   if (selectedColor !== 'blank') {
      // selectedCorolized unicorns popping
   }
}, [...]);

For readability purposes you can even put this in a custom hook and reuse the concern anywhere you want.

If the effect concernX have to be trigged only on updates right after any user action or async stuff, IMO you better should rely on your component scope (props, states, sometimes ref or context, ...). Ex:

So, if I'm understanding right what you're saying and especially this example, what you propose is to use something that already exists and that we're already monitoring to decide whether an effect hook should execute or not (and play with how that something has been initialised), rather than creating a specific reference to know whether the component has been mounted or not. Is that so?

Exactly, rely on data values ans data changes instead of component lifecycle. It seems to be the way hooks are designed.

Actually all your props should be observed and changes propagated in your component render flow, it's a really different approach than class components. Dan Abramov made a really detailed blog post about that here : overreacted.io/a-complete-guide-to...

Just an apetizer :

It’s only after I stopped looking at the useEffect Hook through the prism of the familiar class lifecycle methods that everything came together for me.

“Unlearn what you have learned.” — Yoda

 

Thanks Rudy, I believe you may have got me out of a jam with your suggestion of putting the if statement in useEffect. In my use case i need to watch for a change in the url as my app gets a redirect back from an api with a token.

( when path !== '/' means I have received a token from the api)

So now I can check it but only do something if I have the token, before I was getting a bit confused about needing useMemo or useRef.

Surprised I didn't think of your solution, perhaps I had it in my head that you shouldn't use conditionals inside useEffect.

So thanks again, and thanks SavagePixie for your post.

 

This was a good way of explaining it. Thanks for sharing your notes 🙂