I'm trying to incorporate React Router into my Firefox browser extension to be able to display different pages in the extension's popup window. However, I keep getting the warning, You should call navigate() in a React.useEffect(), not when your component is first rendered., which causes React Router to ignore the navigation attempt.
The thing is, I'm not directly calling navigate. I'm using React Router's provided Link component, in a manner that seems to me to be entirely consistent with the doucmentation. Notably, much like in my own implementation, the documentation suggests that using Link eliminates any need to use useEffect or listen for a change in state.
In an effort to bypass this, I did try emulating a fix found for a similar issue, described here, where they used a toy state prop to make sure that no navigation occurred until the second time rendering the component. However, this didn't change the outcome at all. Below is the relevant code.
How could I fix my code to allow the browser extension to navigate within the popup window? Is something wrong with my implementation, or is this a limitation of Firefox extensions that prevents navigation with React Router?
Any and all assistance would be much appreciated!
import React, { useEffect, useState } from "react"; import ReactDOM from "react-dom" import { StaticRouter as Router, Routes, Route, Link } from "react-router"; import Button, { ButtonType } from "./ui/widgets/Button.jsx"; /* ********* * * CONSTANTS * *************/ const PATH_ROOT = "/"; const PATH_OPTIONS = "/options" /* **************** * * REACT COMPONENTS * ********************/ function MainMenu() { const [rendered, setRendered] = useState(false); console.log("Rendering..."); const menu = <div className="mainMenu"> <Link to={PATH_OPTIONS}><Button>Options</Button></Link> </div> useEffect(() => { setRendered(true); console.log("Rendered!"); }, []) return menu; } function OptionsMenu() { console.log("Attempting to render OptionsMenu"); return <div> <h1>Options</h1> <Link to={PATH_ROOT}> <Button>Back</Button> </Link> </div> } function Error() { console.log("Attempting to render ErrorMenu"); return <h1>Error: URL not found</h1> } function PopupApp() { return <Router> <Routes> <Route path={PATH_ROOT} element={<MainMenu />} /> <Route path={PATH_OPTIONS} element={<OptionsMenu />} /> <Route path="*" element={<Error />} /> </Routes> </Router> } export default PopupApp; HOW TO SET UP A MINIMUM REPRODUCIBLE EXAMPLE
EDIT: Since a minimum reproducible example was requested, I've prepared one below. Instructions for setting up the React project are listed after the code snippet.
popup.html
<!DOCTYPE html> <html> <head> <title>Extension Sandbox</title> </head> <body> <div id="popup-root"></div> </body> </html> popup.js
import React from "react"; import { createRoot } from "react-dom/client"; import PopupApp from "./PopupApp.jsx"; const popup = createRoot(document.getElementById("popup-root")); popup.render( <React.StrictMode> <PopupApp /> </React.StrictMode> ); PopupApp.jsx
import React from "react"; import ReactDOM from "react-dom" import { StaticRouter as Router, Routes, Route, Link } from "react-router"; /* ********* * * CONSTANTS * *************/ const PATH_ROOT = "/"; const PATH_OPTIONS = "/options" /* **************** * * REACT COMPONENTS * ********************/ function MainMenu() { return <div className="mainMenu"> <h1>Main</h1> <Link to={PATH_OPTIONS}> <button>Options</button> </Link> </div> } function OptionsMenu() { return <div> <h1>Options</h1> <Link to={PATH_ROOT}> <button>Back</button> </Link> </div> } function Error() { console.log("Attempting to render ErrorMenu"); return <h1>Error: URL not found</h1> } function PopupApp() { return <Router> <Routes> <Route path={PATH_ROOT} element={<MainMenu />} /> <Route path={PATH_OPTIONS} element={<OptionsMenu />} /> <Route path="*" element={<Error />} /> </Routes> </Router> } export default PopupApp; webpack.config.js
const path = require("path"); const HTMLWebpackPlugin = require("html-webpack-plugin"); const CopyWebpackPlugin = require("copy-webpack-plugin"); module.exports = { entry: "./popup.js", output: { clean: true, filename: "bundle.js", path: path.resolve(__dirname, "dist") }, module: { rules: [ { test: /.(js|jsx)$/, exclude: /node_modules/, use: { loader: "babel-loader", options: { presets: [ "@babel/preset-env", ["@babel/preset-react", {"runtime": "automatic"}] ] } } } ] }, plugins: [ new HTMLWebpackPlugin({ title: "Extension", filename: "./popup.html", template: "./popup.html" }), new CopyWebpackPlugin({ patterns: [ { from: "manifest.json", to: "manifest.json" } ] }) ] } manifest.json
{ "manifest_version": 3, "name": "React Router", "version": "1.0", "action": { "default_popup": "popup.html" } } Installation Instructions
- Copy and paste these five files into some fresh directory
- Navigate to that directory and run
npm init -y - Run
npm install react,npm install react-dom, andnpm install react-router - Run
npm install -D webpack - Run
npm install -D @babel/core,npm install -D babel-loader,npm install -D @babel/preset-react, andnpm-install -D @babel/preset-env - Run
npm install -D html-webpack-pluginandnpm install -D copy-webpack-plugin - Modify
package.json'sscriptsobject to include the property"build": "webpack --mode production". - Finally, run
npm run buildto build the project. Webpack will generate adistfolder containing the build. - Navigate to
about:debuggingin Firefox, click "Load Temporary Add-On," and then click themanifest.jsonfile in thedistdirectory in the project folder. - To reproduce the error, open up the extension, then click the button. Console output for the extension can be read by clicking "Inspect" on the
about:debuggingpage.
Linkcomponent is rendered and is meant for declarative navigation, e.g. it needs to be clicked before it does anything, whereasnavigateis used for imperative navigation, e.g. some code somewhere actively calls it. I don't see anything in your code actively initiating any navigation actions. Do you have some code elsewhere that is attempting to navigate somewhere? Please see minimal reproducible example and try to include a complete example that reproduces the problem you are trying to solve. Please also update the title to more accurately reflect what you are asking for help with.PopupAppand/or this React app is rendered or called?