Skip to content

Upgrade React and Redux dependencies #258

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
306 changes: 130 additions & 176 deletions frontend/package-lock.json

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@
"generate-schema-types": "npx json2ts -i \"../common/schemas/*.json\" --cwd ../common/schemas > src/common/schema_types.ts"
},
"dependencies": {
"@hello-pangea/dnd": "^16.2.0",
"@hello-pangea/dnd": "^17.0.0",
"@popperjs/core": "^2.11.6",
"@reduxjs/toolkit": "^1.9.5",
"@reduxjs/toolkit": "^2.3.0",
"@types/ua-parser-js": "^0.7.36",
"ajv": "^8.12.0",
"async-await-queue": "^2.1.4",
Expand All @@ -30,13 +30,13 @@
"next": "^13.5.4",
"nextjs-google-analytics": "^2.3.3",
"ping.js": "^0.3.0",
"react": "18.2.0",
"react": "^18.3.1",
"react-bootstrap": "^2.7.2",
"react-bootstrap-toggle": "^2.3.2",
"react-dom": "18.2.0",
"react-dom": "^18.3.1",
"react-dropdown-tree-select": "^2.8.0",
"react-dropzone": "^14.2.3",
"react-redux": "^8.1.1",
"react-redux": "^9.1.2",
"react-render-if-visible": "^2.1.1",
"react-use": "^17.5.0",
"styled-components": "^5.3.9",
Expand All @@ -54,9 +54,9 @@
"@types/jest": "^29.5.0",
"@types/js-cookie": "^3.0.3",
"@types/node": "^18.16.0",
"@types/react": "^18.2.0",
"@types/react": "^18.3.1",
"@types/react-bootstrap": "^0.32.32",
"@types/react-dom": "^18.2.0",
"@types/react-dom": "^18.3.1",
"@types/styled-components": "^5.1.26",
"@typescript-eslint/eslint-plugin": "^5.57.0",
"@typescript-eslint/parser": "^5.57.0",
Expand Down
42 changes: 22 additions & 20 deletions frontend/src/app/listenerMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
* Retrieved from https://redux-toolkit.js.org/api/createListenerMiddleware
*/

import type { TypedAddListener, TypedStartListening } from "@reduxjs/toolkit";
import {
addListener,
createListenerMiddleware,
Expand All @@ -25,7 +24,6 @@ import {
} from "@/features/invalidIdentifiers/invalidIdentifiersSlice";
import {
addMembers,
clearQueries,
selectProjectCardback,
setQueries,
setSelectedCardback,
Expand Down Expand Up @@ -55,12 +53,12 @@ import type { AppDispatch, RootState } from "./store";

export const listenerMiddleware = createListenerMiddleware();

export type AppStartListening = TypedStartListening<RootState, AppDispatch>;
const startAppListening = listenerMiddleware.startListening.withTypes<
RootState,
AppDispatch
>();

const startAppListening =
listenerMiddleware.startListening as AppStartListening;

const addAppListener = addListener as TypedAddListener<RootState, AppDispatch>;
const addAppListener = addListener.withTypes<RootState, AppDispatch>();

//# endregion

Expand Down Expand Up @@ -128,7 +126,7 @@ startAppListening({
const isBackendConfigured = selectBackendConfigured(state);
const searchSettingsSourcesValid = selectSearchSettingsSourcesValid(state);
if (isBackendConfigured && searchSettingsSourcesValid) {
await dispatch(clearSearchResults());
dispatch(clearSearchResults());
await fetchCardDocumentsAndReportError(dispatch);
}
},
Expand Down Expand Up @@ -182,18 +180,22 @@ startAppListening({
*/

// wait for all search results to load (removing this will cause a race condition)
await condition((action, currentState) => {
const { slots }: { slots: Array<[Faces, number]> } = action.payload;
return slots
.map(([face, slot]) => {
const searchQuery = currentState.project.members[slot][face]?.query;
return searchQuery?.query != null
? currentState.searchResults.searchResults[searchQuery.query][
searchQuery.card_type
] != null
: true;
})
.every((value) => value);
await condition((action, currentState): boolean => {
if (setQueries.match(action)) {
const { slots }: { slots: Array<[Faces, number]> } = action.payload;
return slots
.map(([face, slot]) => {
const searchQuery = currentState.project.members[slot][face]?.query;
return searchQuery?.query != null
? currentState.searchResults.searchResults[searchQuery.query][
searchQuery.card_type
] != null
: true;
})
.every((value) => value);
} else {
return true;
}
});

const state = getState();
Expand Down
39 changes: 27 additions & 12 deletions frontend/src/app/store.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import type { PreloadedState } from "@reduxjs/toolkit";
import type { Middleware, MiddlewareAPI } from "@reduxjs/toolkit";
import { combineReducers, configureStore } from "@reduxjs/toolkit";
import { isRejectedWithValue } from "@reduxjs/toolkit";
import { isAction, MiddlewareAPI } from "@reduxjs/toolkit";
import {
combineReducers,
configureStore,
isRejectedWithValue,
Tuple,
} from "@reduxjs/toolkit";

import { api } from "@/app/api";
import { listenerMiddleware } from "@/app/listenerMiddleware";
Expand Down Expand Up @@ -38,25 +41,36 @@ const rootReducer = combineReducers({

//# region middleware

const rtkQueryErrorLogger: Middleware =
(api: MiddlewareAPI) => (next) => (action) => {
const rtkQueryErrorLogger =
(api: MiddlewareAPI) =>
(next: (action: unknown) => unknown) =>
(action: unknown) => {
/**
* Whenever a RTK Query API request fails, display the response's error message to the user as a toast.
*/

if (!isAction(action)) {
return;
}

const backendConfigured = selectBackendConfigured(api.getState());
if (
backendConfigured &&
isRejectedWithValue(action) &&
action.payload?.data != null
action.payload != null
) {
// dispatch the error to the store for displaying in a toast to the user
const data = (
action.payload as {
data?: { name?: string | null; message?: string | null };
}
)?.data;
api.dispatch(
setNotification([
action.type,
{
name: action.payload.data.name,
message: action.payload.data.message,
name: data?.name ?? null,
message: data?.message ?? null,
level: "error",
},
])
Expand All @@ -68,20 +82,21 @@ const rtkQueryErrorLogger: Middleware =

//# endregion

export const setupStore = (preloadedState?: PreloadedState<RootState>) => {
export const setupStore = (preloadedState?: Partial<RootState>) => {
return configureStore({
reducer: rootReducer,
preloadedState,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware()
.prepend(listenerMiddleware.middleware)
.concat([api.middleware, rtkQueryErrorLogger]),
.concat(new Tuple(api.middleware, rtkQueryErrorLogger)),
});
};

const store = setupStore();

export type AppStore = ReturnType<typeof setupStore>;
export type RootState = ReturnType<typeof rootReducer>;
export type AppDispatch = typeof store.dispatch;
export type AppDispatch = AppStore["dispatch"];

export default store;
3 changes: 1 addition & 2 deletions frontend/src/common/test-utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
* Retrieved from https://redux.js.org/usage/writing-tests
*/

import type { PreloadedState } from "@reduxjs/toolkit";
import { Store } from "@reduxjs/toolkit";
import { within } from "@testing-library/dom";
import type { RenderOptions } from "@testing-library/react";
Expand All @@ -24,7 +23,7 @@ import { LayoutWithoutReduxProvider } from "@/features/ui/layout";
// This type interface extends the default options for render from RTL, as well
// as allows the user to specify other things such as initialState, store.
interface ExtendedRenderOptions extends Omit<RenderOptions, "queries"> {
preloadedState?: PreloadedState<RootState>;
preloadedState?: Partial<RootState>;
store?: Store;
}

Expand Down
22 changes: 14 additions & 8 deletions frontend/src/common/types.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";

import type { AppDispatch, RootState } from "@/app/store";
import {
asyncThunkCreator,
buildCreateSlice,
createAsyncThunk,
} from "@reduxjs/toolkit";
import { useDispatch, useSelector, useStore } from "react-redux";

import type { AppDispatch, AppStore, RootState } from "@/app/store";
import { CSVHeaders } from "@/common/constants";
import { SearchQuery } from "@/common/schema_types";
export type {
Expand All @@ -13,15 +17,17 @@ export type {
SourceSettings,
} from "@/common/schema_types";

type DispatchFunc = () => AppDispatch;
export const useAppDispatch: DispatchFunc = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
export const useAppDispatch = useDispatch.withTypes<AppDispatch>();
export const useAppSelector = useSelector.withTypes<RootState>();
export const useAppStore = useStore.withTypes<AppStore>();
export const createAppSlice = buildCreateSlice({
creators: { asyncThunk: asyncThunkCreator },
});

export const createAppAsyncThunk = createAsyncThunk.withTypes<{
state: RootState;
dispatch: AppDispatch;
rejectValue: string;
extra: { s: string; n: number };
}>();

export type ThunkStatus = "idle" | "loading" | "succeeded" | "failed";
Expand Down
6 changes: 2 additions & 4 deletions frontend/src/features/backend/backendSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,18 @@
* State management for the backend that the app should communicate with as configured by the user.
*/

import { createSlice } from "@reduxjs/toolkit";

import { useGetBackendInfoQuery } from "@/app/api";
import { RootState } from "@/app/store";
import { ProjectName } from "@/common/constants";
import { BackendState, useAppSelector } from "@/common/types";
import { BackendState, createAppSlice, useAppSelector } from "@/common/types";

//# region slice configuration

const initialState: BackendState = {
url: null,
};

export const backendSlice = createSlice({
export const backendSlice = createAppSlice({
name: "backend",
initialState,
reducers: {
Expand Down
12 changes: 7 additions & 5 deletions frontend/src/features/card/cardbackSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@
* State management for cardbacks retrieved from the backend.
*/

import { createSlice } from "@reduxjs/toolkit";

import { APIGetCardbacks } from "@/app/api";
import { AppDispatch, RootState } from "@/app/store";
import { CardbacksState, createAppAsyncThunk } from "@/common/types";
import {
CardbacksState,
createAppAsyncThunk,
createAppSlice,
} from "@/common/types";
import { selectBackendURL } from "@/features/backend/backendSlice";
import { selectSearchSettings } from "@/features/searchSettings/searchSettingsSlice";
import { setNotification } from "@/features/toasts/toastsSlice";
Expand Down Expand Up @@ -51,15 +53,15 @@ const initialState: CardbacksState = {
error: null,
};

export const cardbackSlice = createSlice({
export const cardbackSlice = createAppSlice({
name: "cardbacks",
initialState,
reducers: {
addCardbackDocuments: (state, action) => {
state.cardbacks = [...action.payload];
},
},
extraReducers(builder) {
extraReducers: (builder) => {
builder
.addCase(fetchCardbacks.pending, (state, action) => {
state.status = "loading";
Expand Down
8 changes: 3 additions & 5 deletions frontend/src/features/export/exportDecklist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
import { saveAs } from "file-saver";
import React from "react";
import Dropdown from "react-bootstrap/Dropdown";
import { useStore } from "react-redux";

import { RootState } from "@/app/store";
import { Back, Card, FaceSeparator, Front } from "@/common/constants";
Expand All @@ -16,6 +15,7 @@ import {
ProjectMember,
SlotProjectMembers,
useAppSelector,
useAppStore,
} from "@/common/types";
import { RightPaddedIcon } from "@/components/icon";
import {
Expand Down Expand Up @@ -120,17 +120,15 @@ const selectGeneratedDecklist = (state: RootState): string => {
export function ExportDecklist() {
//# region queries and hooks

const store = useStore();
const store = useAppStore();
const isProjectEmpty = useAppSelector(selectIsProjectEmpty);

//# endregion

//# region callbacks

const downloadFile = () => {
const generatedDecklist = selectGeneratedDecklist(
store.getState() as RootState
);
const generatedDecklist = selectGeneratedDecklist(store.getState());
saveAs(
new Blob([generatedDecklist], { type: "text/plain;charset=utf-8" }),
"decklist.txt" // TODO: use project name here when we eventually track that
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/features/export/exportXML.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
import { saveAs } from "file-saver";
import React from "react";
import Dropdown from "react-bootstrap/Dropdown";
import { useStore } from "react-redux";
import formatXML from "xml-formatter";

import { RootState } from "@/app/store";
import { Back, Front, ReversedCardTypePrefixes } from "@/common/constants";
import { useAppStore } from "@/common/types";
import {
CardDocuments,
FinishSettingsState,
Expand Down Expand Up @@ -208,10 +208,10 @@ export function generateXML(
}

export function useExportXML() {
const store = useStore();
const store = useAppStore();

return () => {
const generatedXML = selectGeneratedXML(store.getState() as RootState);
const generatedXML = selectGeneratedXML(store.getState());
saveAs(
new Blob([generatedXML], { type: "text/xml;charset=utf-8" }),
"cards.xml"
Expand Down
6 changes: 3 additions & 3 deletions frontend/src/features/finishSettings/finishSettingsSlice.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { PayloadAction } from "@reduxjs/toolkit";

import { RootState } from "@/app/store";
import { CardstockFoilCompatibility } from "@/common/constants";
import { Cardstock, FinishSettingsState } from "@/common/types";
import { Cardstock, createAppSlice, FinishSettingsState } from "@/common/types";

//# region slice configuration

Expand All @@ -11,7 +11,7 @@ const initialState: FinishSettingsState = {
foil: false,
};

export const finishSettingsSlice = createSlice({
export const finishSettingsSlice = createAppSlice({
name: "finishSettings",
initialState,
reducers: {
Expand Down
Loading
Loading