mirror of
https://github.com/hzrd149/nostrudel.git
synced 2025-04-11 05:09:36 +02:00
fix issue with breakpoints causing re-render
This commit is contained in:
parent
85a9dad33a
commit
ed1cb04235
@ -16,8 +16,11 @@
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "^0.8.2-rc.7",
|
||||
"@chakra-ui/anatomy": "^2.2.1",
|
||||
"@chakra-ui/breakpoint-utils": "^2.0.8",
|
||||
"@chakra-ui/icons": "^2.1.1",
|
||||
"@chakra-ui/media-query": "^3.3.0",
|
||||
"@chakra-ui/react": "^2.8.1",
|
||||
"@chakra-ui/shared-utils": "^2.0.4",
|
||||
"@chakra-ui/styled-system": "^2.9.1",
|
||||
"@emotion/react": "^11.11.1",
|
||||
"@emotion/styled": "^11.11.0",
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Card, CardBody, CardProps, Flex, Heading, Image, Link, Tag, Text, useBreakpointValue } from "@chakra-ui/react";
|
||||
import { Card, CardBody, CardProps, Flex, Heading, Image, Link, Tag, Text } from "@chakra-ui/react";
|
||||
import { Link as RouterLink, useNavigate } from "react-router-dom";
|
||||
|
||||
import { parseStreamEvent } from "../../../helpers/nostr/stream";
|
||||
@ -8,6 +8,7 @@ import { UserLink } from "../../user-link";
|
||||
import { UserAvatar } from "../../user-avatar";
|
||||
import useEventNaddr from "../../../hooks/use-event-naddr";
|
||||
import Timestamp from "../../timestamp";
|
||||
import { useBreakpointValue } from "../../../providers/breakpoint-provider";
|
||||
|
||||
export default function EmbeddedStream({ event, ...props }: Omit<CardProps, "children"> & { event: NostrEvent }) {
|
||||
const stream = parseStreamEvent(event);
|
||||
|
@ -8,7 +8,7 @@ import {
|
||||
useRef,
|
||||
useState,
|
||||
} from "react";
|
||||
import { Image, ImageProps, useBreakpointValue } from "@chakra-ui/react";
|
||||
import { Image, ImageProps } from "@chakra-ui/react";
|
||||
|
||||
import appSettings from "../../services/settings/app-settings";
|
||||
import { useTrusted } from "../../providers/trust";
|
||||
@ -19,6 +19,7 @@ import { isImageURL } from "../../helpers/url";
|
||||
import PhotoGallery, { PhotoWithoutSize } from "../photo-gallery";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import useAppSettings from "../../hooks/use-app-settings";
|
||||
import { useBreakpointValue } from "../../providers/breakpoint-provider";
|
||||
|
||||
function useElementBlur(initBlur = false): { style: CSSProperties; onClick: MouseEventHandler } {
|
||||
const [blur, setBlur] = useState(initBlur);
|
||||
|
@ -1,13 +1,14 @@
|
||||
import React from "react";
|
||||
import { Container, Flex, Spacer, useBreakpointValue } from "@chakra-ui/react";
|
||||
import { ErrorBoundary } from "../error-boundary";
|
||||
import { Container, Flex, Spacer } from "@chakra-ui/react";
|
||||
|
||||
import { ErrorBoundary } from "../error-boundary";
|
||||
import { ReloadPrompt } from "../reload-prompt";
|
||||
import DesktopSideNav from "./desktop-side-nav";
|
||||
import MobileBottomNav from "./mobile-bottom-nav";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import accountService from "../../services/account";
|
||||
import GhostToolbar from "./ghost-toolbar";
|
||||
import { useBreakpointValue } from "../../providers/breakpoint-provider";
|
||||
|
||||
export default function Layout({ children }: { children: React.ReactNode }) {
|
||||
const isMobile = useBreakpointValue({ base: true, md: false });
|
||||
|
@ -1,9 +1,10 @@
|
||||
import { ButtonGroup, ButtonGroupProps, Divider, useBreakpointValue } from "@chakra-ui/react";
|
||||
import { ButtonGroup, ButtonGroupProps, Divider } from "@chakra-ui/react";
|
||||
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import ReactionButton from "./reaction-button";
|
||||
import EventReactionButtons from "../../event-reactions";
|
||||
import useEventReactions from "../../../hooks/use-event-reactions";
|
||||
import { useBreakpointValue } from "../../../providers/breakpoint-provider";
|
||||
|
||||
export default function NoteReactions({ event, ...props }: Omit<ButtonGroupProps, "children"> & { event: NostrEvent }) {
|
||||
const reactions = useEventReactions(event.id) ?? [];
|
||||
|
@ -11,7 +11,6 @@ import {
|
||||
IconButton,
|
||||
Link,
|
||||
Text,
|
||||
useBreakpointValue,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { NostrEvent, isATag } from "../../types/nostr-event";
|
||||
@ -44,6 +43,7 @@ import OpenInDrawerButton from "../open-in-drawer-button";
|
||||
import { getSharableEventAddress } from "../../helpers/nip19";
|
||||
import { COMMUNITY_DEFINITION_KIND, getCommunityName } from "../../helpers/nostr/communities";
|
||||
import useReplaceableEvent from "../../hooks/use-replaceable-event";
|
||||
import { useBreakpointValue } from "../../providers/breakpoint-provider";
|
||||
|
||||
export type NoteProps = Omit<CardProps, "children"> & {
|
||||
event: NostrEvent;
|
||||
|
@ -1,11 +1,11 @@
|
||||
import { memo } from "react";
|
||||
import { useBreakpointValue } from "@chakra-ui/react";
|
||||
|
||||
import { getEventRelays } from "../../services/event-relays";
|
||||
import { NostrEvent } from "../../types/nostr-event";
|
||||
import useSubject from "../../hooks/use-subject";
|
||||
import { RelayIconStack, RelayIconStackProps } from "../relay-icon-stack";
|
||||
import { getEventUID } from "../../helpers/nostr/events";
|
||||
import { useBreakpointValue } from "../../providers/breakpoint-provider";
|
||||
|
||||
export type NoteRelaysProps = {
|
||||
event: NostrEvent;
|
||||
|
@ -10,9 +10,9 @@ import {
|
||||
LinkBox,
|
||||
LinkOverlay,
|
||||
Text,
|
||||
useBreakpointValue,
|
||||
} from "@chakra-ui/react";
|
||||
import useOpenGraphData from "../hooks/use-open-graph-data";
|
||||
import { useBreakpointValue } from "../providers/breakpoint-provider";
|
||||
|
||||
export default function OpenGraphCard({ url, ...props }: { url: URL } & Omit<CardProps, "children">) {
|
||||
const { value: data } = useOpenGraphData(url);
|
||||
|
@ -1,16 +1,7 @@
|
||||
import {
|
||||
Alert,
|
||||
AlertDescription,
|
||||
AlertIcon,
|
||||
AlertProps,
|
||||
AlertTitle,
|
||||
Button,
|
||||
Spacer,
|
||||
useBreakpointValue,
|
||||
useModal,
|
||||
} from "@chakra-ui/react";
|
||||
import { Alert, AlertDescription, AlertIcon, AlertProps, AlertTitle, Button, Spacer, useModal } from "@chakra-ui/react";
|
||||
|
||||
import { useExpand } from "../providers/expanded";
|
||||
import { useBreakpointValue } from "../providers/breakpoint-provider";
|
||||
|
||||
export default function SensitiveContentWarning({ description }: { description: string } & AlertProps) {
|
||||
const expand = useExpand();
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { useMemo, useRef } from "react";
|
||||
import { useBreakpointValue } from "@chakra-ui/react";
|
||||
import { Kind } from "nostr-tools";
|
||||
import { Photo } from "react-photo-album";
|
||||
|
||||
@ -14,6 +13,7 @@ import PhotoGallery, { PhotoWithoutSize } from "../../photo-gallery";
|
||||
import { useRegisterIntersectionEntity } from "../../../providers/intersection-observer";
|
||||
import { NostrEvent } from "../../../types/nostr-event";
|
||||
import { getEventUID } from "../../../helpers/nostr/events";
|
||||
import { useBreakpointValue } from "../../../providers/breakpoint-provider";
|
||||
|
||||
function GalleryImage({ event, ...props }: EmbeddedImageProps & { event: NostrEvent }) {
|
||||
const ref = useRef<HTMLImageElement | null>(null);
|
||||
|
@ -1,3 +1,3 @@
|
||||
export function buildAppSelectUrl(identifier: string, select = true) {
|
||||
return `https://nostrapp.link/main/apps/social#${identifier}` + (select ? "?select=true" : "");
|
||||
return `https://nostrapp.link/#${identifier}` + (select ? "?select=true" : "");
|
||||
}
|
||||
|
79
src/providers/breakpoint-provider.tsx
Normal file
79
src/providers/breakpoint-provider.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
import { PropsWithChildren, createContext, useContext } from "react";
|
||||
import { UseBreakpointOptions, useBreakpoint as useBaseBreakpoint, useTheme } from "@chakra-ui/react";
|
||||
import { isObject } from "@chakra-ui/shared-utils";
|
||||
import { arrayToObjectNotation } from "@chakra-ui/breakpoint-utils";
|
||||
import { breakpoints as defaultBreakPoints } from "@chakra-ui/breakpoint-utils";
|
||||
|
||||
// ChakraUIs useBreakpointValue renders twice, once with the fallback value then with the actual breakpoint value
|
||||
// This causes a lot of re-renders and wasted processing.
|
||||
// This provider is designed to solve that by providing the current breakpoint through context
|
||||
|
||||
const BreakpointContext = createContext("base");
|
||||
|
||||
export function useBreakpoint(arg?: string | UseBreakpointOptions) {
|
||||
return useContext(BreakpointContext) ?? (typeof arg === "object" ? arg.fallback : arg);
|
||||
}
|
||||
|
||||
// copied from https://github.com/chakra-ui/chakra-ui/blob/main/packages/components/media-query/src/media-query.utils.ts
|
||||
export function getClosestValue<T = any>(
|
||||
values: Record<string, T>,
|
||||
breakpoint: string,
|
||||
breakpoints = defaultBreakPoints,
|
||||
) {
|
||||
let index = Object.keys(values).indexOf(breakpoint);
|
||||
|
||||
if (index !== -1) {
|
||||
return values[breakpoint];
|
||||
}
|
||||
|
||||
let stopIndex = breakpoints.indexOf(breakpoint);
|
||||
|
||||
while (stopIndex >= 0) {
|
||||
const key = breakpoints[stopIndex];
|
||||
|
||||
if (values.hasOwnProperty(key)) {
|
||||
index = stopIndex;
|
||||
break;
|
||||
}
|
||||
stopIndex -= 1;
|
||||
}
|
||||
|
||||
if (index !== -1) {
|
||||
const key = breakpoints[index];
|
||||
return values[key];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// copied from https://github.com/chakra-ui/chakra-ui/blob/main/packages/components/media-query/src/use-breakpoint-value.ts
|
||||
export function useBreakpointValue<T = any>(
|
||||
values: Partial<Record<string, T>> | Array<T | null>,
|
||||
arg?: UseBreakpointOptions | string,
|
||||
): T | undefined {
|
||||
const opts = isObject(arg) ? arg : { fallback: arg ?? "base" };
|
||||
// NOTE: get the breakpoint from context instead of calling ChakraUIs useBreakpoint hook
|
||||
const breakpoint = useBreakpoint(opts);
|
||||
const theme = useTheme();
|
||||
|
||||
if (!breakpoint) return;
|
||||
|
||||
/**
|
||||
* Get the sorted breakpoint keys from the provided breakpoints
|
||||
*/
|
||||
const breakpoints = Array.from(theme.__breakpoints?.keys || []);
|
||||
|
||||
const obj = Array.isArray(values)
|
||||
? Object.fromEntries<any>(
|
||||
Object.entries(arrayToObjectNotation(values, breakpoints)).map(([key, value]) => [key, value]),
|
||||
)
|
||||
: values;
|
||||
|
||||
return getClosestValue(obj, breakpoint, breakpoints);
|
||||
}
|
||||
|
||||
export default function BreakpointProvider({ children }: PropsWithChildren) {
|
||||
const breakpoint = useBaseBreakpoint();
|
||||
|
||||
return <BreakpointContext.Provider value={breakpoint}>{children}</BreakpointContext.Provider>;
|
||||
}
|
@ -11,6 +11,7 @@ import PostModalProvider from "./post-modal-provider";
|
||||
import { DefaultEmojiProvider, UserEmojiProvider } from "./emoji-provider";
|
||||
import { UserContactsUserDirectoryProvider } from "./user-directory-provider";
|
||||
import MuteModalProvider from "./mute-modal-provider";
|
||||
import BreakpointProvider from "./breakpoint-provider";
|
||||
|
||||
// Top level providers, should be render as close to the root as possible
|
||||
export const GlobalProviders = ({ children }: { children: React.ReactNode }) => {
|
||||
@ -30,22 +31,24 @@ export const GlobalProviders = ({ children }: { children: React.ReactNode }) =>
|
||||
/** Providers that provider functionality to pages (needs to be rendered under a router) */
|
||||
export function PageProviders({ children }: { children: React.ReactNode }) {
|
||||
return (
|
||||
<SigningProvider>
|
||||
<DeleteEventProvider>
|
||||
<MuteModalProvider>
|
||||
<InvoiceModalProvider>
|
||||
<NotificationTimelineProvider>
|
||||
<DefaultEmojiProvider>
|
||||
<UserEmojiProvider>
|
||||
<UserContactsUserDirectoryProvider>
|
||||
<PostModalProvider>{children}</PostModalProvider>
|
||||
</UserContactsUserDirectoryProvider>
|
||||
</UserEmojiProvider>
|
||||
</DefaultEmojiProvider>
|
||||
</NotificationTimelineProvider>
|
||||
</InvoiceModalProvider>
|
||||
</MuteModalProvider>
|
||||
</DeleteEventProvider>
|
||||
</SigningProvider>
|
||||
<BreakpointProvider>
|
||||
<SigningProvider>
|
||||
<DeleteEventProvider>
|
||||
<MuteModalProvider>
|
||||
<InvoiceModalProvider>
|
||||
<NotificationTimelineProvider>
|
||||
<DefaultEmojiProvider>
|
||||
<UserEmojiProvider>
|
||||
<UserContactsUserDirectoryProvider>
|
||||
<PostModalProvider>{children}</PostModalProvider>
|
||||
</UserContactsUserDirectoryProvider>
|
||||
</UserEmojiProvider>
|
||||
</DefaultEmojiProvider>
|
||||
</NotificationTimelineProvider>
|
||||
</InvoiceModalProvider>
|
||||
</MuteModalProvider>
|
||||
</DeleteEventProvider>
|
||||
</SigningProvider>
|
||||
</BreakpointProvider>
|
||||
);
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import {
|
||||
Heading,
|
||||
Spacer,
|
||||
Spinner,
|
||||
useBreakpointValue,
|
||||
useDisclosure,
|
||||
} from "@chakra-ui/react";
|
||||
import { useParams, Navigate, useSearchParams, useNavigate } from "react-router-dom";
|
||||
@ -52,6 +51,7 @@ import StreamZapButton from "../components/stream-zap-button";
|
||||
import StreamGoal from "../components/stream-goal";
|
||||
import StreamShareButton from "../components/stream-share-button";
|
||||
import VerticalPageLayout from "../../../components/vertical-page-layout";
|
||||
import { useBreakpointValue } from "../../../providers/breakpoint-provider";
|
||||
|
||||
function DesktopStreamPage({ stream }: { stream: ParsedStream }) {
|
||||
useAppTitle(stream.title);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { Flex, Heading, IconButton, Spacer, useBreakpointValue } from "@chakra-ui/react";
|
||||
import { Flex, Heading, IconButton, Spacer } from "@chakra-ui/react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
import { EditIcon, GhostIcon } from "../../../components/icons";
|
||||
import { UserAvatar } from "../../../components/user-avatar";
|
||||
import { UserDnsIdentityIcon } from "../../../components/user-dns-identity-icon";
|
||||
@ -9,6 +10,7 @@ import { useUserMetadata } from "../../../hooks/use-user-metadata";
|
||||
import { UserProfileMenu } from "./user-profile-menu";
|
||||
import { UserFollowButton } from "../../../components/user-follow-button";
|
||||
import accountService from "../../../services/account";
|
||||
import { useBreakpointValue } from "../../../providers/breakpoint-provider";
|
||||
|
||||
export default function Header({
|
||||
pubkey,
|
||||
|
@ -99,27 +99,10 @@ const UserView = () => {
|
||||
const userTopRelays = useUserTopRelays(pubkey, relayCount);
|
||||
const relayModal = useDisclosure();
|
||||
|
||||
const articleCount = useUserEventKindCount(pubkey, Kind.Article);
|
||||
const streamCount = useUserEventKindCount(pubkey, STREAM_KIND);
|
||||
const goalCount = useUserEventKindCount(pubkey, GOAL_KIND);
|
||||
|
||||
const filteredTabs = useMemo(
|
||||
() =>
|
||||
tabs.filter((t) => {
|
||||
if (t.path === "streams" && streamCount === 0) return false;
|
||||
if (t.path === "goals" && goalCount === 0) return false;
|
||||
if (t.path === "articles" && articleCount === 0) return false;
|
||||
return true;
|
||||
}),
|
||||
[streamCount, goalCount, articleCount],
|
||||
);
|
||||
|
||||
const matches = useMatches();
|
||||
const lastMatch = matches[matches.length - 1];
|
||||
|
||||
const activeTab = filteredTabs.indexOf(
|
||||
filteredTabs.find((t) => lastMatch.pathname.endsWith(t.path)) ?? filteredTabs[0],
|
||||
);
|
||||
const activeTab = tabs.indexOf(tabs.find((t) => lastMatch.pathname.endsWith(t.path)) ?? tabs[0]);
|
||||
|
||||
const metadata = useUserMetadata(pubkey, userTopRelays, { alwaysRequest: true });
|
||||
|
||||
@ -136,12 +119,12 @@ const UserView = () => {
|
||||
flexGrow="1"
|
||||
isLazy
|
||||
index={activeTab}
|
||||
onChange={(v) => navigate(filteredTabs[v].path, { replace: true })}
|
||||
onChange={(v) => navigate(tabs[v].path, { replace: true })}
|
||||
colorScheme="primary"
|
||||
h="full"
|
||||
>
|
||||
<TabList overflowX="auto" overflowY="hidden" flexShrink={0}>
|
||||
{filteredTabs.map(({ label }) => (
|
||||
{tabs.map(({ label }) => (
|
||||
<Tab key={label} whiteSpace="pre">
|
||||
{label}
|
||||
</Tab>
|
||||
@ -149,7 +132,7 @@ const UserView = () => {
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
{filteredTabs.map(({ label }) => (
|
||||
{tabs.map(({ label }) => (
|
||||
<TabPanel key={label} p={0}>
|
||||
<ErrorBoundary>
|
||||
<Suspense fallback={<Spinner />}>
|
||||
|
@ -1033,7 +1033,7 @@
|
||||
"@chakra-ui/react-context" "2.1.0"
|
||||
"@chakra-ui/shared-utils" "2.0.5"
|
||||
|
||||
"@chakra-ui/breakpoint-utils@2.0.8":
|
||||
"@chakra-ui/breakpoint-utils@2.0.8", "@chakra-ui/breakpoint-utils@^2.0.8":
|
||||
version "2.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@chakra-ui/breakpoint-utils/-/breakpoint-utils-2.0.8.tgz#750d3712668b69f6e8917b45915cee0e08688eed"
|
||||
integrity sha512-Pq32MlEX9fwb5j5xx8s18zJMARNHlQZH2VH1RZgfgRDpp7DcEgtRW5AInfN5CfqdHLO1dGxA7I3MqEuL5JnIsA==
|
||||
@ -1232,7 +1232,7 @@
|
||||
resolved "https://registry.yarnpkg.com/@chakra-ui/live-region/-/live-region-2.1.0.tgz#02b4b1d997075f19a7a9a87187e08c72e82ef0dd"
|
||||
integrity sha512-ZOxFXwtaLIsXjqnszYYrVuswBhnIHHP+XIgK1vC6DePKtyK590Wg+0J0slDwThUAd4MSSIUa/nNX84x1GMphWw==
|
||||
|
||||
"@chakra-ui/media-query@3.3.0":
|
||||
"@chakra-ui/media-query@3.3.0", "@chakra-ui/media-query@^3.3.0":
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@chakra-ui/media-query/-/media-query-3.3.0.tgz#40f9151dedb6a7af9df3be0474b59a799c92c619"
|
||||
integrity sha512-IsTGgFLoICVoPRp9ykOgqmdMotJG0CnPsKvGQeSFOB/dZfIujdVb14TYxDU4+MURXry1MhJ7LzZhv+Ml7cr8/g==
|
||||
@ -1596,6 +1596,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@chakra-ui/shared-utils/-/shared-utils-2.0.5.tgz#cb2b49705e113853647f1822142619570feba081"
|
||||
integrity sha512-4/Wur0FqDov7Y0nCXl7HbHzCg4aq86h+SXdoUeuCMD3dSj7dpsVnStLYhng1vxvlbUnLpdF4oz5Myt3i/a7N3Q==
|
||||
|
||||
"@chakra-ui/shared-utils@^2.0.4":
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@chakra-ui/shared-utils/-/shared-utils-2.0.4.tgz#8661f2b48dd93d04151b10a894a4290c9d9a080c"
|
||||
integrity sha512-JGWr+BBj3PXGZQ2gxbKSD1wYjESbYsZjkCeE2nevyVk4rN3amV1wQzCnBAhsuJktMaZD6KC/lteo9ou9QUDzpA==
|
||||
|
||||
"@chakra-ui/skeleton@2.1.0":
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@chakra-ui/skeleton/-/skeleton-2.1.0.tgz#e3b25dd3afa330029d6d63be0f7cb8d44ad25531"
|
||||
|
Loading…
x
Reference in New Issue
Block a user