Why Did You Render
why-did-you-render
monkey patches React
to notify you about avoidable re-renders. (Works with React Native
as well.)
For example, when you pass style={{width: '100%'}}
to a big pure component and make it always re-render:
It can also help you to simply track when and why a certain component re-renders.
Read More
- You can read more about the library >> HERE <<.
- Part 2 - Common fixing scenarios this library can help to eliminate can be found >> HERE <<.
- Part 3 - React Hooks - Understand and fix hooks issues >> HERE <<.
- Part 4 - React-Redux - Understand and fix react-redux issues >> HERE <<.
Sandbox
You can test the library in the official sandbox >> HERE <<.
Setup
The required React version for the library is 16.12 but it is expected to work with older versions as well.
For versions before 16.8 turn off hooks support by using trackHooks: false
in whyDidYouRender
's init options.*
npm install @welldone-software/why-did-you-render --save
or
yarn add @welldone-software/why-did-you-render
Installation
Execute whyDidYouRender
with React
as its first argument before any React
element is created.
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React);
}
Usage
Mark any component you want to be notified about their "redundant" re-renders with whyDidYouRender
like this:
class BigListPureComponent extends React.PureComponent {
static whyDidYouRender = true
render(){
return (
)
}
}
Or like this:
const BigListPureComponent = props => (
<div>
//some heavy component you want to ensure doesn't happen if its not neceserry
</div>
)
BigListPureComponent.whyDidYouRender = true
You can also pass an object to specify more advanced settings:
EnhancedMenu.whyDidYouRender = {
logOnDifferentValues: true,
customName: 'Menu'
}
-
logOnDifferentValues
:
Normally, only re-renders that are caused by equal values in props / state trigger notifications:
render(<Menu a={1}/>)
render(<Menu a={1}/>)
This option will trigger notifications even if they occurred because of different props / state (Thus, because of "legit" re-renders):
render(<Menu a={1}/>)
render(<Menu a={2}/>)
-
customName
:
Sometimes the name of the component can be very inconvenient. For example:
const EnhancedMenu = withPropsOnChange(withPropsOnChange(withStateHandlers(withPropsOnChange(withState(withPropsOnChange(lifecycle(withPropsOnChange(withPropsOnChange(onlyUpdateForKeys(LoadNamespace(Connect(withState(withState(withPropsOnChange(lifecycle(withPropsOnChange(withHandlers(withHandlers(withHandlers(withHandlers(Connect(lifecycle(Menu)))))))))))))))))))))))
will have the display name:
withPropsOnChange(withPropsOnChange(withStateHandlers(withPropsOnChange(withState(withPropsOnChange(lifecycle(withPropsOnChange(withPropsOnChange(onlyUpdateForKeys(LoadNamespace(Connect(withState(withState(withPropsOnChange(lifecycle(withPropsOnChange(withHandlers(withHandlers(withHandlers(withHandlers(Connect(lifecycle(Menu)))))))))))))))))))))))
To prevent polluting the console, and any other reason, you can change it using customName
.
Options
Optionally you can pass in options
as the second parameter. The following options are available:
include: [RegExp, ...]
(null
by default)exclude: [RegExp, ...]
(null
by default)trackAllPureComponents: false
trackHooks: true
trackExtraHooks: []
logOnDifferentValues: false
hotReloadBufferMs: 500
onlyLogs: false
collapseGroups: false
titleColor
diffNameColor
diffPathColor
notifier: ({Component, displayName, prevProps, prevState, nextProps, nextState, reason, options}) => void
include / exclude
You can include or exclude tracking for re-renders for components
by their displayName with the include
and exclude
options.
Notice: exclude takes priority over both include
and whyDidYouRender
statics on components.
For example, the following code is used to track all redundant re-renders that are caused by React-Redux:
whyDidYouRender(React, { include: [/^ConnectFunction/] });
trackAllPureComponents
You can track all pure components (both React.memo
and React.PureComponent
components)
Notice: You can exclude the tracking of any specific component with whyDidYouRender = false
.
trackHooks
You can turn off tracking of hooks changes.
Understand and fix hook issues >> HERE <<.
Adding extra hooks to track for "redundant" results:
whyDidYouRender(React, {trackExtraHooks: [
[Redux, 'useSelector']
]});
logOnDifferentValues
Normally, you only want notifications about component re-renders when their props and state
are the same, because it means these re-renders could have been avoided. But you can also track
all re-renders, even on different state/props.
render(<BigListPureComponent a={1}/>)
render(<BigListPureComponent a={2}/>)
hotReloadBufferMs
Time in milliseconds to ignore updates after a hot reload is detected.
We can't currently know exactly if a render was triggered by hot reload,
so instead, we ignore all updates for hotReloadBufferMs
(default: 500) after a hot reload.
onlyLogs
If you don't want to use console.group
to group logs by component, you can print them as simple logs.
collapseGroups
Grouped logs can start collapsed:
titleColor / diffNameColor / diffPathColor
Controls the colors used in the console notifications
notifier
You can create a custom notifier if the default one does not suite your needs.
Troubleshooting
Class constructors must be invoked with 'new'
.
If you are building for latest browsers (or using es6 classes without building) you don't transpile the "class" keyword.
This causes an error because the library uses transpiled classes, and transpiled classes currently can't extend native classes.
To fix this, use the "no-classes-transpile" dist:
import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render/dist/no-classes-transpile/umd/whyDidYouRender.min.js');
whyDidYouRender(React);
}
React-Redux connect
HOC is spamming the console
Since connect
hoists statics, if you add WDYR to the inner component, it is also added to the HOC component where complex hooks are running.
To fix this, add the whyDidYouRender = true
static to a component after the connect:
const SimpleComponent = ({a}) => <div data-testid="foo">{a.b}</div>)
const ConnectedSimpleComponent = connect(
state => ({a: state.a})
)(SimpleComponent)
SimpleComponent.whyDidYouRender = true
Credit
Inspired by the following previous work:
https://github.com/maicki/why-did-you-update which i had the chance to maintain for some time.
https://github.com/garbles/why-did-you-update where A deep dive into React perf debugging is credited for the idea.
License
This library is MIT licensed.