From 66f351e944c11614626e1f4e04871b918f05c5e0 Mon Sep 17 00:00:00 2001 From: hzrd149 Date: Sun, 17 Dec 2023 10:06:22 -0600 Subject: [PATCH] store the filter state in route state --- src/hooks/use-route-state-value.ts | 60 ++++++++++++++++++++++++++++++ src/views/hashtag/index.tsx | 8 ++-- src/views/user/notes.tsx | 7 ++-- 3 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 src/hooks/use-route-state-value.ts diff --git a/src/hooks/use-route-state-value.ts b/src/hooks/use-route-state-value.ts new file mode 100644 index 000000000..f6297dd19 --- /dev/null +++ b/src/hooks/use-route-state-value.ts @@ -0,0 +1,60 @@ +import { useCallback, useRef } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; + +type Actions = { + setValue: (v: T | ((v: T | undefined) => T)) => void; + clearValue: () => void; +}; + +export default function useRouteStateValue(key: string, fallback: T): { value: T } & Actions; +export default function useRouteStateValue( + key: string, + fallback?: T, +): { value: T | undefined } & Actions; +export default function useRouteStateValue(key: string, fallback?: T) { + const navigate = useNavigate(); + const location = useLocation(); + + const stateRef = useRef>(location.state ?? {}); + stateRef.current = location.state ?? {}; + const valueRef = useRef(stateRef.current[key] ?? fallback); + valueRef.current = stateRef.current[key] ?? fallback; + + const setValue = useCallback( + (valueOrSetter: T | ((v: T) => T)) => { + const newState = { ...stateRef.current }; + if (typeof valueOrSetter === "function") { + // @ts-ignore + newState[key] = valueOrSetter(valueRef.current); + } else newState[key] = valueOrSetter; + + if (stateRef.current[key] !== newState[key]) { + navigate(".", { state: newState, replace: true }); + } + }, + [key], + ); + const clearValue = useCallback(() => { + const newState = { ...stateRef.current }; + delete newState[key]; + navigate(".", newState); + }, [key]); + + return { value: valueRef.current, setValue, clearValue }; +} + +export function useRouteStateBoolean(key: string, fallback?: boolean) { + const stateValue = useRouteStateValue(key, fallback ?? false); + + const onOpen = useCallback(() => { + stateValue.setValue(true); + }, [stateValue.setValue]); + const onClose = useCallback(() => { + stateValue.setValue(false); + }, [stateValue.setValue]); + const onToggle = useCallback(() => { + stateValue.setValue((v) => !v); + }, [stateValue.setValue]); + + return { isOpen: stateValue.value, onOpen, onClose, onToggle }; +} diff --git a/src/views/hashtag/index.tsx b/src/views/hashtag/index.tsx index e1e3d1e65..83b86827b 100644 --- a/src/views/hashtag/index.tsx +++ b/src/views/hashtag/index.tsx @@ -5,12 +5,9 @@ import { EditableInput, EditablePreview, Flex, - FormControl, - FormLabel, IconButton, Input, Spacer, - useDisclosure, useEditableControls, } from "@chakra-ui/react"; import { CloseIcon } from "@chakra-ui/icons"; @@ -30,6 +27,7 @@ import useClientSideMuteFilter from "../../hooks/use-client-side-mute-filter"; import PeopleListProvider, { usePeopleListContext } from "../../providers/people-list-provider"; import PeopleListSelection from "../../components/people-list-selection/people-list-selection"; import NoteFilterTypeButtons from "../../components/note-filter-type-buttons"; +import { useRouteStateBoolean } from "../../hooks/use-route-state-value"; function EditableControls() { const { isEditing, getSubmitButtonProps, getCancelButtonProps, getEditButtonProps } = useEditableControls(); @@ -53,8 +51,8 @@ function HashTagPage() { useAppTitle("#" + hashtag); - const showReplies = useDisclosure({ defaultIsOpen: true }); - const showReposts = useDisclosure({ defaultIsOpen: true }); + const showReplies = useRouteStateBoolean("show-replies", true); + const showReposts = useRouteStateBoolean("show-reposts", true); const readRelays = useRelaySelectionRelays(); diff --git a/src/views/user/notes.tsx b/src/views/user/notes.tsx index 2ce2f57d6..4cfccba60 100644 --- a/src/views/user/notes.tsx +++ b/src/views/user/notes.tsx @@ -1,5 +1,5 @@ import { useCallback } from "react"; -import { Flex, Spacer, useDisclosure } from "@chakra-ui/react"; +import { Flex, Spacer } from "@chakra-ui/react"; import { useOutletContext } from "react-router-dom"; import { Kind } from "nostr-tools"; @@ -12,13 +12,14 @@ import { STREAM_KIND } from "../../helpers/nostr/stream"; import TimelineViewType from "../../components/timeline-page/timeline-view-type"; import TimelinePage, { useTimelinePageEventFilter } from "../../components/timeline-page"; import NoteFilterTypeButtons from "../../components/note-filter-type-buttons"; +import { useRouteStateBoolean } from "../../hooks/use-route-state-value"; export default function UserNotesTab() { const { pubkey } = useOutletContext() as { pubkey: string }; const readRelays = useAdditionalRelayContext(); - const showReplies = useDisclosure(); - const showReposts = useDisclosure({ defaultIsOpen: true }); + const showReplies = useRouteStateBoolean("show-replies", false); + const showReposts = useRouteStateBoolean("show-reposts", true); const timelineEventFilter = useTimelinePageEventFilter(); const eventFilter = useCallback(