-
-
Save gragland/fe89992181663d5e46d024dec8a8e5e6 to your computer and use it in GitHub Desktop.
import { useState, useEffect, useRef } from 'react'; | |
// Let's pretend this <Counter> component is expensive to re-render so ... | |
// ... we wrap with React.memo, but we're still seeing performance issues :/ | |
// So we add useWhyDidYouUpdate and check our console to see what's going on. | |
const Counter = React.memo(props => { | |
useWhyDidYouUpdate('Counter', props); | |
return <div style={props.style}>{props.count}</div>; | |
}); | |
function App() { | |
const [count, setCount] = useState(0); | |
const [userId, setUserId] = useState(0); | |
// Our console output tells use that the style prop for <Counter> ... | |
// ... changes on every render, even when we only change userId state by ... | |
// ... clicking the "switch user" button. Oh of course! That's because the | |
// ... counterStyle object is being re-created on every render. | |
// Thanks to our hook we figured this out and realized we should probably ... | |
// ... move this object outside of the component body. | |
const counterStyle = { | |
fontSize: '3rem', | |
color: 'red' | |
}; | |
return ( | |
<div> | |
<div className="counter"> | |
<Counter count={count} style={counterStyle} /> | |
<button onClick={() => setCount(count + 1)}>Increment</button> | |
</div> | |
<div className="user"> | |
<img src={`http://i.pravatar.cc/80?img=${userId}`} /> | |
<button onClick={() => setUserId(userId + 1)}>Switch User</button> | |
</div> | |
</div> | |
); | |
} | |
// Hook | |
function useWhyDidYouUpdate(name, props) { | |
// Get a mutable ref object where we can store props ... | |
// ... for comparison next time this hook runs. | |
const previousProps = useRef(); | |
useEffect(() => { | |
if (previousProps.current) { | |
// Get all keys from previous and current props | |
const allKeys = Object.keys({ ...previousProps.current, ...props }); | |
// Use this object to keep track of changed props | |
const changesObj = {}; | |
// Iterate through keys | |
allKeys.forEach(key => { | |
// If previous is different from current | |
if (previousProps.current[key] !== props[key]) { | |
// Add to changesObj | |
changesObj[key] = { | |
from: previousProps.current[key], | |
to: props[key] | |
}; | |
} | |
}); | |
// If changesObj not empty then output to console | |
if (Object.keys(changesObj).length) { | |
console.log('[why-did-you-update]', name, changesObj); | |
} | |
} | |
// Finally update previousProps with current props for next hook call | |
previousProps.current = props; | |
}); | |
} |
@EmrysMyrddin That makes sense. Here's a quick example of what that might look like: https://gist.github.com/gragland/87a0a7cdc0f276e37e97290f00f57c41#file-use-why-did-you-update2-js-L70
What do you think? Will update the recipe unless anyone sees any problems with this.
Seems good.
useWhyDidYouUpdate with new isEqual and pretty log would be great.
https://twitter.com/fengshangwuqi/status/1100279450065727489
Hey.
I did this small change to check for nested objects at the props. If so, it will use lodash's isEquals to check for equality (I'm lazy haha) .
At the main example, this will avoid logging if you have only changed the avatar and caused a rerender.
I realize this is costy, but since is only for debugging, we don't have to worry since it will not be used in prod =)
Hope you like it!
Link here
@davidpn11 Maybe I'm missing something, but isn't the point that it does log every time there is a re-render? Why skip doing it when the avatar changes? Also, could you explain rationale behind isEqual
? Not seeing why a simple previousProps.current[key] !== props[key]
isn't enough, since we just want to compare references to see if they changed (and thus caused a re-render).
@gragland You are right. I was thinking more of a solution to log exactly which props have changed. So it shows you more clearly how your app is behaving. I was not thinking about reference, but more of the data itself.
In conclusion, I was thinking about a different hook. But is nice to see it can be easily derive from your example.
Great work with useHooks by the way! =)
@davidpn11: Gotcha, makes sense and seems like a good way to extend this hook. Thanks for the kind words!
I'm happy if someone could turn the snippet into typescipt.
@mscolnick @brunolemos Yes, good call. Will remove the second arg as no harm in the effect being called every time.