Store ModList filter states in redux

So that navigating between cells and back from other pages will persist the filters.
This commit is contained in:
Tyler Hallada 2022-03-18 20:08:31 -04:00
parent 0b85e30043
commit 68796f6f9c
3 changed files with 84 additions and 20 deletions

View File

@ -10,6 +10,15 @@ import type { Mod } from "./CellData";
import type { File } from "../slices/plugins"; import type { File } from "../slices/plugins";
import { formatBytes } from "../lib/plugins"; import { formatBytes } from "../lib/plugins";
import { jsonFetcher } from "../lib/api"; import { jsonFetcher } from "../lib/api";
import {
ModWithCounts,
setSortBy,
setSortAsc,
setFilter,
setCategory,
setIncludeTranslations,
} from "../slices/modListFilters";
import { useAppDispatch, useAppSelector } from "../lib/hooks";
const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition"; const NEXUS_MODS_URL = "https://www.nexusmods.com/skyrimspecialedition";
@ -19,19 +28,10 @@ type Props = {
counts: Record<number, [number, number, number]> | null; counts: Record<number, [number, number, number]> | null;
}; };
type ModWithCounts = Mod & {
total_downloads: number;
unique_downloads: number;
views: number;
exterior_cells_edited: number;
};
const ModList: React.FC<Props> = ({ mods, files, counts }) => { const ModList: React.FC<Props> = ({ mods, files, counts }) => {
const [includeTranslations, setIncludeTranslations] = useState(true); const dispatch = useAppDispatch();
const [sortBy, setSortBy] = useState<keyof ModWithCounts>("unique_downloads"); const { sortBy, sortAsc, filter, category, includeTranslations } =
const [sortAsc, setSortAsc] = useState<boolean>(false); useAppSelector((state) => state.modListFilters);
const [filter, setFilter] = useState<string>("");
const [category, setCategory] = useState<string>("All");
const [filterResults, setFilterResults] = useState<Set<number>>(new Set()); const [filterResults, setFilterResults] = useState<Set<number>>(new Set());
const { data: cellCounts, error: cellCountsError } = useSWRImmutable( const { data: cellCounts, error: cellCountsError } = useSWRImmutable(
@ -99,7 +99,9 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
useEffect(() => { useEffect(() => {
if (modSearch.current) { if (modSearch.current) {
setFilterResults( setFilterResults(
new Set(modSearch.current.search(filter).map((result) => result.id)) new Set(
modSearch.current.search(filter ?? "").map((result) => result.id)
)
); );
} }
}, [filter]); }, [filter]);
@ -118,7 +120,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
className={styles["sort-by"]} className={styles["sort-by"]}
value={sortBy} value={sortBy}
onChange={(event) => onChange={(event) =>
setSortBy(event.target.value as keyof ModWithCounts) dispatch(setSortBy(event.target.value as keyof ModWithCounts))
} }
> >
<option value="name">Name</option> <option value="name">Name</option>
@ -137,7 +139,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
<button <button
title="Sort ascending" title="Sort ascending"
className={sortAsc ? styles.active : ""} className={sortAsc ? styles.active : ""}
onClick={() => setSortAsc(true)} onClick={() => dispatch(setSortAsc(true))}
> >
<img <img
alt="Sort ascending" alt="Sort ascending"
@ -152,7 +154,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
<button <button
title="Sort descending" title="Sort descending"
className={!sortAsc ? styles.active : ""} className={!sortAsc ? styles.active : ""}
onClick={() => setSortAsc(false)} onClick={() => dispatch(setSortAsc(false))}
> >
<img <img
alt="Sort descending" alt="Sort descending"
@ -173,7 +175,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
id="filter" id="filter"
className={styles.filter} className={styles.filter}
value={filter} value={filter}
onChange={(event) => setFilter(event.target.value)} onChange={(event) => dispatch(setFilter(event.target.value))}
/> />
</div> </div>
<div className={styles["filter-row"]}> <div className={styles["filter-row"]}>
@ -183,7 +185,7 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
id="category" id="category"
className={styles["category"]} className={styles["category"]}
value={category} value={category}
onChange={(event) => setCategory(event.target.value)} onChange={(event) => dispatch(setCategory(event.target.value))}
> >
<option value="All">All</option> <option value="All">All</option>
{( {(
@ -211,7 +213,9 @@ const ModList: React.FC<Props> = ({ mods, files, counts }) => {
id="include-translations" id="include-translations"
className={styles["include-translations"]} className={styles["include-translations"]}
checked={includeTranslations} checked={includeTranslations}
onChange={() => setIncludeTranslations(!includeTranslations)} onChange={() =>
dispatch(setIncludeTranslations(!includeTranslations))
}
/> />
</div> </div>
<hr /> <hr />

View File

@ -2,10 +2,11 @@ import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit"
import pluginsReducer from "../slices/plugins" import pluginsReducer from "../slices/plugins"
import pluginsTxtReducer from "../slices/pluginsTxt" import pluginsTxtReducer from "../slices/pluginsTxt"
import modListFiltersReducer from "../slices/modListFilters"
export function makeStore() { export function makeStore() {
return configureStore({ return configureStore({
reducer: { pluginsTxt: pluginsTxtReducer, plugins: pluginsReducer }, reducer: { pluginsTxt: pluginsTxtReducer, plugins: pluginsReducer, modListFilters: modListFiltersReducer },
middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }), middleware: (getDefaultMiddleware) => getDefaultMiddleware({ serializableCheck: false }),
}) })
} }

59
slices/modListFilters.ts Normal file
View File

@ -0,0 +1,59 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit"
import type { AppState, AppThunk } from "../lib/store"
import type { Mod } from '../components/CellData';
export type ModWithCounts = Mod & {
total_downloads: number;
unique_downloads: number;
views: number;
exterior_cells_edited: number;
};
export type ModListFiltersState = {
sortBy: keyof ModWithCounts,
sortAsc: boolean,
filter?: string,
category: string,
includeTranslations: boolean,
}
const initialState: ModListFiltersState = {
sortBy: "unique_downloads",
sortAsc: false,
filter: undefined,
category: "All",
includeTranslations: true,
};
export const modListFiltersSlice = createSlice({
name: "modListFilters",
initialState,
reducers: {
setSortBy: (state, action: PayloadAction<keyof ModWithCounts>) => ({
...state,
sortBy: action.payload,
}),
setSortAsc: (state, action: PayloadAction<boolean>) => ({
...state,
sortAsc: action.payload,
}),
setFilter: (state, action: PayloadAction<string | undefined>) => ({
...state,
filter: action.payload,
}),
setCategory: (state, action: PayloadAction<string>) => ({
...state,
category: action.payload,
}),
setIncludeTranslations: (state, action: PayloadAction<boolean>) => ({
...state,
includeTranslations: action.payload,
}),
clearFilters: () => initialState,
},
})
export const { setSortBy, setSortAsc, setFilter, setCategory, setIncludeTranslations, clearFilters } = modListFiltersSlice.actions
export default modListFiltersSlice.reducer