Taming the Globals: Best Practices for Component-Based Navigation in React Router
- In JavaScript, certain built-in objects like
window
,document
, and browser APIs (likealert
orprompt
) are considered "globals" because they're accessible from anywhere in your code. - However, using these globals directly can make code less maintainable, testable, and prone to errors due to unintended side effects or name conflicts.
"No Restricted Globals" Rule
- This is a linting rule (often enforced by tools like ESLint) that helps prevent developers from directly accessing these globals.
- The goal is to encourage writing cleaner, more modular, and testable code.
Alternatives in React and React Router
- React provides component-specific hooks and props to manage state and interactions within your application. For instance, use
useState
to manage internal component state, and pass data as props from parent to child components. - React Router offers hooks like
useHistory
(in older versions) oruseNavigate
(in newer versions) for programmatic navigation within your app, instead of relying on the globalwindow.history
object. By using these hooks, React Router manages the navigation state and URL updates.
Benefits of Avoiding Globals
- Improved Maintainability: Code is easier to understand and modify without worrying about hidden dependencies on globals.
- Enhanced Testability: Components can be isolated and tested independently without relying on external state or side effects introduced by globals.
- Reduced Risk of Errors: Accidental modifications to globals can be prevented, leading to more stable and reliable code.
Example (React Router Navigation with useNavigate
):
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/new-page'); // Programmatic navigation using useNavigate
};
return (
<button onClick={handleClick}>Go to New Page</button>
);
}
Without no-restricted-globals
:
// This might trigger the "no-restricted-globals" rule
function MyComponent() {
const currentPath = window.location.pathname; // Using global `window.location`
// ...
}
With useLocation
:
import { useLocation } from 'react-router-dom';
function MyComponent() {
const location = useLocation();
const currentPath = location.pathname; // Using `useLocation` hook
// ...
}
Programmatic Navigation (Using useNavigate):
// This might trigger the "no-restricted-globals" rule
function MyComponent() {
const handleClick = () => {
window.location.href = '/new-page'; // Using global `window.location`
};
// ...
}
With useNavigate
:
import { useNavigate } from 'react-router-dom';
function MyComponent() {
const navigate = useNavigate();
const handleClick = () => {
navigate('/new-page'); // Using `useNavigate` hook
};
// ...
}
Accessing DOM Elements (Avoiding document):
// This might trigger the "no-restricted-globals" rule
function MyComponent() {
const titleElement = document.getElementById('my-title');
// ...
}
In this specific case, directly accessing the DOM might not be the best approach for React components. However, if necessary, alternative solutions exist:
- Refs: Create a React ref using
useRef
hook and attach it to the desired element. Access it within the component using the ref object. - Third-party Libraries: Consider libraries like
react-dom
orstyled-components
for more controlled DOM manipulation within React.
- Context API: If location information needs to be shared across multiple components, consider using React's Context API to provide a centralized location object accessible throughout your application.
Programmatic Navigation:
- React Router v5
useHistory
(For Older Versions): WhileuseNavigate
is preferred in newer versions, React Router v5 offered theuseHistory
hook for programmatic navigation:
import { useHistory } from 'react-router-dom';
function MyComponent() {
const history = useHistory();
const handleClick = () => {
history.push('/new-page'); // Using useHistory hook
};
// ...
}
Accessing DOM Elements:
- Controlled Components: For form elements (inputs, selects, etc.), use controlled components where the value is managed by the component's state and reflected in the DOM.
- Event Delegation: Instead of directly accessing specific elements for event handling, consider using event delegation on parent elements to capture events and conditionally apply logic based on the target element.
Here's an example of controlled components:
function MyComponent() {
const [inputValue, setInputValue] = useState('');
const handleChange = (event) => {
setInputValue(event.target.value);
};
return (
<input type="text" value={inputValue} onChange={handleChange} />
);
}
javascript reactjs react-router