Skip to content

Commit eec0fc5

Browse files
authored
Replace react-router v5 with @tanstack/react-router (#63)
1 parent f9c1205 commit eec0fc5

9 files changed

+187
-184
lines changed

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
"@radix-ui/react-slot": "^1.2.0",
2424
"@radix-ui/react-tabs": "^1.1.9",
2525
"@tailwindcss/vite": "^4.1.5",
26+
"@tanstack/react-router": "^1.119.0",
27+
"@tanstack/react-router-devtools": "^1.119.1",
2628
"ansi-to-html": "^0.7.2",
2729
"class-variance-authority": "^0.7.1",
2830
"clsx": "^2.1.1",
@@ -47,7 +49,6 @@
4749
"react-joyride": "^2.5.3",
4850
"react-reflex": "^4.0.9",
4951
"react-responsive-carousel": "^3.2.23",
50-
"react-router-dom": "^5.3.4",
5152
"sjcl": "^1.0.8",
5253
"string-to-color": "^2.2.2",
5354
"string.prototype.replaceall": "^1.0.6",
@@ -76,7 +77,6 @@
7677
"@types/react": "^18.3.1",
7778
"@types/react-copy-to-clipboard": "^5.0.4",
7879
"@types/react-dom": "^18.3.1",
79-
"@types/react-router-dom": "^5.3.0",
8080
"@types/sjcl": "^1.0.30",
8181
"@types/styled-components": "^5.1.26",
8282
"@types/use-deep-compare-effect": "^1.5.1",

src/App.tsx

Lines changed: 40 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ import { ConfirmDialogProvider } from "./playground-ui/ConfirmDialogProvider";
33
import { useGoogleAnalytics } from "./playground-ui/GoogleAnalyticsHook";
44
import PlaygroundUIThemed from "./playground-ui/PlaygroundUIThemed";
55
import "react-reflex/styles.css";
6-
import { BrowserRouter } from "react-router-dom";
7-
import { type ReactNode } from "react";
86
import "typeface-roboto-mono/index.css"; // Import the Roboto Mono font.
97
import "./App.css";
108
import { EmbeddedPlayground } from "./components/EmbeddedPlayground";
@@ -13,53 +11,48 @@ import { InlinePlayground } from "./components/InlinePlayground";
1311
import AppConfig from "./services/configservice";
1412
import { PLAYGROUND_UI_COLORS } from "./theme";
1513
import { ThemeProvider } from "@/components/ThemeProvider";
14+
import {
15+
Outlet,
16+
RouterProvider,
17+
createRouter,
18+
createRoute,
19+
createRootRoute,
20+
} from "@tanstack/react-router";
21+
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
1622

17-
export interface AppProps {
18-
/**
19-
* forTesting indicates whether the app is for testing.
20-
*/
21-
forTesting?: boolean | undefined;
22-
}
23-
24-
function ForTesting() {
25-
return <FullPlayground />;
26-
}
23+
const rootRoute = createRootRoute({
24+
component: () => (
25+
<>
26+
<Outlet />
27+
<TanStackRouterDevtools />
28+
</>
29+
),
30+
});
2731

28-
function ThemedApp(props: {
29-
withRouter?: () => ReactNode;
30-
forTesting: boolean | undefined;
31-
}) {
32-
if (window.location.pathname.indexOf("/i/") >= 0) {
33-
return (
34-
// @ts-expect-error RRv5 types are jank
35-
<BrowserRouter>
36-
<InlinePlayground />
37-
</BrowserRouter>
38-
);
39-
}
40-
41-
if (window.location.pathname.indexOf("/e/") >= 0) {
42-
return (
43-
// @ts-expect-error RRv5 types are jank
44-
<BrowserRouter>
45-
<EmbeddedPlayground />
46-
</BrowserRouter>
47-
);
48-
}
49-
50-
if (props.forTesting) {
51-
return <ForTesting />;
52-
} else {
53-
return (
54-
// @ts-expect-error RRv5 types are jank
55-
<BrowserRouter>
56-
<FullPlayground />
57-
</BrowserRouter>
58-
);
59-
}
60-
}
32+
// TODO: extend the routing; the $s are catchalls.
33+
const indexRoute = createRoute({
34+
getParentRoute: () => rootRoute,
35+
path: "$",
36+
component: FullPlayground,
37+
});
38+
const inlineRoute = createRoute({
39+
getParentRoute: () => rootRoute,
40+
path: "/i/$",
41+
component: InlinePlayground,
42+
});
43+
const embeddedRoute = createRoute({
44+
getParentRoute: () => rootRoute,
45+
path: "/e/$",
46+
component: EmbeddedPlayground,
47+
});
6148

62-
function App(props: AppProps) {
49+
const routeTree = rootRoute.addChildren([
50+
indexRoute,
51+
inlineRoute,
52+
embeddedRoute,
53+
]);
54+
const router = createRouter({ routeTree });
55+
function App() {
6356
// Register GA hook.
6457
useGoogleAnalytics(AppConfig().ga.measurementId);
6558

@@ -72,7 +65,7 @@ function App(props: AppProps) {
7265
>
7366
<AlertProvider>
7467
<ConfirmDialogProvider>
75-
<ThemedApp forTesting={props.forTesting} />
68+
<RouterProvider router={router} />
7669
</ConfirmDialogProvider>
7770
</AlertProvider>
7871
</PlaygroundUIThemed>

src/components/EditorDisplay.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import monaco from "monaco-editor";
1818
import { useEffect, useMemo, useRef, useState } from "react";
1919
import { flushSync } from "react-dom";
2020
import "react-reflex/styles.css";
21-
import { useHistory, useLocation } from "react-router-dom";
21+
import { useNavigate, useLocation } from "@tanstack/react-router";
2222
import { ScrollLocation, useCookieService } from "../services/cookieservice";
2323
import {
2424
DataStore,
@@ -90,7 +90,7 @@ export function EditorDisplay(props: EditorDisplayProps) {
9090
}, [props.services.localParseService.state]);
9191

9292
const classes = useStyles();
93-
const history = useHistory();
93+
const navigate = useNavigate();
9494
const location = useLocation();
9595

9696
const datastore = props.datastore;
@@ -175,9 +175,10 @@ export function EditorDisplay(props: EditorDisplayProps) {
175175
flushSync(() => {
176176
setLocalIndex(localIndex + 1);
177177

178+
// TODO: this shouldn't be necessary. Moving to redux may make this less painful.
178179
const updated = datastore.update(currentItem!, value || "");
179180
if (updated && updated.pathname !== location.pathname) {
180-
history.replace(updated.pathname);
181+
navigate({ to: updated.pathname, replace: true });
181182
}
182183

183184
props.datastoreUpdated();

src/components/FullPlayground.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ import { fileDialog } from "file-select-dialog";
4242
import { useEffect, useMemo, useState, type ReactNode } from "react";
4343
import { useCookies } from "react-cookie";
4444
import "react-reflex/styles.css";
45-
import { useHistory, useLocation } from "react-router-dom";
45+
import { useNavigate, useLocation } from "@tanstack/react-router";
4646
import sjcl from "sjcl";
4747
import { useKeyboardShortcuts } from "use-keyboard-shortcuts";
4848
import DISCORD from "../assets/discord.svg?react";
@@ -385,7 +385,7 @@ export function ThemedAppView(props: { datastore: DataStore }) {
385385

386386
const classes = useStyles({ prefersDarkMode: prefersDarkMode });
387387
const location = useLocation();
388-
const history = useHistory();
388+
const navigate = useNavigate();
389389

390390
const datastore = props.datastore;
391391

@@ -424,13 +424,14 @@ export function ThemedAppView(props: { datastore: DataStore }) {
424424
]);
425425

426426
// Effect: If the user lands on the `/` route, redirect them to the schema editor.
427+
// TODO: this should probably be a redirect at the routing layer.
427428
useEffect(() => {
428429
(async () => {
429430
if (currentItem === undefined) {
430-
history.replace(DataStorePaths.Schema());
431+
navigate({ to: DataStorePaths.Schema(), replace: true });
431432
}
432433
})();
433-
}, [datastore, currentItem, history]);
434+
}, [datastore, currentItem, navigate]);
434435

435436
const conductDownload = () => {
436437
const yamlContents = createValidationYAML(datastore);
@@ -468,7 +469,7 @@ export function ThemedAppView(props: { datastore: DataStore }) {
468469
datastore.loadFromParsed(uploaded);
469470
datastoreUpdated();
470471

471-
history.replace(DataStorePaths.Schema());
472+
navigate({ to: DataStorePaths.Schema(), replace: true });
472473
}
473474
})();
474475
};
@@ -563,7 +564,7 @@ export function ThemedAppView(props: { datastore: DataStore }) {
563564
datastoreUpdated();
564565

565566
services.liveCheckService.clear();
566-
history.replace(DataStorePaths.Schema());
567+
navigate({ to: DataStorePaths.Schema(), replace: true });
567568
};
568569

569570
const [previousValidationForDiff, setPreviousValidationForDiff] = useState<
@@ -639,23 +640,24 @@ export function ThemedAppView(props: { datastore: DataStore }) {
639640
const validationState = validationService.state;
640641

641642
const handleChangeTab = (
643+
// TODO: this should be a Link
642644
_event: React.ChangeEvent<object>,
643645
selectedTabValue: string,
644646
) => {
645647
const item = datastore.getById(selectedTabValue)!;
646-
history.push(item.pathname);
648+
navigate({ to: item.pathname });
647649
};
648650

649651
const setDismissTour = () => {
650652
setShowTour(false);
651653
setCookie("dismiss-tour", true);
652-
history.push(DataStorePaths.Schema());
654+
navigate({ to: DataStorePaths.Schema() });
653655
};
654656

655657
const handleTourBeforeStep = (selector: string) => {
656658
// Activate the Assertions tab before the assertions dialogs
657659
if (selector.includes(TourElementClass.assert)) {
658-
history.push(DataStorePaths.Assertions());
660+
navigate({ to: DataStorePaths.Assertions() });
659661
}
660662
};
661663

src/components/ShareLoader.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { RpcError } from "@protobuf-ts/runtime-rpc";
88
import Alert from "@material-ui/lab/Alert";
99
import React, { useEffect, useState } from "react";
1010
import "react-reflex/styles.css";
11-
import { useHistory, useLocation } from "react-router-dom";
11+
import { useNavigate, useLocation } from "@tanstack/react-router";
1212
import AppConfig from "../services/configservice";
1313
import { DataStore } from "../services/datastore";
1414

@@ -33,7 +33,7 @@ export function ShareLoader(props: {
3333
}) {
3434
const { showAlert } = useAlert();
3535
const { showConfirm } = useConfirmDialog();
36-
const history = useHistory();
36+
const navigate = useNavigate();
3737
const location = useLocation();
3838

3939
const datastore = props.datastore;
@@ -73,7 +73,7 @@ export function ShareLoader(props: {
7373
// TODO: use routing for this instead of string manipulation
7474
const pieces = location.pathname.replace(urlPrefix, "").split("/");
7575
if (pieces.length < 1 && !props.sharedRequired) {
76-
history.push("/");
76+
navigate({ to: "/" });
7777
return;
7878
}
7979

@@ -97,7 +97,7 @@ export function ShareLoader(props: {
9797
content: "Invalid sharing reference",
9898
buttonTitle: "Okay",
9999
});
100-
history.replace("/");
100+
navigate({ to: "/", replace: true });
101101
return;
102102
}
103103

@@ -116,7 +116,7 @@ export function ShareLoader(props: {
116116
content: "The shared playground specified does not exist",
117117
buttonTitle: "Okay",
118118
});
119-
history.replace("/");
119+
navigate({ to: "/", replace: true });
120120
return;
121121
}
122122

@@ -150,12 +150,14 @@ export function ShareLoader(props: {
150150
}
151151

152152
if (!props.sharedRequired) {
153-
history.replace(
154-
location.pathname.slice(
153+
// TODO: do this with routing as well
154+
navigate({
155+
to: location.pathname.slice(
155156
0,
156157
urlPrefix.length + shareReference.length,
157158
),
158-
);
159+
replace: true,
160+
});
159161
}
160162

161163
setLoadingStatus(SharedLoadingStatus.LOADED);
@@ -166,15 +168,15 @@ export function ShareLoader(props: {
166168
content: error?.message,
167169
buttonTitle: "Okay",
168170
});
169-
history.replace("/");
171+
navigate({ to: "/", replace: true });
170172
return;
171173
}
172174
})();
173175
}, [
174176
location.pathname,
175177
loadingStatus,
176178
datastore,
177-
history,
179+
navigate,
178180
showAlert,
179181
showConfirm,
180182
urlPrefix,

src/components/panels/errordisplays.tsx

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import {
66
import { Theme, createStyles, makeStyles } from "@material-ui/core/styles";
77
import Alert from "@material-ui/lab/Alert";
88
import "react-reflex/styles.css";
9-
import { Link } from "react-router-dom";
9+
import { Link } from "@tanstack/react-router";
1010
import { DataStoreItemKind, DataStorePaths } from "../../services/datastore";
1111

1212
export const ERROR_SOURCE_TO_ITEM = {
@@ -121,7 +121,7 @@ export function DeveloperWarningSourceDisplay({
121121

122122
return (
123123
<div className={classes.validationErrorContext}>
124-
In {/* @ts-expect-error RRv5 types are jank */}
124+
In
125125
<Link className={classes.link} to={DataStorePaths.Schema()}>
126126
Schema
127127
</Link>
@@ -140,7 +140,7 @@ export function DeveloperSourceDisplay(props: { error: DeveloperError }) {
140140
<div>
141141
{ve.source === DeveloperError_Source.SCHEMA && (
142142
<div className={classes.validationErrorContext}>
143-
In {/* @ts-expect-error RRv5 types are jank */}
143+
In
144144
<Link className={classes.link} to={DataStorePaths.Schema()}>
145145
Schema
146146
</Link>
@@ -149,7 +149,7 @@ export function DeveloperSourceDisplay(props: { error: DeveloperError }) {
149149
)}
150150
{ve.source === DeveloperError_Source.ASSERTION && (
151151
<div className={classes.validationErrorContext}>
152-
In {/* @ts-expect-error RRv5 types are jank */}
152+
In
153153
<Link className={classes.link} to={DataStorePaths.Assertions()}>
154154
Assertions
155155
</Link>
@@ -158,7 +158,7 @@ export function DeveloperSourceDisplay(props: { error: DeveloperError }) {
158158
)}
159159
{ve.source === DeveloperError_Source.RELATIONSHIP && (
160160
<div className={classes.validationErrorContext}>
161-
In {/* @ts-expect-error RRv5 types are jank */}
161+
In
162162
<Link className={classes.link} to={DataStorePaths.Relationships()}>
163163
Test Data
164164
</Link>
@@ -167,7 +167,7 @@ export function DeveloperSourceDisplay(props: { error: DeveloperError }) {
167167
)}
168168
{ve.source === DeveloperError_Source.VALIDATION_YAML && (
169169
<div className={classes.validationErrorContext}>
170-
In {/* @ts-expect-error RRv5 types are jank */}
170+
In
171171
<Link
172172
className={classes.link}
173173
to={DataStorePaths.ExpectedRelations()}

src/components/panels/problems.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { createStyles, makeStyles, Theme } from "@material-ui/core/styles";
99
import ErrorOutlineIcon from "@material-ui/icons/ErrorOutline";
1010
import clsx from "clsx";
1111
import "react-reflex/styles.css";
12-
import { Link } from "react-router-dom";
12+
import { Link } from "@tanstack/react-router";
1313
import { DataStorePaths } from "../../services/datastore";
1414
import { TourElementClass } from "../GuidedTour";
1515
import { PanelProps, PanelSummaryProps, useSummaryStyles } from "./base/common";
@@ -107,7 +107,7 @@ export function ProblemsPanel(props: PanelProps<PlaygroundPanelLocation>) {
107107
<Paper className={classes.errorContainer} key={`ir${index}`}>
108108
<div>
109109
<div className={classes.validationErrorContext}>
110-
In {/* @ts-expect-error RRv5 TS definitions are jank */}
110+
In
111111
<Link
112112
className={classes.link}
113113
to={DataStorePaths.Relationships()}

0 commit comments

Comments
 (0)