refactor: migrate from HashRouter to createHashRouter#40696
refactor: migrate from HashRouter to createHashRouter#40696
Conversation
Replace the legacy component-based HashRouter with the data router API (createHashRouter + RouterProvider). This unlocks access to loader, action, errorElement, native lazy, and useNavigation() for future use. Changes: - ui/pages/index.js: Replace HashRouter with createHashRouter + RouterProvider. Move context providers into a root layout route element (AppProviders) that renders Outlet. Error boundary fallback keeps a standalone HashRouter for the error page. - ui/pages/routes/routes.component.tsx: Extract routeConfig to module level (removes useMemo with empty deps). Replace useRoutes() with Outlet — the data router handles route matching internally. - ui/pages/routes/index.js: Re-export routeConfig for use by index.js. Test helpers keep using MemoryRouter (recommended for unit tests by React Router docs). No test file changes needed. Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
Integration tests used window.history.pushState to reset the route between tests. This manipulates the URL pathname, but createHashRouter reads window.location.hash. Replace with window.location.hash = '#/' so the data router sees the navigation. Also revert index.js to the canonical module-level createHashRouter pattern per React Router documentation. Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
8118d54 to
c3d7cfe
Compare
Builds ready [c3d7cfe]
⚡ Performance Benchmarks
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
With useRoutes(), an unmatched URL returned null and the app shell still rendered (ConfirmationHandler would navigate to the right place). With createHashRouter, unmatched URLs trigger the built-in 404 error boundary showing 'Unexpected Application Error! 404 Not Found'. Add a catch-all '*' route that redirects to home, preserving the old graceful behavior. Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
The catch-all '*' route redirected ALL unmatched URLs to home, which broke E2E tests that open popups at specific routes (confirm, connect, etc.) — they saw the homepage instead of their intended page. Replace with errorElement on the Routes layout route. When no child route matches, the data router's 404 error boundary now renders the same Routes shell (app chrome, modals, ConfirmationHandler) with an empty Outlet. This matches the old useRoutes() behavior where no match returned null — the shell rendered and ConfirmationHandler navigated to the correct route. Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
Builds ready [89cdf8b]
⚡ Performance Benchmarks
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
The errorElement approach rendered the Routes shell but with an empty
Outlet (no matched child), causing pages to not render their content.
Replace with a simple { path: '*', element: null } catch-all at the
end of routeConfig. This prevents the data router from throwing a 404
error boundary while keeping the Routes shell rendering normally with
ConfirmationHandler able to navigate to the correct route — matching
the old useRoutes() behavior where no match returned null.
Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
Builds ready [e9c3ee2]
⚡ Performance Benchmarks
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
92fa47a to
a2bf37b
Compare
a2bf37b to
f77c573
Compare
Builds ready [f77c573]
⚡ Performance Benchmarks
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
f77c573 to
3b70c4e
Compare
Builds ready [3b70c4e]
⚡ Performance Benchmarks
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
- Add RouteErrorBoundary with errorElement on both root and inner layout routes to handle render errors (page crashes) at any level. - Add catch-all '*' route that redirects to home instead of rendering null. On Firefox, some navigations produce URLs that temporarily don't match any route (different pushState/hash behavior from Chrome). The redirect ensures the app shell and ConfirmationHandler render so they can navigate to the correct route. - Restore module-level createHashRouter (canonical React Router pattern). Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
3b70c4e to
0f29f41
Compare
|
CLA Signature Action: All authors have signed the CLA. You may need to manually re-run the blocking PR check if it doesn't pass in a few minutes. |
✨ Files requiring CODEOWNER review ✨🔑 @MetaMask/accounts-engineers (8 files, +28 -28)
|
Builds ready [9ef5025]
⚡ Performance Benchmarks
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|
|
Builds ready [7e369cc]
⚡ Performance Benchmarks
🌐 Dapp Page Load BenchmarksCurrent Commit: 📄 Localhost MetaMask Test DappSamples: 100 Summary
📈 Detailed Results
Bundle size diffs [🚨 Warning! Bundle size has increased!]
|



Replace component-based HashRouter with the modern data router API (createHashRouter + RouterProvider). This unlocks access to loader, action, errorElement, native lazy, and useNavigation() for future use.
Changes:
Description
Changelog
CHANGELOG entry: refactor: migrate from HashRouter to createHashRouter
Related issues
Fixes:
Manual testing steps
Screenshots/Recordings
Before
After
Pre-merge author checklist
Pre-merge reviewer checklist