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.
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:
import { experimental_useEffectEvent as useEffectEvent } from 'react';
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 the MyDialog
component:
const MyDialog = ({ open, onOpen, children }) => {
const onOpenHandler = useEffectEvent(onOpen);
React.useEffect(() => {
if (open) {
onOpenHandler();
}
}, [open]);
...
}
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.