Skip to content

Commit 7a01e87

Browse files
authored
Upgrade react, redux & react router (#100)
* Upgrade React & Redux * Migrate to latest react router * Upgrade router to v7 * Clean dev vs non-dev deps
1 parent ab96b34 commit 7a01e87

File tree

14 files changed

+234
-311
lines changed

14 files changed

+234
-311
lines changed

eslint.config.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import js from "@eslint/js";
2-
import globals from "globals";
2+
import react from "eslint-plugin-react";
33
import reactHooks from "eslint-plugin-react-hooks";
44
import reactRefresh from "eslint-plugin-react-refresh";
5-
import react from "eslint-plugin-react";
5+
import globals from "globals";
66
import tseslint from "typescript-eslint";
77

88
export default tseslint.config(
@@ -46,7 +46,7 @@ export default tseslint.config(
4646
},
4747
settings: {
4848
react: {
49-
version: "17.0",
49+
version: "18.2",
5050
},
5151
},
5252
}

package.json

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,44 +9,42 @@
99
"@fortawesome/react-fontawesome": "^0.2.0",
1010
"@hookform/error-message": "^2.0.1",
1111
"@react-oauth/google": "^0.12.1",
12-
"@reduxjs/toolkit": "^1.8.0",
13-
"@types/lodash": "^4.14.182",
14-
"@types/react-datepicker": "^4.4.1",
15-
"@types/react-dom": "^18.0.4",
16-
"@types/react-router-dom": "^5.3.3",
17-
"@types/styled-components": "^5.1.25",
12+
"@reduxjs/toolkit": "^2.3.0",
1813
"bootstrap": "^5.2.1",
1914
"dayjs": "^1.11.0",
2015
"lodash": "^4.17.21",
2116
"query-string": "^7.1.1",
22-
"react": "^17.0.2",
2317
"react-bootstrap": "^2.7.0",
2418
"react-datepicker": "^4.7.0",
25-
"react-dom": "^17.0.2",
19+
"react-dom": "^18.2.0",
2620
"react-hook-form": "^7.52.0",
27-
"react-redux": "^7.2.6",
28-
"react-router-dom": "^5.3.0",
21+
"react-redux": "^9.2.0",
22+
"react-router-dom": "^7.9.1",
2923
"react-select": "^5.3.0",
24+
"react": "^18.2.0",
3025
"styled-components": "^5.3.3"
3126
},
3227
"devDependencies": {
3328
"@eslint/eslintrc": "^3.3.1",
3429
"@eslint/js": "^9.35.0",
30+
"@types/lodash": "^4.14.182",
3531
"@types/node": "^24.4.0",
36-
"@types/react": "^18.0.9",
32+
"@types/react-datepicker": "^4.4.1",
33+
"@types/react-dom": "^18.0.4",
3734
"@types/react-modal": "^3.16.3",
38-
"@types/react-redux": "^7.1.7",
35+
"@types/react": "^18.0.9",
36+
"@types/styled-components": "^5.1.25",
3937
"@vitejs/plugin-react": "^5.0.2",
40-
"eslint": "^9.35.0",
4138
"eslint-config-prettier": "^9.1.0",
4239
"eslint-plugin-prettier": "^5.0.1",
43-
"eslint-plugin-react": "^7.37.5",
4440
"eslint-plugin-react-hooks": "^5.2.0",
4541
"eslint-plugin-react-refresh": "^0.4.20",
42+
"eslint-plugin-react": "^7.37.5",
43+
"eslint": "^9.35.0",
4644
"globals": "^16.4.0",
4745
"prettier": "^3.1.1",
48-
"typescript": "^5.9.2",
4946
"typescript-eslint": "^8.44.0",
47+
"typescript": "^5.9.2",
5048
"vite": "^7.1.5"
5149
},
5250
"scripts": {

src/App.tsx

Lines changed: 85 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import { useEffect } from "react";
22
import {
33
BrowserRouter as Router,
4-
Redirect,
4+
Navigate,
55
Route,
6-
Switch,
6+
Routes,
77
} from "react-router-dom";
8-
import { useLoadCurrentUser, usePermissions } from "src/redux/auth";
98

109
import { refreshCsrfToken } from "src/apiClient/client";
1110
import BaseLayout from "src/components/BaseLayout";
@@ -17,6 +16,7 @@ import { MyOfficeHoursHistory } from "src/pages/OfficeHours/MyOfficeHoursHistory
1716
import { OfficeHoursHistory } from "src/pages/OfficeHours/OfficeHoursHistory";
1817
import { RequestDeskCreditPage } from "src/pages/OfficeHours/RequestDeskCreditPage";
1918
import { ChangePassword } from "src/pages/People/PersonProfile/PersonChangePassword";
19+
import { useLoadCurrentUser, usePermissions } from "src/redux/auth";
2020

2121
import { AllGearPage, GearItemPage } from "./pages/Gear";
2222
import { AddNewGear } from "./pages/Gear/AddNewGear";
@@ -31,98 +31,100 @@ function App() {
3131
const { loggedIn, isLoading } = useLoadCurrentUser();
3232
const { pathname, search } = window.location;
3333
const { isApprover } = usePermissions();
34+
3435
useEffect(() => {
3536
if (loggedIn) {
3637
// Pre-load CSRF token
3738
refreshCsrfToken();
3839
}
3940
}, [loggedIn]);
4041

42+
if (isLoading) {
43+
return null;
44+
}
45+
46+
const publicRoutes = (
47+
<>
48+
<Route path="/login" element={<LoginPage />} />
49+
<Route
50+
path="/reset-password/request"
51+
element={<RequestPasswordReset />}
52+
/>
53+
<Route
54+
path="/reset-password/confirm"
55+
element={<RequestPasswordConfirm />}
56+
/>
57+
</>
58+
);
59+
60+
// If not logged in and not loading, redirect to login
61+
if (!isLoading && !loggedIn) {
62+
return (
63+
<Router basename="/">
64+
<BaseLayout>
65+
<Routes>
66+
{publicRoutes}
67+
<Route
68+
path="*"
69+
element={
70+
<Navigate
71+
to={{
72+
pathname: "/login",
73+
search: `redirectTo=${encodeURIComponent(
74+
pathname + search,
75+
)}`,
76+
}}
77+
replace
78+
/>
79+
}
80+
/>
81+
</Routes>
82+
</BaseLayout>
83+
</Router>
84+
);
85+
}
86+
4187
return (
4288
<Router basename="/">
4389
<BaseLayout>
44-
<Switch>
45-
<Route path="/login">
46-
<LoginPage />
47-
</Route>
48-
<Route exact path="/reset-password/request/">
49-
<RequestPasswordReset />
50-
</Route>
51-
<Route exact path="/reset-password/confirm/">
52-
<RequestPasswordConfirm />
53-
</Route>
54-
<Route exact path="/change-password/">
55-
<ChangePassword />
56-
</Route>
57-
{!isLoading && !loggedIn && (
58-
<Redirect
59-
to={{
60-
pathname: "/login",
61-
search: `redirectTo=${encodeURIComponent(pathname + search)}`,
62-
}}
63-
/>
64-
)}
65-
<Route exact path="/people">
66-
<PeoplePage />
67-
</Route>
68-
<Route path="/people/:personId">
69-
<PersonPage />
70-
</Route>
71-
<Route exact path="/add-person">
72-
<AddNewPerson />
73-
</Route>
74-
<Route exact path="/add-gear">
75-
<AddNewGear />
76-
</Route>
77-
<Route exact path="/gear">
78-
<AllGearPage />
79-
</Route>
80-
<Route path="/gear/:gearId">
81-
<GearItemPage />
82-
</Route>
83-
<Route exact path="/office-hours">
84-
<OfficeHoursPage />
85-
</Route>
86-
<Route exact path="/gear-inventory">
87-
<GearInventoryPage />
88-
</Route>
89-
<Route exact path="/approvals">
90-
<ApprovalsPage />
91-
</Route>
90+
<Routes>
91+
{publicRoutes}
92+
93+
<Route path="/change-password" element={<ChangePassword />} />
94+
95+
<Route path="/people" element={<PeoplePage />} />
96+
<Route path="/people/:personId" element={<PersonPage />} />
97+
<Route path="/add-person" element={<AddNewPerson />} />
98+
99+
<Route path="/add-gear" element={<AddNewGear />} />
100+
<Route path="/gear" element={<AllGearPage />} />
101+
<Route path="/gear/:gearId" element={<GearItemPage />} />
102+
103+
<Route path="/office-hours" element={<OfficeHoursPage />} />
104+
<Route path="/gear-inventory" element={<GearInventoryPage />} />
105+
<Route path="/approvals" element={<ApprovalsPage />} />
106+
92107
{isApprover && (
93-
<Route exact path="/add-approval">
94-
<AddNewApproval />
95-
</Route>
96-
)}
97-
{loggedIn && (
98-
<Route exact path="/request-desk-credit">
99-
<RequestDeskCreditPage />
100-
</Route>
101-
)}
102-
{loggedIn && (
103-
<Route exact path="/approve-desk-credit">
104-
<ApproveDeskCreditPage />
105-
</Route>
108+
<Route path="/add-approval" element={<AddNewApproval />} />
106109
)}
107-
{loggedIn && (
108-
<Route exact path="/office-hours-history">
109-
<OfficeHoursHistory />
110-
</Route>
111-
)}
112-
{loggedIn && (
113-
<Route exact path="/volunteer-history">
114-
<MyOfficeHoursHistory />
115-
</Route>
116-
)}
117-
<Route exact path="/">
118-
<Redirect to="/people" />
119-
</Route>
120-
{loggedIn && (
121-
<Route path="*">
122-
<Redirect to="/people" />
123-
</Route>
124-
)}
125-
</Switch>
110+
111+
<Route
112+
path="/request-desk-credit"
113+
element={<RequestDeskCreditPage />}
114+
/>
115+
<Route
116+
path="/approve-desk-credit"
117+
element={<ApproveDeskCreditPage />}
118+
/>
119+
<Route
120+
path="/office-hours-history"
121+
element={<OfficeHoursHistory />}
122+
/>
123+
<Route path="/volunteer-history" element={<MyOfficeHoursHistory />} />
124+
125+
<Route path="/" element={<Navigate to="/people" replace />} />
126+
<Route path="*" element={<Navigate to="/people" replace />} />
127+
</Routes>
126128
</BaseLayout>
127129
</Router>
128130
);

src/components/BaseLayout/Header.tsx

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,27 +1,25 @@
1-
import { newApprovalUI } from "src/featureFlags";
21
import { useEffect } from "react";
3-
import { Link, useHistory } from "react-router-dom";
2+
import { Link, useLocation } from "react-router-dom";
43
import styled from "styled-components";
54

65
import { User } from "src/apiClient/types";
76
import { PersonLink } from "src/components/PersonLink";
7+
import { newApprovalUI } from "src/featureFlags";
88
import { logOut, useCurrentUser, usePermissions } from "src/redux/auth";
99
import { useAppDispatch, useConfig } from "src/redux/hooks";
1010

1111
export function Header() {
1212
const { user } = useCurrentUser();
1313

14-
const { listen } = useHistory();
14+
const location = useLocation();
1515

1616
// Hack to close the menu when the user clicks navigates to a new page
1717
useEffect(() => {
18-
return listen(() => {
19-
const menuToggle = document.querySelector(".navbar-collapse");
20-
if (menuToggle) {
21-
menuToggle.classList.remove("show");
22-
}
23-
});
24-
}, [listen]);
18+
const menuToggle = document.querySelector(".navbar-collapse");
19+
if (menuToggle) {
20+
menuToggle.classList.remove("show");
21+
}
22+
}, [location]);
2523

2624
return (
2725
<StyledNavBar className="navbar navbar-expand-md navbar-dark bg-dark mb-3 ">

src/hooks/useQueryParamFilters.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useHistory, useLocation } from "react-router";
1+
import { useLocation, useNavigate } from "react-router-dom";
22

33
type Args<Filters> = {
44
parse: (params: URLSearchParams) => Filters;
@@ -14,7 +14,7 @@ export function useQueryParamFilters<Filters extends Record<string, any>>({
1414
parse,
1515
serialize,
1616
}: Args<Filters>): ReturnType<Filters> {
17-
const history = useHistory();
17+
const navigate = useNavigate();
1818
const location = useLocation();
1919

2020
const getFilters = () => {
@@ -26,7 +26,10 @@ export function useQueryParamFilters<Filters extends Record<string, any>>({
2626
const newFilters = updater(getFilters());
2727
const serialized = serialize(newFilters);
2828
const params = new URLSearchParams(serialized);
29-
history.replace({ pathname: location.pathname, search: params.toString() });
29+
navigate(
30+
{ pathname: location.pathname, search: params.toString() },
31+
{ replace: true },
32+
);
3033
};
3134

3235
return { filters: getFilters(), setFilters };

src/index.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ import "bootstrap/js/dist/dropdown";
44
import "./index.css";
55

66
import React from "react";
7-
import ReactDOM from "react-dom";
7+
import { createRoot } from "react-dom/client";
88
import { Provider } from "react-redux";
99

1010
import App from "./App";
1111
import { store } from "./redux/store";
1212

13-
ReactDOM.render(
13+
const container = document.getElementById("root");
14+
const root = createRoot(container!);
15+
16+
root.render(
1417
<React.StrictMode>
1518
<Provider store={store}>
1619
<App />
1720
</Provider>
1821
</React.StrictMode>,
19-
document.getElementById("root"),
2022
);

src/pages/Approvals/AddNewApproval.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { useState } from "react";
2-
import { useHistory, useLocation } from "react-router-dom";
2+
import { useLocation, useNavigate } from "react-router-dom";
33

44
import {
55
createNewApproval,
@@ -14,7 +14,7 @@ import { AddNewApprovalForm } from "./AddNewApprovalForm";
1414

1515
export function AddNewApproval() {
1616
useSetPageTitle("Approve restricted gear rental");
17-
const history = useHistory();
17+
const navigate = useNavigate();
1818
const location = useLocation();
1919
const [error, setError] = useState<APIErrorType | undefined>();
2020
const refetchAllApprovals = gearDbApi.useLazyGetApprovalsQuery()[0];
@@ -32,9 +32,9 @@ export function AddNewApproval() {
3232
refetchAllApprovals({ past: undefined });
3333
if (personId != null) {
3434
refetchPersonApprovals({ personID: personId, past: false });
35-
history.push(`/people/${personId}?tab=approvals`);
35+
navigate(`/people/${personId}?tab=approvals`);
3636
} else {
37-
history.push("/approvals");
37+
navigate("/approvals");
3838
}
3939
})
4040
.catch((err) => {

0 commit comments

Comments
 (0)