Skip to content

Commit

Permalink
fix: set ref on mount (#1251)
Browse files Browse the repository at this point in the history
  • Loading branch information
abvthecity authored Aug 6, 2024
1 parent bfc1eea commit 0f9223a
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 128 deletions.
1 change: 0 additions & 1 deletion packages/ui/app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@
"react-error-boundary": "^4.0.10",
"react-feather": "^2.0.10",
"react-instantsearch": "^7.7.0",
"react-intersection-observer": "^9.4.3",
"react-medium-image-zoom": "^5.1.10",
"react-virtuoso": "^4.7.7",
"rehype-katex": "^7.0.0",
Expand Down
42 changes: 25 additions & 17 deletions packages/ui/app/src/api-page/ApiSectionMarkdownPage.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
import clsx from "clsx";
import { ReactElement, memo } from "react";
import { ReactElement, memo, useRef } from "react";
import { useShouldLazyRender } from "../hooks/useShouldLazyRender";
import { MdxContent } from "../mdx/MdxContent";
import { ResolvedPageMetadata } from "../resolver/types";
import { useApiPageCenterElement } from "./useApiPageCenterElement";

interface ApiSectionMarkdownPageProps {
page: ResolvedPageMetadata;
hideBottomSeparator: boolean;
}

const ApiSectionMarkdownContent = ({ page, hideBottomSeparator }: ApiSectionMarkdownPageProps) => {
const ref = useRef<HTMLDivElement>(null);
useApiPageCenterElement(ref, page.slug);

return (
<div
className={clsx("scroll-mt-content", {
"border-default border-b mb-px": !hideBottomSeparator,
})}
ref={ref}
id={`/${page.slug}`}
>
<MdxContent mdx={page.markdown} />
</div>
);
};

export const ApiSectionMarkdownPage = memo(
({
page,
Expand All @@ -13,28 +35,14 @@ export const ApiSectionMarkdownPage = memo(
page: ResolvedPageMetadata;
hideBottomSeparator: boolean;
}): ReactElement | null => {
const slug = page.slug;

const ref = useApiPageCenterElement({ slug });

// TODO: this is a temporary fix to only SSG the content that is requested by the requested route.
// - webcrawlers will accurately determine the canonical URL (right now every page "returns" the same full-length content)
// - this allows us to render the static page before hydrating, preventing layout-shift caused by the navigation context.
if (useShouldLazyRender(slug)) {
if (useShouldLazyRender(page.slug)) {
return null;
}

return (
<div
className={clsx("scroll-mt-content", {
"border-default border-b mb-px": !hideBottomSeparator,
})}
ref={ref}
id={`/${slug}`}
>
<MdxContent mdx={page.markdown} />
</div>
);
return <ApiSectionMarkdownContent page={page} hideBottomSeparator={hideBottomSeparator} />;
},
);

Expand Down
4 changes: 3 additions & 1 deletion packages/ui/app/src/api-page/artifacts/ApiArtifacts.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DocsV1Read, FernNavigation } from "@fern-api/fdr-sdk";
import { useRef } from "react";
import { API_ARTIFACTS_TITLE } from "../../config";
import { ResolvedWithApiDefinition } from "../../resolver/types";
import { ApiPageMargins } from "../page-margins/ApiPageMargins";
Expand All @@ -21,7 +22,8 @@ export declare namespace ApiArtifacts {
export const ApiArtifacts: React.FC<ApiArtifacts.Props> = ({ apiDefinition, apiArtifacts }) => {
const slug = FernNavigation.utils.slugjoin(apiDefinition?.slug ?? "", "client-libraries");

const ref = useApiPageCenterElement({ slug });
const ref = useRef<HTMLDivElement>(null);
useApiPageCenterElement(ref, slug);

return (
<ApiPageMargins>
Expand Down
4 changes: 0 additions & 4 deletions packages/ui/app/src/api-page/endpoints/Endpoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ import { memo, useEffect } from "react";
import { FERN_STREAM_ATOM, useResolvedPath } from "../../atoms";
import { useShouldLazyRender } from "../../hooks/useShouldLazyRender";
import { ResolvedEndpointDefinition, ResolvedTypeDefinition } from "../../resolver/types";
import { useApiPageCenterElement } from "../useApiPageCenterElement";
import { EndpointContent } from "./EndpointContent";

export declare namespace Endpoint {
Expand Down Expand Up @@ -40,8 +39,6 @@ const UnmemoizedEndpoint: React.FC<Endpoint.Props> = ({
}
}, [endpoint.slug, endpoint.stream, resolvedPath.slug, setStream]);

const ref = useApiPageCenterElement({ slug: endpointSlug });

// TODO: this is a temporary fix to only SSG the content that is requested by the requested route.
// - webcrawlers will accurately determine the canonical URL (right now every page "returns" the same full-length content)
// - this allows us to render the static page before hydrating, preventing layout-shift caused by the navigation context.
Expand All @@ -55,7 +52,6 @@ const UnmemoizedEndpoint: React.FC<Endpoint.Props> = ({
showErrors={showErrors}
endpoint={endpoint}
breadcrumbs={breadcrumbs}
ref={ref}
hideBottomSeparator={isLastInApi}
types={types}
/>
Expand Down
31 changes: 13 additions & 18 deletions packages/ui/app/src/api-page/endpoints/EndpointContent.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import cn from "clsx";
import { useInView } from "framer-motion";
import { atom, useAtom, useAtomValue } from "jotai";
import { selectAtom } from "jotai/utils";
import { isEqual } from "lodash-es";
import dynamic from "next/dynamic";
import { forwardRef, memo, useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react";
import { useInView } from "react-intersection-observer";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useCallbackOne } from "use-memo-one";
import {
ANCHOR_ATOM,
Expand All @@ -28,6 +28,7 @@ import {
import { ApiPageDescription } from "../ApiPageDescription";
import { JsonPropertyPath } from "../examples/JsonPropertyPath";
import { CodeExample, generateCodeExamples } from "../examples/code-example";
import { useApiPageCenterElement } from "../useApiPageCenterElement";
import { EndpointAvailabilityTag } from "./EndpointAvailabilityTag";
import { EndpointContentLeft, convertNameToAnchorPart } from "./EndpointContentLeft";
import { EndpointStreamingEnabledToggle } from "./EndpointStreamingEnabledToggle";
Expand Down Expand Up @@ -78,22 +79,18 @@ function maybeGetErrorStatusCodeOrNameFromAnchor(anchor: string | undefined): nu

const paddingAtom = atom((get) => (get(MOBILE_SIDEBAR_ENABLED_ATOM) ? 0 : 26));

const UnmemoizedEndpointContent = forwardRef<HTMLDivElement, EndpointContent.Props>((props, ref) => {
export const EndpointContent = memo<EndpointContent.Props>((props) => {
const { api, showErrors, endpoint: endpointProp, breadcrumbs, hideBottomSeparator = false, types } = props;
const [isStream, setIsStream] = useAtom(FERN_STREAM_ATOM);
const endpoint = isStream && endpointProp.stream != null ? endpointProp.stream : endpointProp;

const containerRef = useRef<HTMLDivElement>(null);
const ref = useRef<HTMLDivElement>(null);
useApiPageCenterElement(ref, endpoint.slug);

// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
useImperativeHandle(ref, () => containerRef.current!);

const [isInViewport, setIsInViewport] = useState(() => store.get(CURRENT_NODE_ID_ATOM) === endpoint.nodeId);
const { ref: viewportRef } = useInView({
onChange: setIsInViewport,
rootMargin: "100%",
});
useImperativeHandle(viewportRef, () => containerRef.current ?? undefined);
const isInViewport =
useInView(ref, {
margin: "100%",
}) || store.get(CURRENT_NODE_ID_ATOM) === endpoint.nodeId;

const [hoveredRequestPropertyPath, setHoveredRequestPropertyPath] = useState<JsonPropertyPath | undefined>();
const [hoveredResponsePropertyPath, setHoveredResponsePropertyPath] = useState<JsonPropertyPath | undefined>();
Expand Down Expand Up @@ -258,7 +255,7 @@ const UnmemoizedEndpointContent = forwardRef<HTMLDivElement, EndpointContent.Pro
<div
className={"fern-endpoint-content"}
onClick={() => setSelectedError(undefined)}
ref={containerRef}
ref={ref}
id={`/${endpoint.slug}`}
>
<div
Expand Down Expand Up @@ -286,7 +283,7 @@ const UnmemoizedEndpointContent = forwardRef<HTMLDivElement, EndpointContent.Pro
value={isStream}
setValue={setIsStream}
endpointProp={endpointProp}
container={containerRef}
container={ref}
/>
)}
</div>
Expand Down Expand Up @@ -357,6 +354,4 @@ const UnmemoizedEndpointContent = forwardRef<HTMLDivElement, EndpointContent.Pro
);
});

UnmemoizedEndpointContent.displayName = "EndpointContent";

export const EndpointContent = memo(UnmemoizedEndpointContent);
EndpointContent.displayName = "EndpointContent";
5 changes: 4 additions & 1 deletion packages/ui/app/src/api-page/subpackages/ApiSubpackage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { FdrAPI } from "@fern-api/fdr-sdk";
import { useRef } from "react";
import { ResolvedApiDefinitionPackage, ResolvedTypeDefinition } from "../../resolver/types";
import { ApiPackageContents } from "../ApiPackageContents";
import { useApiPageCenterElement } from "../useApiPageCenterElement";
Expand All @@ -24,7 +25,9 @@ export const ApiSubpackage: React.FC<ApiSubpackage.Props> = ({
anchorIdParts,
breadcrumbs,
}) => {
const ref = useApiPageCenterElement({ slug: apiDefinition.slug, skip: true });
const ref = useRef<HTMLDivElement>(null);
useApiPageCenterElement(ref, apiDefinition.slug, true);

return (
<>
<div ref={ref} className="scroll-mt-content" id={`/${apiDefinition.slug}`} />
Expand Down
22 changes: 6 additions & 16 deletions packages/ui/app/src/api-page/useApiPageCenterElement.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import { FernNavigation } from "@fern-api/fdr-sdk";
import { useInView } from "framer-motion";
import { useSetAtom } from "jotai";
import { RefObject, useEffect, useRef } from "react";
import { RefObject, useEffect } from "react";
import { SLUG_ATOM } from "../atoms";

export declare namespace useApiPageCenterElement {
export interface Args {
slug: FernNavigation.Slug;
skip?: boolean;
}
}

export function useApiPageCenterElement({
slug,
skip = false,
}: useApiPageCenterElement.Args): RefObject<HTMLDivElement> {
export function useApiPageCenterElement(
ref: RefObject<HTMLDivElement>,
slug: FernNavigation.Slug,
skip: boolean = false,
): void {
const setSelectedSlug = useSetAtom(SLUG_ATOM);

const ref = useRef<HTMLDivElement>(null);

const isInView = useInView(ref, {
// https://stackoverflow.com/questions/54807535/intersection-observer-api-observe-the-center-of-the-viewport
margin: "-50% 0px",
Expand All @@ -29,6 +21,4 @@ export function useApiPageCenterElement({
setSelectedSlug(slug);
}
}, [isInView, setSelectedSlug, skip, slug]);

return ref;
}
5 changes: 3 additions & 2 deletions packages/ui/app/src/api-page/web-socket/WebSocket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { APIV1Read, FernNavigation } from "@fern-api/fdr-sdk";
import { CopyToClipboardButton, FernScrollArea } from "@fern-ui/components";
import { ArrowDownIcon, ArrowUpIcon } from "@radix-ui/react-icons";
import cn from "clsx";
import { Children, FC, HTMLAttributes, ReactNode, useMemo } from "react";
import { Children, FC, HTMLAttributes, ReactNode, useMemo, useRef } from "react";
import { Wifi } from "react-feather";
import { PlaygroundButton } from "../../api-playground/PlaygroundButton";
import { useNavigationNodes } from "../../atoms";
Expand Down Expand Up @@ -55,7 +55,8 @@ const WebhookContent: FC<WebSocket.Props> = ({ websocket, isLastInApi, types })

const route = `/${websocket.slug}`;

const ref = useApiPageCenterElement({ slug: websocket.slug });
const ref = useRef<HTMLDivElement>(null);
useApiPageCenterElement(ref, websocket.slug);

const publishMessages = useMemo(
() => websocket.messages.filter((message) => message.origin === APIV1Read.WebSocketMessageOrigin.Client),
Expand Down
3 changes: 0 additions & 3 deletions packages/ui/app/src/api-page/webhooks/Webhook.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { useShouldLazyRender } from "../../hooks/useShouldLazyRender";
import { ResolvedTypeDefinition, ResolvedWebhookDefinition } from "../../resolver/types";
import { useApiPageCenterElement } from "../useApiPageCenterElement";
import { WebhookContent } from "./WebhookContent";
import { WebhookContextProvider } from "./webhook-context/WebhookContextProvider";

Expand All @@ -14,7 +13,6 @@ export declare namespace Webhook {
}

export const Webhook: React.FC<Webhook.Props> = ({ webhook, breadcrumbs, isLastInApi, types }) => {
const ref = useApiPageCenterElement({ slug: webhook.slug });
const route = `/${webhook.slug}`;

// TODO: merge this with the Endpoint component
Expand All @@ -27,7 +25,6 @@ export const Webhook: React.FC<Webhook.Props> = ({ webhook, breadcrumbs, isLastI
<WebhookContent
webhook={webhook}
breadcrumbs={breadcrumbs}
ref={ref}
hideBottomSeparator={isLastInApi}
route={route}
types={types}
Expand Down
13 changes: 8 additions & 5 deletions packages/ui/app/src/api-page/webhooks/WebhookContent.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import cn from "clsx";
import dynamic from "next/dynamic";
import { forwardRef, memo, useCallback } from "react";
import { memo, useCallback, useRef } from "react";
import { Breadcrumbs } from "../../components/Breadcrumbs";
import { ResolvedTypeDefinition, ResolvedWebhookDefinition, getParameterDescription } from "../../resolver/types";
import { ApiPageDescription } from "../ApiPageDescription";
import { EndpointParameter } from "../endpoints/EndpointParameter";
import { EndpointSection } from "../endpoints/EndpointSection";
import { JsonPropertyPath } from "../examples/JsonPropertyPath";
import { TypeComponentSeparator } from "../types/TypeComponentSeparator";
import { useApiPageCenterElement } from "../useApiPageCenterElement";
import { WebhookPayloadSection } from "./WebhookPayloadSection";
import { WebhookResponseSection } from "./WebhookResponseSection";
import { useWebhookContext } from "./webhook-context/useWebhookContext";
Expand All @@ -27,8 +28,12 @@ export declare namespace WebhookContent {
}
}

const UnmemoizedWebhookContent = forwardRef<HTMLDivElement, WebhookContent.Props>((props, ref) => {
export const WebhookContent = memo<WebhookContent.Props>((props) => {
const { webhook, breadcrumbs, hideBottomSeparator = false, route, types } = props;

const ref = useRef<HTMLDivElement>(null);
useApiPageCenterElement(ref, webhook.slug);

const { setHoveredPayloadPropertyPath } = useWebhookContext();
const onHoverPayloadProperty = useCallback(
(jsonPropertyPath: JsonPropertyPath, { isHovering }: { isHovering: boolean }) => {
Expand Down Expand Up @@ -131,6 +136,4 @@ const UnmemoizedWebhookContent = forwardRef<HTMLDivElement, WebhookContent.Props
);
});

UnmemoizedWebhookContent.displayName = "WebhookContent";

export const WebhookContent = memo(UnmemoizedWebhookContent);
WebhookContent.displayName = "WebhookContent";
Loading

0 comments on commit 0f9223a

Please sign in to comment.