From 19e45424ae848370694100538a48889d24fb783c Mon Sep 17 00:00:00 2001
From: schroda <50052685+schroda@users.noreply.github.com>
Date: Sat, 20 Sep 2025 19:44:27 +0200
Subject: [PATCH] Make react router routing possible outside components
---
src/App.tsx | 14 ++++++++-
.../reader/services/ReaderControls.ts | 6 ++--
src/features/reader/services/ReaderService.ts | 15 ++++------
src/lib/react-router/ReactRouter.ts | 29 +++++++++++++++++++
4 files changed, 50 insertions(+), 14 deletions(-)
create mode 100644 src/lib/react-router/ReactRouter.ts
diff --git a/src/App.tsx b/src/App.tsx
index eec219e5..8ef58e81 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -8,7 +8,7 @@
import CssBaseline from '@mui/material/CssBaseline';
import React, { useEffect, useLayoutEffect } from 'react';
-import { Navigate, Outlet, Route, Routes, useLocation } from 'react-router-dom';
+import { Navigate, Outlet, Route, Routes, useLocation, useNavigate } from 'react-router-dom';
import { loadErrorMessages, loadDevMessages } from '@apollo/client/dev';
import { loadable } from 'react-lazily/loadable';
import Box from '@mui/material/Box';
@@ -32,6 +32,7 @@ import { LoginPage } from '@/features/authentication/screens/LoginPage.tsx';
import { AuthGuard } from '@/features/authentication/components/AuthGuard.tsx';
import { SearchParam } from '@/base/Base.types.ts';
import { defaultPromiseErrorHandler } from '@/lib/DefaultPromiseErrorHandler.ts';
+import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
const { Browse } = loadable(() => import('@/features/browse/screens/Browse.tsx'), lazyLoadFallback);
const { DownloadQueue } = loadable(() => import('@/features/downloads/screens/DownloadQueue.tsx'), lazyLoadFallback);
@@ -143,6 +144,16 @@ const BackgroundSubscriptions = () => {
return null;
};
+const ReactRouterSetter = () => {
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ ReactRouter.setNavigateFn(navigate);
+ }, []);
+
+ return null;
+};
+
const PrivateRoutes = () => {
const { isAuthRequired, accessToken, refreshToken } = useSessionContext();
@@ -291,6 +302,7 @@ export const App: React.FC = () => (
+
diff --git a/src/features/reader/services/ReaderControls.ts b/src/features/reader/services/ReaderControls.ts
index 3587cb37..ecb09fd6 100644
--- a/src/features/reader/services/ReaderControls.ts
+++ b/src/features/reader/services/ReaderControls.ts
@@ -166,8 +166,6 @@ export class ReaderControls {
) => void {
const { t } = useTranslation();
- const openChapter = ReaderService.useNavigateToChapter();
-
return useCallback(
(offset, doTransitionCheck = true, scrollIntoView = true) => {
const {
@@ -250,7 +248,7 @@ export class ReaderControls {
);
}
- openChapter(chapterToOpen, {
+ ReaderService.navigateToChapter(chapterToOpen, {
resumeMode: getReaderOpenChapterResumeMode(
isSpecificChapterMode || !keepRenderedChapters,
isPreviousChapter,
@@ -264,7 +262,7 @@ export class ReaderControls {
doOpenChapter().catch(defaultPromiseErrorHandler('ReaderControls#useOpenChapter'));
},
- [t, openChapter],
+ [t],
);
}
diff --git a/src/features/reader/services/ReaderService.ts b/src/features/reader/services/ReaderService.ts
index ac33fcc1..fa3c8ffb 100644
--- a/src/features/reader/services/ReaderService.ts
+++ b/src/features/reader/services/ReaderService.ts
@@ -47,6 +47,7 @@ import { FALLBACK_MANGA } from '@/features/manga/Manga.constants.ts';
import { getMetadataKey } from '@/features/metadata/Metadata.utils.ts';
import { DirectionOffset } from '@/base/Base.types.ts';
import { getReaderStore, useReaderStore } from '@/features/reader/stores/ReaderStore.ts';
+import { ReactRouter } from '@/lib/react-router/ReactRouter.ts';
const DIRECTION_TO_INVERTED: Record = {
ltr: 'rtl',
@@ -71,15 +72,11 @@ export class ReaderService {
return ReaderService.chapterUpdateQueues.get(id)!;
}
- static useNavigateToChapter(): (chapter: TChapterReader, state?: ReaderOpenChapterLocationState) => void {
- const navigate = useNavigate();
-
- return useCallback((chapter, state) => {
- navigate(Chapters.getReaderUrl(chapter), {
- replace: true,
- state,
- });
- }, []);
+ static navigateToChapter(chapter: TChapterReader, state?: ReaderOpenChapterLocationState): void {
+ ReactRouter.navigate(Chapters.getReaderUrl(chapter), {
+ replace: true,
+ state,
+ });
}
static downloadAhead(
diff --git a/src/lib/react-router/ReactRouter.ts b/src/lib/react-router/ReactRouter.ts
new file mode 100644
index 00000000..c8f8f3f6
--- /dev/null
+++ b/src/lib/react-router/ReactRouter.ts
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) Contributors to the Suwayomi project
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+import { NavigateFunction, NavigateOptions, To } from 'react-router-dom';
+import { noOp } from '@/lib/HelperFunctions.ts';
+
+export class ReactRouter {
+ private static navigateFn: NavigateFunction = noOp;
+
+ static setNavigateFn(navigate: NavigateFunction) {
+ this.navigateFn = navigate;
+ }
+
+ static navigate(delta: number): void;
+ static navigate(to: To, options?: NavigateOptions): void;
+ static navigate(toOrDelta: number | To, options?: NavigateOptions): void {
+ if (typeof toOrDelta === 'number') {
+ this.navigateFn(toOrDelta);
+ return;
+ }
+
+ this.navigateFn(toOrDelta, options);
+ }
+}