Reputation: 57
Do all variables used inside a useEffect always, absolutely, without exception need to be specified as dependencies?
My use case (simplified for demonstration purposes) involves different functions being executed depending on the width of the browser window, but not run when browser window changes:
const scrollToTop = () => window.scrollTo(0, 0)
const scrollToTopOfArticle = () => window.scrollTo(0, 200)
function App({
isDesktop,
selectedArticle
}) {
useEffect(() => {
isDesktop ? scrollToTop() : scrollToTopOfArticle()
}, [selectedArticle])
return (
<div className="App">
<h1>{selectedArticle.title}</h1>
<p>{selectedArticle.body}</p>
</div>
);
}
If I add isDesktop to the dependencies then the effect runs whenever the user resizes the window between mobile and desktop, which is not desired, but I'm also aware of the dogma that everything used inside the effect must be listed as a dependency.
Any suggestions on how to reconcile these two requirements?
Upvotes: 4
Views: 1917
Reputation: 51766
If you want to make a useEffect()
only responsive to a change in selectedArticle
, use isDesktop
and selectedArticle
to initialize component states. Whenever selectedArticle
changes, the first useEffect()
will update both states with the passed-in props, and trigger the second useEffect()
to re-run on the next render.
const scrollToTop = () => window.scrollTo(0, 0)
const scrollToTopOfArticle = () => window.scrollTo(0, 200)
function App({
isDesktop,
selectedArticle
}) {
const [desktop, setDesktop] = useState(isDesktop)
const [article, setArticle] = useState(selectedArticle)
useEffect(() => {
if (article !== selectedArticle) {
setDesktop(isDesktop)
setArticle(selectedArticle)
}
}, [isDesktop, selectedArticle, article])
useEffect(() => {
if (desktop) scrollToTop()
else scrollToTopOfArticle()
}, [desktop, article])
return (
<div className="App">
<h1>{selectedArticle.title}</h1>
<p>{selectedArticle.body}</p>
</div>
)
}
Alternatively, you can abstract this latching behavior to another hook so that isDesktop
only updates to its live value when selectedArticle
changes. Note that selectedArticle
still needs to be a dependency of the scroll action effect, so that the useEffect()
will trigger the scroll action on every change to selectedArticle
even if isDesktop
has not changed values since the last trigger.
const useLatch = (value, deps) => {
const [state, setState] = useState(value)
const effect = useCallback(() => { setState(value) }, [value])
useEffect(effect, deps)
return state
}
const scrollToTop = () => window.scrollTo(0, 0)
const scrollToTopOfArticle = () => window.scrollTo(0, 200)
function App({
isDesktop,
selectedArticle
}) {
const latchedIsDesktop = useLatch(isDesktop, [selectedArticle])
useEffect(() => {
if (latchedIsDesktop) scrollToTop()
else scrollToTopOfArticle()
}, [latchedIsDesktop, selectedArticle])
return (
<div className="App">
<h1>{selectedArticle.title}</h1>
<p>{selectedArticle.body}</p>
</div>
)
}
Upvotes: 4
Reputation: 12174
Are all variables inside a useEffect absolutely required to be listed as dependencies?
Yes or else it will generate a warning.
That's why it's best to have clear idea what each effect should do (i.e separation of concern).
Thus, you can have two separate effects with different dependencies.
Something like:
useEffect(
() => {
scrollToTopOfArticle();
}, [selectedArticle]
);
useEffect(
() => {
if (selectedArticle && isDesktop) {
scrollToTop();
}
}, [isDesktop, selectedArticle]
)
Upvotes: 1