Skip to content

feat: make workspace search bar remember text #9759

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 17 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
Next Next commit
refactor: Centralize stable useSearchParams
  • Loading branch information
Parkreiner committed Sep 18, 2023
commit b06d77218b4bef58915fe49e9a198f83ba787d37
16 changes: 6 additions & 10 deletions site/src/components/Filter/filter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import Divider from "@mui/material/Divider";
import OpenInNewOutlined from "@mui/icons-material/OpenInNewOutlined";

import { useDebouncedFunction } from "hooks/debounce";
import { useEffectEvent } from "hooks/hookPolyfills";

export type PresetFilter = {
name: string;
Expand All @@ -48,17 +47,18 @@ export const useFilter = ({
onUpdate,
searchParamsResult,
}: UseFilterConfig) => {
const [searchParams, setSearchParams] = searchParamsResult;

// Fully expect the initialValue functions to have some impurity (e.g. reading
// from localStorage during a render path). (Ab)using useState's lazy
// initialization mode to guarantee impurities only exist on mount. Pattern
// has added benefit of locking down initialValue and ignoring any accidental
// value changes on re-renders
const [readonlyInitialQueryState] = useState(initialValue);

// React Router doesn't give setSearchParams a stable memory reference; need
// extra logic to prevent on-mount effect from running too often
const [searchParams, setSearchParams] = searchParamsResult;
const syncSearchParamsOnMount = useEffectEvent(() => {
// Sync the params with the value provided via the initialValue function;
// should behave only as an on-mount effect
useEffect(() => {
setSearchParams((current) => {
const currentFilter = current.get(useFilterParamsKey);
if (currentFilter !== readonlyInitialQueryState) {
Expand All @@ -67,11 +67,7 @@ export const useFilter = ({

return current;
});
});

useEffect(() => {
syncSearchParamsOnMount();
}, [syncSearchParamsOnMount]);
}, [setSearchParams, readonlyInitialQueryState]);

const update = (newValues: string | FilterValues) => {
const serialized =
Expand Down
17 changes: 16 additions & 1 deletion site/src/pages/WorkspacesPage/WorkspacesPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,28 @@ import { MONOSPACE_FONT_FAMILY } from "theme/constants";
import TextField from "@mui/material/TextField";
import { displayError } from "components/GlobalSnackbar/utils";
import { getErrorMessage } from "api/errors";
import { useEffectEvent } from "hooks/hookPolyfills";

function useSafeSearchParams() {
// Have to wrap setSearchParams because React Router doesn't make sure that
// the function's memory reference stays stable on each render, even though
// its logic never changes, and it even has function update support
const [searchParams, setSearchParams] = useSearchParams();
const stableSetSearchParams = useEffectEvent(setSearchParams);

// Need this to be a tuple type, but can't use "as const", because that would
// make the whole array readonly and cause type mismatches
return [searchParams, stableSetSearchParams] as ReturnType<
typeof useSearchParams
>;
}

const WorkspacesPage: FC = () => {
const [dormantWorkspaces, setDormantWorkspaces] = useState<Workspace[]>([]);
// If we use a useSearchParams for each hook, the values will not be in sync.
// So we have to use a single one, centralizing the values, and pass it to
// each hook.
const searchParamsResult = useSearchParams();
const searchParamsResult = useSafeSearchParams();
const pagination = usePagination({ searchParamsResult });
const filterProps = useWorkspacesFilter({ searchParamsResult, pagination });
const { data, error, queryKey, refetch } = useWorkspacesData({
Expand Down