|
4 | 4 | useDashboard,
|
5 | 5 | useIsWorkspaceActionsEnabled,
|
6 | 6 | } from "components/Dashboard/DashboardProvider";
|
7 |
| -import { FC, useEffect, useState } from "react"; |
| 7 | +import { type FC, useEffect, useState, useSyncExternalStore } from "react"; |
8 | 8 | import { Helmet } from "react-helmet-async";
|
9 | 9 | import { pageTitle } from "utils/page";
|
10 | 10 | import { useWorkspacesData, useWorkspaceUpdate } from "./data";
|
@@ -44,7 +44,11 @@ const WorkspacesPage: FC = () => {
|
44 | 44 | // each hook.
|
45 | 45 | const searchParamsResult = useSafeSearchParams();
|
46 | 46 | const pagination = usePagination({ searchParamsResult });
|
47 |
| - const filterProps = useWorkspacesFilter({ searchParamsResult, pagination }); |
| 47 | + const filterProps = useWorkspacesFilter({ |
| 48 | + searchParamsResult, |
| 49 | + onPageChange: () => pagination.goToPage(1), |
| 50 | + }); |
| 51 | + |
48 | 52 | const { data, error, queryKey, refetch } = useWorkspacesData({
|
49 | 53 | ...pagination,
|
50 | 54 | query: filterProps.filter.query,
|
@@ -136,30 +140,61 @@ const WorkspacesPage: FC = () => {
|
136 | 140 |
|
137 | 141 | export default WorkspacesPage;
|
138 | 142 |
|
| 143 | +const workspaceFilterKey = "WorkspacesPage/filter"; |
| 144 | +const defaultWorkspaceFilter = "owner:me"; |
| 145 | + |
| 146 | +// Function should stay outside components as much as possible; if declared |
| 147 | +// inside the component, React would add/remove event listeners every render |
| 148 | +function subscribeToFilterChanges(notifyReact: () => void) { |
| 149 | + const onStorageChange = (event: StorageEvent) => { |
| 150 | + const { key, storageArea, oldValue, newValue } = event; |
| 151 | + |
| 152 | + const shouldNotify = |
| 153 | + key === workspaceFilterKey && |
| 154 | + storageArea === window.localStorage && |
| 155 | + newValue !== oldValue; |
| 156 | + |
| 157 | + if (shouldNotify) { |
| 158 | + notifyReact(); |
| 159 | + } |
| 160 | + }; |
| 161 | + |
| 162 | + window.addEventListener("storage", onStorageChange); |
| 163 | + return () => window.removeEventListener("storage", onStorageChange); |
| 164 | +} |
| 165 | + |
139 | 166 | type UseWorkspacesFilterOptions = {
|
140 | 167 | searchParamsResult: ReturnType<typeof useSearchParams>;
|
141 |
| - pagination: ReturnType<typeof usePagination>; |
| 168 | + onPageChange: () => void; |
142 | 169 | };
|
143 | 170 |
|
144 |
| -const filterPreferencesKey = "WorkspacesPage/filterPreferences"; |
145 |
| - |
146 | 171 | const useWorkspacesFilter = ({
|
147 | 172 | searchParamsResult,
|
148 |
| - pagination, |
| 173 | + onPageChange, |
149 | 174 | }: UseWorkspacesFilterOptions) => {
|
| 175 | + // Using useSyncExternalStore store to safely access localStorage from the |
| 176 | + // first render; both snapshot callbacks return primitives, so no special |
| 177 | + // trickery needed to prevent hook from immediately blowing up in dev mode |
| 178 | + const localStorageFilter = useSyncExternalStore( |
| 179 | + subscribeToFilterChanges, |
| 180 | + () => { |
| 181 | + return ( |
| 182 | + window.localStorage.getItem(workspaceFilterKey) ?? |
| 183 | + defaultWorkspaceFilter |
| 184 | + ); |
| 185 | + }, |
| 186 | + () => defaultWorkspaceFilter, |
| 187 | + ); |
| 188 | + |
150 | 189 | const filter = useFilter({
|
| 190 | + /** |
| 191 | + * @todo Rename initialValue to fallbackFilter once changes have been tested |
| 192 | + */ |
| 193 | + initialValue: localStorageFilter, |
151 | 194 | searchParamsResult,
|
152 |
| - initialValue: () => { |
153 |
| - const fallbackValue = "owner:me"; |
154 |
| - if (typeof window === "undefined") { |
155 |
| - return fallbackValue; |
156 |
| - } |
157 |
| - |
158 |
| - return window.localStorage.getItem(filterPreferencesKey) ?? fallbackValue; |
159 |
| - }, |
160 | 195 | onUpdate: (newValues) => {
|
161 |
| - window.localStorage.setItem(filterPreferencesKey, newValues); |
162 |
| - pagination.goToPage(1); |
| 196 | + window.localStorage.setItem(workspaceFilterKey, newValues); |
| 197 | + onPageChange(); |
163 | 198 | },
|
164 | 199 | });
|
165 | 200 |
|
|
0 commit comments