I have a simple dialog component that takes an open
prop to control whether the dialog is shown or not, and an onOpen
prop which is a callback to be run every time the dialog opens.
I can achieve this very simply like this:
const MyDialog = ({ open, onOpen, children }) => {
React.useEffect(() => {
if (open) {
onOpen()
}
}, [open])
// The dialog that gets rendered uses a UI library’s dialog component.
return (
<UnderlyingDialogComponent open={open}>
{children}
</UnderlyingDialogComponent>
)
}
This works perfectly fine, except for the fact that the react-hooks/exhaustive-deps
linting rule (from eslint-plugin-react-hooks) tells me that I need to include onOpen
in the dependency array for the useEffect
.
If I do that however, then the onOpen
callback also gets called whenever the onOpen
callback changes, which is not the behaviour that I want. I want it only to be called when the open
prop changes from false
to true
.
Is there a simple way to do this without just disabling the linting rule? Or is this a situation where I can ignore the “rule” that all dependencies must be included in the dependency array?
Edit: I am aware that if the onOpen
handler is made using useCallback
then it shouldn’t change, but I can’t guarantee that any user of this component will do that. I would like my component to function correctly regardless of whether the user has used useCallback
for their onOpen
handler.
3
Answers
You can consider a custom hook,
useEffectEvent
to help you handle this. React is currently experimenting with a feature like this, so you can also import it with:or, you can create something similar which you maintain yourself by using a polyfill such as this one.
This hook will return a stable function reference across all re-renders, so it doesn’t need to be included in your dependency array. It also has the added advatnage of always calling the "latest"
onOpen
function, avoiding potential stale closure issues within that callback. You can use it like so in theMyDialog
component:Keep an eye out for the current limitations with the callback you get back from this hook. Also, it’s worth considering if you need an effect here in the first place. For example, if your inner dialog has an onOpen event you can tap into, as using that would be better to call your
onOpen
callback.While adding the dependency is one of the right ways to do it, if you do not want it to only be triggered when
open
changes to true, you can do track the previous value to make the correct updates:Keep a ref to
Here
ref
is a ref that holds the value ofopen
from previous render and in theuseEffect
you can check whether that value was falsy earlier and true right now.This functionality can also be moved to a hook
You can achieve this by ensuring the callback (onOpen) is triggered every time the open prop becomes true, and making sure that it doesn’t trigger unnecessarily when the open prop is already true.