Global Scoped Variables in useEffect Dependencies? A big NO!

Ajay n Jain
4 min readFeb 5, 2023

--

Photo by Pavel Neznanov on Unsplash

Outer scope values like ‘window.location.pathname’ aren’t valid dependencies because mutating them doesn’t re-render the component. (react-hooks/exhaustive-deps)

The use case

Let’s say we have to perform an operation on each page and we have to do this whenever the route changes.

As we have to perform the operation on each page component, let’s abstract the logic out into a hook.

Files

index.js files contain Router config with two pages -> Home Page and Other Page.

Home Page and Other Page renders just a text and calls the common useLocation hook.

In useLocation hook, we will perform some operations based on window.location.pathname which is a global scoped variable.

// FILENAME -> index.js

import React from "react";
import { render } from "react-dom";
import { BrowserRouter as Router, Route, Link } from "react-router-dom";

import Other from "./components/Other";
import Home from "./components/Home";

// Basic Router to render Home Page and Other Page
const BasicExample = () => (
<Router>
<div>
<ul>
<li>
<Link to="/">Home</Link>
</li>
<li>
<Link to="/other">Other</Link>
</li>
</ul>

<hr />

<Route exact path="/" component={Home} />
<Route path="/other" component={Other} />
</div>
</Router>
);

render(<BasicExample />, document.getElementById("root"));
// FILENAME -> useLocation.js
// This is the hook that we will be running on each page

import { useEffect } from "react";

const useLocation = (pathname) => {
useEffect(() => {
/*
* If the pathname is same as current page path, perform some operation
* For this example, the operation is to alert the user
* only if pathname passed and window.location.pathname are equal
*/
if (pathname === window.location.pathname) {
alert(
`On Page -> , pathname -> ${pathname},
window.location.pathname -> ${window.location.pathname}`
);
}
}, [pathname, window.location.pathname]);
};

export default useLocation;
// Home Page
import React from "react";
import useLocation from "../useLocation";

const Home = () => {
useLocation("/");

return <h2>Home</h2>;
};

export default Home;

// ----------------------------------------------------------------------

// Other Page
import React from "react";
import useLocation from "../useLocation";

const Other = () => {
useLocation("/other");

return <h2>Other</h2>;
};

export default Other;

Interaction

Gif of interaction of navigating across Home Page and Other Page

In the interaction, we can see that, once we land on Home Page, we see an alert of path -> ‘/’ and navigating to Other Page, we see an alert of path -> ‘/other’.

Everything seems to be working fine, so what’s the problem?

The Problem

Let’s modify the code to perform cleanup operation once the effect has finished running.

In the modified code, we will be alerting as part of the cleanup operation.

// FILENAME -> useLocation.js
// This is the hook that we will be running on each page

import { useEffect } from "react";

const useLocation = (pathname) => {
useEffect(() => {
/*
* If the pathname is same as current page path, perform some operation
* For this example, the operation is to alert the user
* only if pathname passed and window.location.pathname are equal
*/
if (pathname === window.location.pathname) {
// MODIFIED CODE HERE -> removed alert from this condition
// doSomeOperation()
}

// MODIFIED CODE HERE -> CLEANUP OPERATION
// Added the same alert here
return () => {
alert(
`On Page -> , pathname -> ${pathname},
window.location.pathname -> ${window.location.pathname}`
);
}
}, [pathname, window.location.pathname]);
};

export default useLocation;

Let’s see the interaction now

Gif of interaction of navigating across Home Page and Other Page showcasing the Problem

When we navigate to Other Page, the Home Page-effect has completed running and runs the cleanup operation.

While we expect both path and pathname to be same, in the alert we have pathname -> ‘/’ and window.location.pathname -> ‘/other’.

This happens because window.location.pathname is mutated by the time cleanup operation has started.

The same thing happens when we navigate back to Home Page from Other Page.

The Solution

Let’s modify the code to not use window.location.pathname in the dependency and instead, use a local variable defined in the hook.

// FILENAME -> useLocation.js
// This is the hook that we will be running on each page

import { useEffect } from "react";

const useLocation = (pathname) => {
// MODIFIED CODE HERE -> Assigned path = window.location.pathname
// Use this variable across the hook
const path = window.location.pathname;

useEffect(() => {
/*
* If the pathname is same as current page path, perform some operation
* For this example, the operation is to alert the user
* only if pathname passed and path are equal
*/
if (pathname === path) {
// doSomeOperation()
}

return () => {
alert(
`On Page -> , pathname -> ${pathname},
window.location.pathname -> ${path}` // Use path instead of window.location.pathname
);
}
}, [pathname, path]);
};

export default useLocation;

Let’s see the interaction now

Gif of interaction of navigating across Home Page and Other Page after applying the Fix

After applying the fix, we can see that we get correct and consistent values.

This works because path is a local variable of each hook instance. While it is assigned to window.location.pathname at the init, the reassignment only happens when there is a rerender.

Have you ever faced issues like these! Do let me know in the comments!

Thanks

--

--

Ajay n Jain
Ajay n Jain

Written by Ajay n Jain

Frontend Engineer! I observe, I write, follow for my deductions. I hope to be a Sherlock in Engineering

No responses yet