Skip to content
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

perf: Reduce api reference re-renders while scrolling #595

Merged
merged 9 commits into from
Apr 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
19 changes: 13 additions & 6 deletions packages/ui/app/src/api-page/ApiPackageContents.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { FdrAPI } from "@fern-api/fdr-sdk";
import { EMPTY_ARRAY } from "@fern-ui/core-utils";
import { memo, useMemo } from "react";
import { FernErrorBoundary } from "../components/FernErrorBoundary";
import {
isResolvedSubpackage,
ResolvedPackageItem,
ResolvedTypeDefinition,
ResolvedWithApiDefinition,
isResolvedSubpackage,
} from "../util/resolver";
import { Endpoint } from "./endpoints/Endpoint";
import { ApiSubpackage } from "./subpackages/ApiSubpackage";
Expand All @@ -18,23 +20,26 @@ export declare namespace ApiPackageContents {
showErrors: boolean;
apiDefinition: ResolvedWithApiDefinition;
isLastInParentPackage: boolean;
anchorIdParts: string[];
breadcrumbs?: string[];
anchorIdParts: readonly string[];
breadcrumbs?: readonly string[];
}
}

export const ApiPackageContents: React.FC<ApiPackageContents.Props> = ({
const UnmemoizedApiPackageContents: React.FC<ApiPackageContents.Props> = ({
api,
types,
showErrors,
apiDefinition,
isLastInParentPackage,
anchorIdParts,
breadcrumbs = [],
breadcrumbs = EMPTY_ARRAY,
}) => {
const { items } = apiDefinition;
const subpackageTitle = isResolvedSubpackage(apiDefinition) ? apiDefinition.title : undefined;
const currentBreadcrumbs = subpackageTitle != null ? [...breadcrumbs, subpackageTitle] : breadcrumbs;
const currentBreadcrumbs = useMemo(
() => (subpackageTitle != null ? [...breadcrumbs, subpackageTitle] : breadcrumbs),
[breadcrumbs, subpackageTitle],
);

return (
<>
Expand Down Expand Up @@ -85,3 +90,5 @@ export const ApiPackageContents: React.FC<ApiPackageContents.Props> = ({
</>
);
};

export const ApiPackageContents = memo(UnmemoizedApiPackageContents);
3 changes: 2 additions & 1 deletion packages/ui/app/src/api-page/ApiPage.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DocsV1Read } from "@fern-api/fdr-sdk";
import { EMPTY_ARRAY } from "@fern-ui/core-utils";
import { useSetAtom } from "jotai";
import { useEffect } from "react";
import { useFeatureFlags } from "../contexts/FeatureFlagContext";
Expand Down Expand Up @@ -39,7 +40,7 @@ export const ApiPage: React.FC<ApiPage.Props> = ({ initialApi, artifacts, showEr
showErrors={showErrors}
apiDefinition={initialApi}
isLastInParentPackage={true}
anchorIdParts={[]}
anchorIdParts={EMPTY_ARRAY}
/>

{isApiScrollingDisabled && (
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-page/Breadcrumbs.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChevronRightIcon } from "@radix-ui/react-icons";
import { Fragment, ReactElement } from "react";

export function Breadcrumbs({ breadcrumbs }: { breadcrumbs: string[] }): ReactElement | null {
export function Breadcrumbs({ breadcrumbs }: { breadcrumbs: readonly string[] }): ReactElement | null {
if (breadcrumbs.length === 0) {
return null;
}
Expand Down
2 changes: 1 addition & 1 deletion packages/ui/app/src/api-page/endpoints/Endpoint.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export declare namespace Endpoint {
api: string;
showErrors: boolean;
endpoint: ResolvedEndpointDefinition;
breadcrumbs: string[];
breadcrumbs: readonly string[];
isLastInApi: boolean;
types: Record<string, ResolvedTypeDefinition>;
}
Expand Down
12 changes: 7 additions & 5 deletions packages/ui/app/src/api-page/endpoints/EndpointContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,16 @@ import React, { useCallback, useEffect, useMemo, useState } from "react";
import { useInView } from "react-intersection-observer";
import { withStream } from "../../commons/withStream";
import { useDocsContext } from "../../contexts/docs-context/useDocsContext";
import { useViewportContext } from "../../contexts/viewport-context/useViewportContext";
import { useLayoutBreakpoint } from "../../contexts/layout-breakpoint/useLayoutBreakpoint";
import { useViewportSize } from "../../hooks/useViewportSize";
import { FERN_LANGUAGE_ATOM } from "../../sidebar/atom";
import { ResolvedEndpointDefinition, ResolvedError, ResolvedTypeDefinition } from "../../util/resolver";
import { ApiPageDescription } from "../ApiPageDescription";
import { Breadcrumbs } from "../Breadcrumbs";
import { CodeExample, generateCodeExamples } from "../examples/code-example";
import { JsonPropertyPath } from "../examples/JsonPropertyPath";
import { CodeExample, generateCodeExamples } from "../examples/code-example";
import { EndpointAvailabilityTag } from "./EndpointAvailabilityTag";
import { convertNameToAnchorPart, EndpointContentLeft } from "./EndpointContentLeft";
import { EndpointContentLeft, convertNameToAnchorPart } from "./EndpointContentLeft";
import { EndpointUrlWithOverflow } from "./EndpointUrlWithOverflow";

const EndpointContentCodeSnippets = dynamic(
Expand All @@ -28,7 +29,7 @@ export declare namespace EndpointContent {
api: string;
showErrors: boolean;
endpoint: ResolvedEndpointDefinition;
breadcrumbs: string[];
breadcrumbs: readonly string[];
hideBottomSeparator?: boolean;
setContainerRef: (ref: HTMLElement | null) => void;
isInViewport: boolean;
Expand Down Expand Up @@ -75,7 +76,8 @@ export const EndpointContent: React.FC<EndpointContent.Props> = ({
}) => {
const router = useRouter();
const { layout } = useDocsContext();
const { layoutBreakpoint, viewportSize } = useViewportContext();
const layoutBreakpoint = useLayoutBreakpoint();
const viewportSize = useViewportSize();
const [isInViewport, setIsInViewport] = useState(initiallyInViewport);
const { ref: containerRef } = useInView({
onChange: setIsInViewport,
Expand Down
44 changes: 24 additions & 20 deletions packages/ui/app/src/api-page/endpoints/EndpointContentLeft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import { camelCase, sortBy, upperFirst } from "lodash-es";
import { memo } from "react";
import { FernCard } from "../../components/FernCard";
import {
dereferenceObjectProperties,
ResolvedEndpointDefinition,
ResolvedError,
ResolvedHttpRequestBodyShape,
ResolvedHttpResponseBodyShape,
ResolvedTypeDefinition,
dereferenceObjectProperties,
} from "../../util/resolver";
import { JsonPropertyPath } from "../examples/JsonPropertyPath";
import { TypeComponentSeparator } from "../types/TypeComponentSeparator";
Expand Down Expand Up @@ -38,6 +38,15 @@ export declare namespace EndpointContentLeft {
}
}

const REQUEST = ["request"];
const RESPONSE = ["response"];
const REQUEST_PATH = ["request", "path"];
const REQUEST_QUERY = ["request", "query"];
const REQUEST_HEADER = ["request", "header"];
const REQUEST_BODY = ["request", "body"];
const RESPONSE_BODY = ["response", "body"];
const RESPONSE_ERROR = ["response", "error"];

const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
endpoint,
showErrors,
Expand All @@ -60,7 +69,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{endpoint.pathParameters.length > 0 && (
<EndpointSection
title="Path parameters"
anchorIdParts={["request", "path"]}
anchorIdParts={REQUEST_PATH}
route={"/" + endpoint.slug.join("/")}
>
<div>
Expand All @@ -70,7 +79,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointParameter
name={parameter.key}
shape={parameter.valueShape}
anchorIdParts={["request", "path", parameter.key]}
anchorIdParts={[...REQUEST_PATH, parameter.key]}
route={"/" + endpoint.slug.join("/")}
description={parameter.description}
availability={parameter.availability}
Expand All @@ -82,19 +91,15 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
</EndpointSection>
)}
{headers.length > 0 && (
<EndpointSection
title="Headers"
anchorIdParts={["request", "header"]}
route={"/" + endpoint.slug.join("/")}
>
<EndpointSection title="Headers" anchorIdParts={REQUEST_HEADER} route={"/" + endpoint.slug.join("/")}>
<div>
{headers.map((parameter) => (
<div key={parameter.key}>
<TypeComponentSeparator />
<EndpointParameter
name={parameter.key}
shape={parameter.valueShape}
anchorIdParts={["request", "header", parameter.key]}
anchorIdParts={[...REQUEST_HEADER, parameter.key]}
route={"/" + endpoint.slug.join("/")}
description={parameter.description}
availability={parameter.availability}
Expand All @@ -108,7 +113,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{endpoint.queryParameters.length > 0 && (
<EndpointSection
title="Query parameters"
anchorIdParts={["request", "query"]}
anchorIdParts={REQUEST_QUERY}
route={"/" + endpoint.slug.join("/")}
>
<div>
Expand All @@ -118,7 +123,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointParameter
name={parameter.key}
shape={parameter.valueShape}
anchorIdParts={["request", "query", parameter.key]}
anchorIdParts={[...REQUEST_QUERY, parameter.key]}
route={"/" + endpoint.slug.join("/")}
description={parameter.description}
availability={parameter.availability}
Expand Down Expand Up @@ -158,7 +163,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointSection
key={requestBody.contentType}
title="Request"
anchorIdParts={["request"]}
anchorIdParts={REQUEST}
route={"/" + endpoint.slug.join("/")}
expandAll={requestExpandAll.setTrue}
collapseAll={requestExpandAll.setFalse}
Expand All @@ -167,7 +172,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointRequestSection
requestBody={requestBody}
onHoverProperty={onHoverRequestProperty}
anchorIdParts={["request", "body"]}
anchorIdParts={REQUEST_BODY}
route={"/" + endpoint.slug.join("/")}
defaultExpandAll={requestExpandAll.value}
types={types}
Expand All @@ -182,7 +187,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointSection
key={endpoint.requestBody[0].contentType}
title="Request"
anchorIdParts={["request"]}
anchorIdParts={REQUEST}
route={"/" + endpoint.slug.join("/")}
expandAll={requestExpandAll.setTrue}
collapseAll={requestExpandAll.setFalse}
Expand All @@ -191,7 +196,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointRequestSection
requestBody={endpoint.requestBody[0]}
onHoverProperty={onHoverRequestProperty}
anchorIdParts={["request", "body"]}
anchorIdParts={REQUEST_BODY}
route={"/" + endpoint.slug.join("/")}
defaultExpandAll={requestExpandAll.value}
types={types}
Expand All @@ -201,7 +206,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{endpoint.responseBody != null && (
<EndpointSection
title="Response"
anchorIdParts={["response"]}
anchorIdParts={RESPONSE}
route={"/" + endpoint.slug.join("/")}
expandAll={responseExpandAll.setTrue}
collapseAll={responseExpandAll.setFalse}
Expand All @@ -210,7 +215,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
<EndpointResponseSection
responseBody={endpoint.responseBody}
onHoverProperty={onHoverResponseProperty}
anchorIdParts={["response", "body"]}
anchorIdParts={RESPONSE_BODY}
route={"/" + endpoint.slug.join("/")}
defaultExpandAll={responseExpandAll.value}
types={types}
Expand All @@ -220,7 +225,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
{showErrors && endpoint.errors.length > 0 && (
<EndpointSection
title="Errors"
anchorIdParts={["response", "error"]}
anchorIdParts={RESPONSE_ERROR}
route={"/" + endpoint.slug.join("/")}
expandAll={errorExpandAll.setTrue}
collapseAll={errorExpandAll.setFalse}
Expand All @@ -245,8 +250,7 @@ const UnmemoizedEndpointContentLeft: React.FC<EndpointContentLeft.Props> = ({
}}
onHoverProperty={onHoverResponseProperty}
anchorIdParts={[
"response",
"error",
...RESPONSE_ERROR,
`${convertNameToAnchorPart(error.name) ?? error.statusCode}`,
]}
route={"/" + endpoint.slug.join("/")}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/app/src/api-page/endpoints/EndpointError.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { APIV1Read } from "@fern-api/fdr-sdk";
import { titleCase, visitDiscriminatedUnion } from "@fern-ui/core-utils";
import cn from "clsx";
import { memo, MouseEventHandler } from "react";
import { MouseEventHandler, memo } from "react";
import { FernCollapse } from "../../components/FernCollapse";
import {
dereferenceObjectProperties,
ResolvedError,
ResolvedTypeDefinition,
ResolvedTypeShape,
dereferenceObjectProperties,
unwrapReference,
} from "../../util/resolver";
import { type JsonPropertyPath } from "../examples/JsonPropertyPath";
Expand All @@ -24,7 +24,7 @@ export declare namespace EndpointError {
isSelected: boolean;
onClick: MouseEventHandler<HTMLButtonElement>;
onHoverProperty?: (path: JsonPropertyPath, opts: { isHovering: boolean }) => void;
anchorIdParts: string[];
anchorIdParts: readonly string[];
route: string;
availability: APIV1Read.Availability | null | undefined;
defaultExpandAll?: boolean;
Expand Down
Loading
Loading