-
-
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; | |
| }); | |
| } |
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.
Seems good.