chore: fixing some initial re-renders (#10185)
Co-authored-by: Keith Williams <keithwillcode@gmail.com>bug/json-input^2
parent
2fd9ef2733
commit
628a29e89c
|
@ -1,4 +1,3 @@
|
|||
import { isEmpty } from "lodash";
|
||||
import { useTranslation } from "next-i18next";
|
||||
import { useEffect } from "react";
|
||||
|
||||
|
@ -20,13 +19,13 @@ export function useViewerI18n() {
|
|||
/**
|
||||
* Auto-switches locale client-side to the logged in user's preference
|
||||
*/
|
||||
const I18nLanguageHandler = (): null => {
|
||||
const useI18nLanguageHandler = () => {
|
||||
const { i18n } = useTranslation("common");
|
||||
const locale = useViewerI18n().data?.locale || i18n.language;
|
||||
|
||||
useEffect(() => {
|
||||
// bail early when i18n = {}
|
||||
if (isEmpty(i18n)) return;
|
||||
if (Object.keys(i18n).length === 0) return;
|
||||
// if locale is ready and the i18n.language does != locale - changeLanguage
|
||||
if (locale && i18n.language !== locale) {
|
||||
i18n.changeLanguage(locale);
|
||||
|
@ -35,8 +34,6 @@ const I18nLanguageHandler = (): null => {
|
|||
document.dir = i18n.dir();
|
||||
document.documentElement.setAttribute("lang", locale);
|
||||
}, [locale, i18n]);
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export default I18nLanguageHandler;
|
||||
export default useI18nLanguageHandler;
|
||||
|
|
|
@ -13,7 +13,7 @@ import type { AppProps } from "@lib/app-providers";
|
|||
import AppProviders from "@lib/app-providers";
|
||||
import { seoConfig } from "@lib/config/next-seo.config";
|
||||
|
||||
import I18nLanguageHandler from "@components/I18nLanguageHandler";
|
||||
import useI18nLanguageHandler from "@components/I18nLanguageHandler";
|
||||
|
||||
export interface CalPageWrapper {
|
||||
(props?: AppProps): JSX.Element;
|
||||
|
@ -29,6 +29,7 @@ const calFont = localFont({
|
|||
});
|
||||
|
||||
function PageWrapper(props: AppProps) {
|
||||
useI18nLanguageHandler();
|
||||
const { Component, pageProps, err, router } = props;
|
||||
let pageStatus = "200";
|
||||
|
||||
|
@ -72,7 +73,6 @@ function PageWrapper(props: AppProps) {
|
|||
}
|
||||
{...seoConfig.defaultNextSeo}
|
||||
/>
|
||||
<I18nLanguageHandler />
|
||||
<Script
|
||||
nonce={nonce}
|
||||
id="page-status"
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
|
||||
import { useState } from "react";
|
||||
import { useState, memo } from "react";
|
||||
import { Controller, useFormContext } from "react-hook-form";
|
||||
import type { OptionProps, SingleValueProps } from "react-select";
|
||||
import { components } from "react-select";
|
||||
|
@ -71,86 +71,91 @@ const format = (date: Date, hour12: boolean) =>
|
|||
hourCycle: hour12 ? "h12" : "h24",
|
||||
}).format(new Date(dayjs.utc(date).format("YYYY-MM-DDTHH:mm:ss")));
|
||||
|
||||
const EventTypeScheduleDetails = ({
|
||||
isManagedEventType,
|
||||
selectedScheduleValue,
|
||||
}: {
|
||||
isManagedEventType: boolean;
|
||||
selectedScheduleValue: AvailabilityOption | undefined;
|
||||
}) => {
|
||||
const { data: loggedInUser } = useMeQuery();
|
||||
const timeFormat = loggedInUser?.timeFormat;
|
||||
const { t, i18n } = useLocale();
|
||||
const { watch } = useFormContext<FormValues>();
|
||||
const EventTypeScheduleDetails = memo(
|
||||
({
|
||||
isManagedEventType,
|
||||
selectedScheduleValue,
|
||||
}: {
|
||||
isManagedEventType: boolean;
|
||||
selectedScheduleValue: AvailabilityOption | undefined;
|
||||
}) => {
|
||||
const { data: loggedInUser } = useMeQuery();
|
||||
const timeFormat = loggedInUser?.timeFormat;
|
||||
const { t, i18n } = useLocale();
|
||||
const { watch } = useFormContext<FormValues>();
|
||||
|
||||
const scheduleId = watch("schedule");
|
||||
const { isLoading, data: schedule } = trpc.viewer.availability.schedule.get.useQuery(
|
||||
{
|
||||
scheduleId: scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
|
||||
isManagedEventType,
|
||||
},
|
||||
{ enabled: !!scheduleId || !!loggedInUser?.defaultScheduleId || !!selectedScheduleValue }
|
||||
);
|
||||
const scheduleId = watch("schedule");
|
||||
const { isLoading, data: schedule } = trpc.viewer.availability.schedule.get.useQuery(
|
||||
{
|
||||
scheduleId:
|
||||
scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
|
||||
isManagedEventType,
|
||||
},
|
||||
{ enabled: !!scheduleId || !!loggedInUser?.defaultScheduleId || !!selectedScheduleValue }
|
||||
);
|
||||
|
||||
const filterDays = (dayNum: number) =>
|
||||
schedule?.schedule.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
|
||||
const filterDays = (dayNum: number) =>
|
||||
schedule?.schedule.filter((item) => item.days.includes((dayNum + 1) % 7)) || [];
|
||||
|
||||
return (
|
||||
<div className="border-default space-y-4 rounded border px-6 pb-4">
|
||||
<ol className="table border-collapse text-sm">
|
||||
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
|
||||
const isAvailable = !!filterDays(index).length;
|
||||
return (
|
||||
<li key={day} className="my-6 flex border-transparent last:mb-2">
|
||||
<span
|
||||
className={classNames(
|
||||
"w-20 font-medium sm:w-32 ",
|
||||
!isAvailable ? "text-subtle line-through" : "text-default"
|
||||
)}>
|
||||
{day}
|
||||
</span>
|
||||
{isLoading ? (
|
||||
<SkeletonText className="block h-5 w-60" />
|
||||
) : isAvailable ? (
|
||||
<div className="space-y-3 text-right">
|
||||
{filterDays(index).map((dayRange, i) => (
|
||||
<div key={i} className="text-default flex items-center leading-4">
|
||||
<span className="w-16 sm:w-28 sm:text-left">
|
||||
{format(dayRange.startTime, timeFormat === 12)}
|
||||
</span>
|
||||
<span className="ms-4">-</span>
|
||||
<div className="ml-6 sm:w-28">{format(dayRange.endTime, timeFormat === 12)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-subtle ml-6 sm:ml-0">{t("unavailable")}</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
<hr className="border-subtle" />
|
||||
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
|
||||
<span className="text-default flex items-center justify-center text-sm sm:justify-start">
|
||||
<Globe className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
|
||||
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
|
||||
</span>
|
||||
{!!schedule?.id && !schedule.isManaged && (
|
||||
<Button
|
||||
href={`/availability/${schedule.id}`}
|
||||
disabled={isLoading}
|
||||
color="minimal"
|
||||
EndIcon={ExternalLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{t("edit_availability")}
|
||||
</Button>
|
||||
)}
|
||||
return (
|
||||
<div className="border-default space-y-4 rounded border px-6 pb-4">
|
||||
<ol className="table border-collapse text-sm">
|
||||
{weekdayNames(i18n.language, 1, "long").map((day, index) => {
|
||||
const isAvailable = !!filterDays(index).length;
|
||||
return (
|
||||
<li key={day} className="my-6 flex border-transparent last:mb-2">
|
||||
<span
|
||||
className={classNames(
|
||||
"w-20 font-medium sm:w-32 ",
|
||||
!isAvailable ? "text-subtle line-through" : "text-default"
|
||||
)}>
|
||||
{day}
|
||||
</span>
|
||||
{isLoading ? (
|
||||
<SkeletonText className="block h-5 w-60" />
|
||||
) : isAvailable ? (
|
||||
<div className="space-y-3 text-right">
|
||||
{filterDays(index).map((dayRange, i) => (
|
||||
<div key={i} className="text-default flex items-center leading-4">
|
||||
<span className="w-16 sm:w-28 sm:text-left">
|
||||
{format(dayRange.startTime, timeFormat === 12)}
|
||||
</span>
|
||||
<span className="ms-4">-</span>
|
||||
<div className="ml-6 sm:w-28">{format(dayRange.endTime, timeFormat === 12)}</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<span className="text-subtle ml-6 sm:ml-0">{t("unavailable")}</span>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
})}
|
||||
</ol>
|
||||
<hr className="border-subtle" />
|
||||
<div className="flex flex-col justify-center gap-2 sm:flex-row sm:justify-between">
|
||||
<span className="text-default flex items-center justify-center text-sm sm:justify-start">
|
||||
<Globe className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
|
||||
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
|
||||
</span>
|
||||
{!!schedule?.id && !schedule.isManaged && (
|
||||
<Button
|
||||
href={`/availability/${schedule.id}`}
|
||||
disabled={isLoading}
|
||||
color="minimal"
|
||||
EndIcon={ExternalLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
{t("edit_availability")}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
EventTypeScheduleDetails.displayName = "EventTypeScheduleDetails";
|
||||
|
||||
const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
||||
const { t } = useLocale();
|
||||
|
|
|
@ -54,7 +54,12 @@ const getEmbedNamespace = (query: ReturnType<typeof useRouter>["query"]) => {
|
|||
return typeof window !== "undefined" ? window.getEmbedNamespace() : (query.embed as string) || null;
|
||||
};
|
||||
|
||||
const CustomI18nextProvider = (props: AppPropsWithChildren) => {
|
||||
// We dont need to pass nonce to the i18n provider - this was causing x2-x3 re-renders on a hard refresh
|
||||
type AppPropsWithoutNonce = Omit<AppPropsWithChildren, "pageProps"> & {
|
||||
pageProps: Omit<AppPropsWithChildren["pageProps"], "nonce">;
|
||||
};
|
||||
|
||||
const CustomI18nextProvider = (props: AppPropsWithoutNonce) => {
|
||||
/**
|
||||
* i18n should never be clubbed with other queries, so that it's caching can be managed independently.
|
||||
* We intend to not cache i18n query
|
||||
|
@ -222,11 +227,19 @@ const AppProviders = (props: AppPropsWithChildren) => {
|
|||
const session = trpc.viewer.public.session.useQuery().data;
|
||||
// No need to have intercom on public pages - Good for Page Performance
|
||||
const isPublicPage = usePublicPage();
|
||||
const { pageProps, ...rest } = props;
|
||||
const { _nonce, ...restPageProps } = pageProps;
|
||||
const propsWithoutNonce = {
|
||||
pageProps: {
|
||||
...restPageProps,
|
||||
},
|
||||
...rest,
|
||||
};
|
||||
|
||||
const RemainingProviders = (
|
||||
<EventCollectionProvider options={{ apiPath: "/api/collect-events" }}>
|
||||
<SessionProvider session={session || undefined}>
|
||||
<CustomI18nextProvider {...props}>
|
||||
<CustomI18nextProvider {...propsWithoutNonce}>
|
||||
<TooltipProvider>
|
||||
{/* color-scheme makes background:transparent not work which is required by embed. We need to ensure next-theme adds color-scheme to `body` instead of `html`(https://github.com/pacocoursey/next-themes/blob/main/src/index.tsx#L74). Once that's done we can enable color-scheme support */}
|
||||
<CalcomThemeProvider
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { memo } from "react";
|
||||
|
||||
import { FilterCheckboxFieldsContainer } from "@calcom/features/filters/components/TeamsFilter";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import type { RouterOutputs } from "@calcom/trpc";
|
||||
|
@ -14,7 +16,7 @@ const mapEventTypeToOption = (eventType: EventType): Option => ({
|
|||
label: eventType.teamId ? `${eventType.title} (${eventType.team?.name})` : eventType.title,
|
||||
});
|
||||
|
||||
export const EventTypeList = () => {
|
||||
export const EventTypeList = memo(() => {
|
||||
const { t } = useLocale();
|
||||
const { filter, setConfigFilters } = useFilterContext();
|
||||
const { selectedTeamId, selectedEventTypeId, selectedUserId, isAll } = filter;
|
||||
|
@ -72,4 +74,6 @@ export const EventTypeList = () => {
|
|||
</FilterCheckboxFieldsContainer>
|
||||
</AnimatedPopover>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
EventTypeList.displayName = "EventTypeList";
|
||||
|
|
|
@ -5,7 +5,7 @@ import Link from "next/link";
|
|||
import type { NextRouter } from "next/router";
|
||||
import { useRouter } from "next/router";
|
||||
import type { Dispatch, ReactNode, SetStateAction } from "react";
|
||||
import React, { Fragment, useEffect, useState, useRef } from "react";
|
||||
import React, { Fragment, useEffect, useState, useRef, useMemo } from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import dayjs from "@calcom/dayjs";
|
||||
|
@ -801,15 +801,18 @@ const getOrganizationUrl = (slug: string) =>
|
|||
|
||||
function SideBar({ bannersHeight, user }: SideBarProps) {
|
||||
const { t, isLocaleReady } = useLocale();
|
||||
const router = useRouter();
|
||||
const orgBranding = useOrgBrandingValues();
|
||||
const publicPageUrl = orgBranding?.slug ? getOrganizationUrl(orgBranding?.slug) : "";
|
||||
|
||||
const publicPageUrl = useMemo(() => {
|
||||
if (!user?.organizationId) return `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`;
|
||||
const publicPageUrl = orgBranding?.slug ? getOrganizationUrl(orgBranding?.slug) : "";
|
||||
return publicPageUrl;
|
||||
}, [orgBranding?.slug, user?.organizationId, user?.username]);
|
||||
|
||||
const bottomNavItems: NavigationItemType[] = [
|
||||
{
|
||||
name: "view_public_page",
|
||||
href: !!user?.organizationId
|
||||
? publicPageUrl
|
||||
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`,
|
||||
href: publicPageUrl,
|
||||
icon: ExternalLink,
|
||||
target: "__blank",
|
||||
},
|
||||
|
@ -818,9 +821,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
|||
href: "",
|
||||
onClick: (e: { preventDefault: () => void }) => {
|
||||
e.preventDefault();
|
||||
navigator.clipboard.writeText(
|
||||
!!user?.organizationId ? publicPageUrl : `${process.env.NEXT_PUBLIC_WEBSITE_URL}/${user?.username}`
|
||||
);
|
||||
navigator.clipboard.writeText(publicPageUrl);
|
||||
showToast(t("link_copied"), "success");
|
||||
},
|
||||
icon: Copy,
|
||||
|
@ -898,6 +899,7 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
|||
{bottomNavItems.map(({ icon: Icon, ...item }, index) => (
|
||||
<Tooltip side="right" content={t(item.name)} className="lg:hidden" key={item.name}>
|
||||
<ButtonOrLink
|
||||
id={item.name}
|
||||
href={item.href || undefined}
|
||||
aria-label={t(item.name)}
|
||||
target={item.target}
|
||||
|
@ -908,11 +910,6 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
|||
isLocaleReady ? "hover:bg-emphasis hover:text-emphasis" : "",
|
||||
index === 0 && "mt-3"
|
||||
)}
|
||||
aria-current={
|
||||
defaultIsCurrent && defaultIsCurrent({ item: { href: item.href }, router })
|
||||
? "page"
|
||||
: undefined
|
||||
}
|
||||
onClick={item.onClick}>
|
||||
{!!Icon && (
|
||||
<Icon
|
||||
|
@ -921,11 +918,6 @@ function SideBar({ bannersHeight, user }: SideBarProps) {
|
|||
"me-3 md:ltr:mr-2 md:rtl:ml-2"
|
||||
)}
|
||||
aria-hidden="true"
|
||||
aria-current={
|
||||
defaultIsCurrent && defaultIsCurrent({ item: { href: item.href }, router })
|
||||
? "page"
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isLocaleReady ? (
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||
import { useState } from "react";
|
||||
import { useState, memo } from "react";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { localStorage } from "@calcom/lib/webstorage";
|
||||
|
@ -98,9 +97,7 @@ export const tips = [
|
|||
|
||||
const reversedTips = tips.slice(0).reverse();
|
||||
|
||||
export default function Tips() {
|
||||
const [animationRef] = useAutoAnimate<HTMLDivElement>();
|
||||
|
||||
function Tips() {
|
||||
const { t } = useLocale();
|
||||
|
||||
const [list, setList] = useState<typeof tips>(() => {
|
||||
|
@ -175,3 +172,5 @@ export default function Tips() {
|
|||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default memo(Tips);
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
import { useRef, useEffect } from "react";
|
||||
|
||||
export function useTraceUpdate(props: { [s: string]: unknown } | ArrayLike<unknown>) {
|
||||
const prev = useRef(props);
|
||||
useEffect(() => {
|
||||
const changedProps = Object.entries(props).reduce((ps, [k, v]) => {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore TODO: fix this
|
||||
if (prev.current[k] !== v) {
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore TODO: fix this
|
||||
ps[k] = [prev.current[k], v];
|
||||
}
|
||||
return ps;
|
||||
}, {});
|
||||
if (Object.keys(changedProps).length > 0) {
|
||||
console.log("Changed props:", changedProps);
|
||||
}
|
||||
prev.current = props;
|
||||
});
|
||||
}
|
Loading…
Reference in New Issue