From a49c34e733bacc55e8977c4af081eb18aee80971 Mon Sep 17 00:00:00 2001 From: Hariom Balhara Date: Thu, 10 Aug 2023 04:24:51 +0530 Subject: [PATCH] perf: Avoid unmounting of Shell on navigation and thus reduce number of paints (#10646) Co-authored-by: zomars --- .../components/apps/layouts/AppsLayout.tsx | 9 +- apps/web/lib/hooks/useIsBookingPage.ts | 2 +- apps/web/pages/_app.tsx | 2 + apps/web/pages/apps/index.tsx | 2 + apps/web/pages/availability/index.tsx | 9 +- apps/web/pages/bookings/[status].tsx | 188 +++++++++++------- apps/web/pages/event-types/index.tsx | 27 +-- apps/web/pages/insights/index.tsx | 8 +- apps/web/pages/teams/index.tsx | 9 +- .../debugging/components/RenderCounter.tsx | 17 ++ packages/debugging/index.tsx | 1 + packages/debugging/package.json | 7 + packages/debugging/tsconfig.json | 15 ++ packages/features/MainLayout.tsx | 17 ++ .../bookings/layout/BookingLayout.tsx | 51 ----- .../features/ee/workflows/pages/index.tsx | 9 +- yarn.lock | 6 + 17 files changed, 221 insertions(+), 158 deletions(-) create mode 100644 packages/debugging/components/RenderCounter.tsx create mode 100644 packages/debugging/index.tsx create mode 100644 packages/debugging/package.json create mode 100644 packages/debugging/tsconfig.json create mode 100644 packages/features/MainLayout.tsx delete mode 100644 packages/features/bookings/layout/BookingLayout.tsx diff --git a/apps/web/components/apps/layouts/AppsLayout.tsx b/apps/web/components/apps/layouts/AppsLayout.tsx index 56d64dd7b9..90e590eade 100644 --- a/apps/web/components/apps/layouts/AppsLayout.tsx +++ b/apps/web/components/apps/layouts/AppsLayout.tsx @@ -3,7 +3,7 @@ import { useRouter } from "next/navigation"; import type { ComponentProps } from "react"; import React from "react"; -import Shell from "@calcom/features/shell/Shell"; +import { ShellMain } from "@calcom/features/shell/Shell"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { EmptyScreen } from "@calcom/ui"; import { AlertCircle } from "@calcom/ui/components/icon"; @@ -12,7 +12,7 @@ type AppsLayoutProps = { children: React.ReactNode; actions?: (className?: string) => JSX.Element; emptyStore?: boolean; -} & Omit, "actions">; +} & Omit, "actions">; export default function AppsLayout({ children, actions, emptyStore, ...rest }: AppsLayoutProps) { const { t } = useLocale(); @@ -22,7 +22,7 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A if (session.status === "loading") return <>; return ( - +
{emptyStore ? ( @@ -38,7 +38,6 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A )}
-
+ ); } -export const getLayout = (page: React.ReactElement) => {page}; diff --git a/apps/web/lib/hooks/useIsBookingPage.ts b/apps/web/lib/hooks/useIsBookingPage.ts index 9e975171e4..1e231e3f40 100644 --- a/apps/web/lib/hooks/useIsBookingPage.ts +++ b/apps/web/lib/hooks/useIsBookingPage.ts @@ -2,7 +2,7 @@ import { usePathname, useSearchParams } from "next/navigation"; export default function useIsBookingPage() { const pathname = usePathname(); - const isBookingPage = ["/booking", "/cancel", "/reschedule"].some((route) => pathname?.startsWith(route)); + const isBookingPage = ["/booking/", "/cancel", "/reschedule"].some((route) => pathname?.startsWith(route)); const searchParams = useSearchParams(); const userParam = searchParams.get("user"); diff --git a/apps/web/pages/_app.tsx b/apps/web/pages/_app.tsx index c5690123d1..a42968e2d2 100644 --- a/apps/web/pages/_app.tsx +++ b/apps/web/pages/_app.tsx @@ -1,3 +1,5 @@ +import React from "react"; + import { trpc } from "@calcom/trpc/react"; import type { AppProps } from "@lib/app-providers"; diff --git a/apps/web/pages/apps/index.tsx b/apps/web/pages/apps/index.tsx index f5c24cbfee..677ff7963f 100644 --- a/apps/web/pages/apps/index.tsx +++ b/apps/web/pages/apps/index.tsx @@ -3,6 +3,7 @@ import type { ChangeEventHandler } from "react"; import { useState } from "react"; import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry"; +import { getLayout } from "@calcom/features/MainLayout"; import { getServerSession } from "@calcom/features/auth/lib/getServerSession"; import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams"; import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams"; @@ -94,6 +95,7 @@ export default function Apps({ } Apps.PageWrapper = PageWrapper; +Apps.getLayout = getLayout; export const getServerSideProps = async (context: GetServerSidePropsContext) => { const { req, res } = context; diff --git a/apps/web/pages/availability/index.tsx b/apps/web/pages/availability/index.tsx index 7a31265697..172f2fa14f 100644 --- a/apps/web/pages/availability/index.tsx +++ b/apps/web/pages/availability/index.tsx @@ -1,8 +1,9 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import { useRouter } from "next/navigation"; +import { getLayout } from "@calcom/features/MainLayout"; import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules"; -import Shell from "@calcom/features/shell/Shell"; +import { ShellMain } from "@calcom/features/shell/Shell"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { HttpError } from "@calcom/lib/http-error"; import type { RouterOutputs } from "@calcom/trpc/react"; @@ -130,15 +131,17 @@ export default function AvailabilityPage() { const { t } = useLocale(); return (
- }> } customLoader={} /> - +
); } +AvailabilityPage.getLayout = getLayout; + AvailabilityPage.PageWrapper = PageWrapper; diff --git a/apps/web/pages/bookings/[status].tsx b/apps/web/pages/bookings/[status].tsx index 1f04434229..bcb05e2cfd 100644 --- a/apps/web/pages/bookings/[status].tsx +++ b/apps/web/pages/bookings/[status].tsx @@ -1,16 +1,21 @@ import { useAutoAnimate } from "@formkit/auto-animate/react"; import type { GetStaticPaths, GetStaticProps } from "next"; import { Fragment } from "react"; +import React from "react"; import { z } from "zod"; import { WipeMyCalActionButton } from "@calcom/app-store/wipemycalother/components"; -import BookingLayout from "@calcom/features/bookings/layout/BookingLayout"; +import { getLayout } from "@calcom/features/MainLayout"; +import { FiltersContainer } from "@calcom/features/bookings/components/FiltersContainer"; import type { filterQuerySchema } from "@calcom/features/bookings/lib/useFilterQuery"; import { useFilterQuery } from "@calcom/features/bookings/lib/useFilterQuery"; +import { ShellMain } from "@calcom/features/shell/Shell"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; +import { HorizontalTabs } from "@calcom/ui"; +import type { VerticalTabItemProps, HorizontalTabItemProps } from "@calcom/ui"; import { Alert, Button, EmptyScreen } from "@calcom/ui"; import { Calendar } from "@calcom/ui/components/icon"; @@ -32,6 +37,28 @@ type RecurringInfo = { bookings: { [key: string]: Date[] }; }; +const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [ + { + name: "upcoming", + href: "/bookings/upcoming", + }, + { + name: "unconfirmed", + href: "/bookings/unconfirmed", + }, + { + name: "recurring", + href: "/bookings/recurring", + }, + { + name: "past", + href: "/bookings/past", + }, + { + name: "cancelled", + href: "/bookings/cancelled", + }, +]; const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const; const descriptionByStatus: Record, string> = { @@ -112,90 +139,101 @@ export default function Bookings() { const [animationParentRef] = useAutoAnimate(); return ( - -
- {query.status === "error" && ( - - )} - {(query.status === "loading" || query.isPaused) && } - {query.status === "success" && !isEmpty && ( - <> - {!!bookingsToday.length && status === "upcoming" && ( -
- -

{t("today")}

-
- - - - {bookingsToday.map((booking: BookingOutput) => ( - + +
+
+ +
+ +
+
+
+
+ {query.status === "error" && ( + + )} + {(query.status === "loading" || query.isPaused) && } + {query.status === "success" && !isEmpty && ( + <> + {!!bookingsToday.length && status === "upcoming" && ( +
+ +

{t("today")}

+
+
+ + + {bookingsToday.map((booking: BookingOutput) => ( + + ))} + + +
+
+
+ )} +
+
+ + + {query.data.pages.map((page, index) => ( + + {page.bookings.filter(filterBookings).map((booking: BookingOutput) => { + const recurringInfo = page.recurringInfo.find( + (info) => info.recurringEventId === booking.recurringEventId + ); + return ( + + ); + })} + ))} - - -
+ + +
+
+ +
+ + )} + {query.status === "success" && isEmpty && ( +
+
)} -
-
- - - {query.data.pages.map((page, index) => ( - - {page.bookings.filter(filterBookings).map((booking: BookingOutput) => { - const recurringInfo = page.recurringInfo.find( - (info) => info.recurringEventId === booking.recurringEventId - ); - return ( - - ); - })} - - ))} - -
-
-
- -
-
- - )} - {query.status === "success" && isEmpty && ( -
-
- )} +
-
+ ); } Bookings.PageWrapper = PageWrapper; +Bookings.getLayout = getLayout; export const getStaticProps: GetStaticProps = async (ctx) => { const params = querySchema.safeParse(ctx.params); diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index 6b6bad7450..01fee72116 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -7,6 +7,7 @@ import type { FC } from "react"; import { memo, useEffect, useState } from "react"; import { z } from "zod"; +import { getLayout } from "@calcom/features/MainLayout"; import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider"; import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom"; import { EventTypeEmbedButton, EventTypeEmbedDialog } from "@calcom/features/embed/EventTypeEmbed"; @@ -15,7 +16,7 @@ import CreateEventTypeDialog from "@calcom/features/eventtypes/components/Create import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog"; import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter"; import { getTeamsFiltersFromQuery } from "@calcom/features/filters/lib/getTeamsFiltersFromQuery"; -import Shell from "@calcom/features/shell/Shell"; +import { ShellMain } from "@calcom/features/shell/Shell"; import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants"; import { useBookerUrl } from "@calcom/lib/hooks/useBookerUrl"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -932,25 +933,25 @@ const EventTypesPage = () => { }, []); return ( -
+ } + beforeCTAactions={} + CTA={}> - } - beforeCTAactions={} - CTA={}> -
- -
+
+ ); }; +EventTypesPage.getLayout = getLayout; + EventTypesPage.PageWrapper = PageWrapper; export default EventTypesPage; diff --git a/apps/web/pages/insights/index.tsx b/apps/web/pages/insights/index.tsx index 49c6f44c72..ecb7cdbf0b 100644 --- a/apps/web/pages/insights/index.tsx +++ b/apps/web/pages/insights/index.tsx @@ -1,3 +1,4 @@ +import { getLayout } from "@calcom/features/MainLayout"; import { getFeatureFlagMap } from "@calcom/features/flags/server/utils"; import { AverageEventDurationChart, @@ -9,7 +10,7 @@ import { } from "@calcom/features/insights/components"; import { FiltersProvider } from "@calcom/features/insights/context/FiltersProvider"; import { Filters } from "@calcom/features/insights/filters"; -import Shell from "@calcom/features/shell/Shell"; +import { ShellMain } from "@calcom/features/shell/Shell"; import { UpgradeTip } from "@calcom/features/tips"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -56,7 +57,7 @@ export default function InsightsPage() { return (
- + )} - +
); } InsightsPage.PageWrapper = PageWrapper; +InsightsPage.getLayout = getLayout; // If feature flag is disabled, return not found on getServerSideProps export const getServerSideProps = async () => { diff --git a/apps/web/pages/teams/index.tsx b/apps/web/pages/teams/index.tsx index 846698cb8d..392c54f59e 100644 --- a/apps/web/pages/teams/index.tsx +++ b/apps/web/pages/teams/index.tsx @@ -1,7 +1,8 @@ import type { GetServerSidePropsContext } from "next"; +import { getLayout } from "@calcom/features/MainLayout"; import { TeamsListing } from "@calcom/features/ee/teams/components"; -import Shell from "@calcom/features/shell/Shell"; +import { ShellMain } from "@calcom/features/shell/Shell"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import { trpc } from "@calcom/trpc/react"; @@ -17,7 +18,7 @@ function Teams() { const [user] = trpc.viewer.me.useSuspenseQuery(); return ( - - + ); } @@ -46,5 +47,5 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) => Teams.requiresLicense = false; Teams.PageWrapper = PageWrapper; - +Teams.getLayout = getLayout; export default Teams; diff --git a/packages/debugging/components/RenderCounter.tsx b/packages/debugging/components/RenderCounter.tsx new file mode 100644 index 0000000000..d6bb7cdbaa --- /dev/null +++ b/packages/debugging/components/RenderCounter.tsx @@ -0,0 +1,17 @@ +import { useRef } from "react"; + +/** + * Updates in document the number of times a component has been rendered. Helps in 2 ways. Using it doesn't cause any additional renders. + * - Did the component render when it shouldn't have? + * - Did the component reset its state when it shouldn't have? + */ +export const RenderCounter = ({ label }: { label: string }) => { + const counterRef = useRef(0); + counterRef.current++; + return ( + + {label}: + {counterRef.current} + + ); +}; diff --git a/packages/debugging/index.tsx b/packages/debugging/index.tsx new file mode 100644 index 0000000000..fd49c57f9c --- /dev/null +++ b/packages/debugging/index.tsx @@ -0,0 +1 @@ +export { RenderCounter } from "./components/RenderCounter"; diff --git a/packages/debugging/package.json b/packages/debugging/package.json new file mode 100644 index 0000000000..b28ba6534d --- /dev/null +++ b/packages/debugging/package.json @@ -0,0 +1,7 @@ +{ + "name": "@calcom/debugging", + "description": "Debugging utilities", + "private": true, + "version": "1.0.0", + "main": "./index.ts" +} diff --git a/packages/debugging/tsconfig.json b/packages/debugging/tsconfig.json new file mode 100644 index 0000000000..47cd4fedc9 --- /dev/null +++ b/packages/debugging/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "@calcom/tsconfig/react-library.json", + "compilerOptions": { + "resolveJsonModule": true + }, + "include": [ + "../../apps/web/next-env.d.ts", + "../types/*.d.ts", + "../types/next-auth.d.ts", + "**/*.ts", + "**/*.tsx" + ], + "exclude": ["dist", "build", "node_modules"] + } + \ No newline at end of file diff --git a/packages/features/MainLayout.tsx b/packages/features/MainLayout.tsx new file mode 100644 index 0000000000..03a739c875 --- /dev/null +++ b/packages/features/MainLayout.tsx @@ -0,0 +1,17 @@ +import type { ComponentProps } from "react"; +import React from "react"; + +import Shell from "@calcom/features/shell/Shell"; + +export default function MainLayout({ + children, + ...rest +}: { children: React.ReactNode } & ComponentProps) { + return ( + + {children} + + ); +} + +export const getLayout = (page: React.ReactElement) => {page}; diff --git a/packages/features/bookings/layout/BookingLayout.tsx b/packages/features/bookings/layout/BookingLayout.tsx deleted file mode 100644 index 5081d69da7..0000000000 --- a/packages/features/bookings/layout/BookingLayout.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import type { ComponentProps } from "react"; -import React from "react"; - -import Shell from "@calcom/features/shell/Shell"; -import { HorizontalTabs } from "@calcom/ui"; -import type { VerticalTabItemProps, HorizontalTabItemProps } from "@calcom/ui"; - -import { FiltersContainer } from "../components/FiltersContainer"; - -const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [ - { - name: "upcoming", - href: "/bookings/upcoming", - }, - { - name: "unconfirmed", - href: "/bookings/unconfirmed", - }, - { - name: "recurring", - href: "/bookings/recurring", - }, - { - name: "past", - href: "/bookings/past", - }, - { - name: "cancelled", - href: "/bookings/cancelled", - }, -]; - -export default function BookingLayout({ - children, - ...rest -}: { children: React.ReactNode } & ComponentProps) { - return ( - -
-
- -
- -
-
-
{children}
-
-
- ); -} -export const getLayout = (page: React.ReactElement) => {page}; diff --git a/packages/features/ee/workflows/pages/index.tsx b/packages/features/ee/workflows/pages/index.tsx index 73a084a1eb..7c72c114f8 100644 --- a/packages/features/ee/workflows/pages/index.tsx +++ b/packages/features/ee/workflows/pages/index.tsx @@ -3,7 +3,8 @@ import { useRouter } from "next/navigation"; import type { Dispatch, SetStateAction } from "react"; import { useState } from "react"; -import Shell from "@calcom/features/shell/Shell"; +import { getLayout } from "@calcom/features/MainLayout"; +import { ShellMain } from "@calcom/features/shell/Shell"; import { classNames } from "@calcom/lib"; import { WEBAPP_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; @@ -49,7 +50,7 @@ function WorkflowsPage() { }); return ( - - + ); } @@ -221,4 +222,6 @@ const Filter = (props: { ); }; +WorkflowsPage.getLayout = getLayout; + export default WorkflowsPage; diff --git a/yarn.lock b/yarn.lock index b999117991..8204c701f2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4026,6 +4026,12 @@ __metadata: languageName: unknown linkType: soft +"@calcom/debugging@workspace:packages/debugging": + version: 0.0.0-use.local + resolution: "@calcom/debugging@workspace:packages/debugging" + languageName: unknown + linkType: soft + "@calcom/discord@workspace:packages/app-store/discord": version: 0.0.0-use.local resolution: "@calcom/discord@workspace:packages/app-store/discord"