Refactor routing components to use Redirect instead of Navigate; update react-router-dom version and implement history for navigation

This commit is contained in:
Cédric Verstraeten
2026-05-27 07:13:17 +00:00
parent 6c2f38679b
commit af6e75426a
8 changed files with 65 additions and 84 deletions

View File

@@ -13,6 +13,7 @@
"@testing-library/user-event": "^14.5.2",
"axios": "^1.7.7",
"classnames": "^2.5.1",
"history": "^4.10.1",
"eslint-config-prettier": "8.5.*",
"eslint-plugin-prettier": "4.2.*",
"eslint-watch": "^7.0.0",
@@ -26,7 +27,7 @@
"react-dom": "^19.0.0",
"react-i18next": "^15.1.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.27.0",
"react-router-dom": "^5.3.4",
"react-scripts": "5.0.1",
"react-tooltip": "^5.28.0",
"redux": "^5.0.1",
@@ -71,6 +72,8 @@
},
"resolutions": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
"react-dom": "^19.0.0",
"react-router-dom": "^5.3.4",
"react-router": "^5.3.4"
}
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
import PropTypes from 'prop-types';
import { withTranslation } from 'react-i18next';
import { Outlet } from 'react-router-dom';
import {
connect as connectWS,
disconnect as disconnectWS,
@@ -94,7 +93,7 @@ class App extends React.Component {
}
render() {
const { t, connected } = this.props;
const { t, connected, children } = this.props;
const { username, dashboard, dispatchLogout } = this.props;
const cloudOnline = this.getCurrentTimestamp() - dashboard.cloudOnline < 30;
return (
@@ -211,7 +210,7 @@ class App extends React.Component {
)}
<MainBody>
<Outlet />
{children}
</MainBody>
</Main>
</div>
@@ -247,6 +246,11 @@ App.propTypes = {
connected: PropTypes.bool.isRequired,
dashboard: PropTypes.object.isRequired,
dispatchGetDashboardInformation: PropTypes.func.isRequired,
children: PropTypes.node,
};
App.defaultProps = {
children: null,
};
export default withTranslation()(

View File

@@ -1,12 +1,12 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { Redirect } from 'react-router-dom';
export default function RequireAuth({ children }) {
const isAuthenticated = useSelector((s) => s.authentication.loggedIn);
if (!isAuthenticated) {
return <Navigate to="/login" replace />;
return <Redirect to="/login" />;
}
return children;
}

View File

@@ -1,16 +1,16 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { Redirect } from 'react-router-dom';
export default function RequireGuest({ children }) {
const isAuthenticated = useSelector((s) => s.authentication.loggedIn);
const isInstalled = useSelector((s) => s.authentication.installed);
if (!isInstalled) {
return <Navigate to="/install" replace />;
return <Redirect to="/install" />;
}
if (isAuthenticated) {
return <Navigate to="/" replace />;
return <Redirect to="/" />;
}
return children;
}

View File

@@ -1,13 +1,13 @@
import React from 'react';
import PropTypes from 'prop-types';
import { useSelector } from 'react-redux';
import { Navigate } from 'react-router-dom';
import { Redirect } from 'react-router-dom';
export default function RequireInstall({ children }) {
const isAuthenticated = useSelector((s) => s.authentication.loggedIn);
const isInstalled = useSelector((s) => s.authentication.installed);
if (isInstalled) {
return <Navigate to={isAuthenticated ? '/' : '/login'} replace />;
return <Redirect to={isAuthenticated ? '/' : '/login'} />;
}
return children;
}

4
ui/src/history.js Normal file
View File

@@ -0,0 +1,4 @@
import { createBrowserHistory } from 'history';
const history = createBrowserHistory();
export default history;

View File

@@ -1,12 +1,6 @@
import React, { Suspense, useEffect } from 'react';
import React, { Suspense } from 'react';
import { createRoot } from 'react-dom/client';
import {
BrowserRouter,
Routes,
Route,
Navigate,
useNavigate,
} from 'react-router-dom';
import { Router, Switch, Route, Redirect } from 'react-router-dom';
import { createStore, applyMiddleware, compose } from 'redux';
import { Provider } from 'react-redux';
import reduxWebsocket from '@giantmachines/redux-websocket';
@@ -20,7 +14,7 @@ import Media from './pages/Media/Media';
import Settings from './pages/Settings/Settings';
import RequireAuth from './containers/RequireAuth';
import RequireGuest from './containers/RequireGuest';
import { setNavigator } from './navigation';
import history from './history';
import './i18n';
// We get the token from the store to initialise the store.
@@ -66,65 +60,45 @@ const store = createStore(
const Loader = () => <div>loading...</div>;
// Bridges React Router's navigate function into a module-scoped singleton
// so Redux thunks (e.g. login/logout) can navigate without a hook.
function NavigationSetup() {
const nav = useNavigate();
useEffect(() => {
setNavigator(nav);
}, [nav]);
return null;
}
const container = document.getElementById('root');
const root = createRoot(container);
root.render(
<Provider store={store}>
<BrowserRouter>
<NavigationSetup />
<Router history={history}>
<Suspense fallback={<Loader />}>
<Routes>
<Route
path="/login"
element={
<RequireGuest>
<Login />
</RequireGuest>
}
/>
<Route element={<App />}>
<Route
path="/"
element={<Navigate to="/dashboard" replace />}
/>
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
<Route
path="/media"
element={
<RequireAuth>
<Media />
</RequireAuth>
}
/>
<Route
path="/settings"
element={
<RequireAuth>
<Settings />
</RequireAuth>
}
/>
<Switch>
<Route path="/login">
<RequireGuest>
<Login />
</RequireGuest>
</Route>
</Routes>
<Route>
<App>
<Switch>
<Route exact path="/">
<Redirect to="/dashboard" />
</Route>
<Route exact path="/dashboard">
<RequireAuth>
<Dashboard />
</RequireAuth>
</Route>
<Route exact path="/media">
<RequireAuth>
<Media />
</RequireAuth>
</Route>
<Route exact path="/settings">
<RequireAuth>
<Settings />
</RequireAuth>
</Route>
</Switch>
</App>
</Route>
</Switch>
</Suspense>
</BrowserRouter>
</Router>
</Provider>
);

View File

@@ -1,16 +1,12 @@
// Lightweight navigation singleton so non-React code (Redux thunks) can
// trigger client-side navigation. The value is set from a component that
// has access to React Router's `useNavigate` hook (see NavigationSetup in
// index.jsx).
// trigger client-side navigation. Backed by the shared `history` instance
// passed to react-router's <Router>.
import history from './history';
let navigatorFn = null;
export const setNavigator = (fn) => {
navigatorFn = fn;
export const setNavigator = () => {
// Kept for API compatibility; no-op since history is module-scoped.
};
export const navigate = (path, options) => {
if (navigatorFn) {
navigatorFn(path, options);
}
export const navigate = (path) => {
history.push(path);
};