Filip
Filip
Explore posts from servers
TTCTheo's Typesafe Cult
Created by Filip on 6/20/2023 in #questions
How to invalidate routes/router in backend/webhook?
Update: the previous solution using a local react component state to store the past user data copy wasn't working as expected. I suspect that the way react batches things together was causing this. I found out that in tanstack that the onSuccess property of the query one can extract the incoming data before it is stored to the variable, and this allows me to make a comparison between the old and new to check for difference ( https://tanstack.com/query/v4/docs/react/reference/QueryCache ). This approach seems to work. See edit in posts above for the updated code. (Also moved the interval state to a context provider and added toast notifications).
8 replies
TTCTheo's Typesafe Cult
Created by Filip on 6/20/2023 in #questions
How to invalidate routes/router in backend/webhook?
8 replies
TTCTheo's Typesafe Cult
Created by Filip on 6/20/2023 in #questions
How to invalidate routes/router in backend/webhook?
Ok, so I got an idea looking at one of the examples from Tanstack, and it involves using the refetchInterval option in useQuery. I'll post it here and add explanation of the following code in next post if it can be of help to others. So on my billing page I have:
const billingQueryInterval = useBillingQueryInterval(); //context provided state
const setBillingQueryInterval = useBillingQueryIntervalUpdate(); // context provided state setter
const {
data: userSubscriptionPlan,
isLoading: userSubscriptionPlanLoading,
isError: userSubscriptionPlanError
} = api.stripe.getUserSubscriptionPlan.useQuery(undefined, {
refetchInterval: billingQueryInterval,
onSuccess: (newData) => {
if (userSubPlanQueryRetries > 60) {
toastError();
setBillingQueryInterval(false);
setUserSubPlanQueryRetries(0);
return;
}
if (JSON.stringify(newData) !== JSON.stringify(userSubscriptionPlan)) {
if (isNumber(billingQueryInterval) && billingQueryInterval > 0) { // Condition to ensure toast runs only after user interaction.
toastSubUpdateSuccess();
}
setBillingQueryInterval(false);
setUserSubPlanQueryRetries(0);
return;
}
if (billingQueryInterval !== false) {
setUserSubPlanQueryRetries(prevCount => prevCount + 1)
}
},
});
const billingQueryInterval = useBillingQueryInterval(); //context provided state
const setBillingQueryInterval = useBillingQueryIntervalUpdate(); // context provided state setter
const {
data: userSubscriptionPlan,
isLoading: userSubscriptionPlanLoading,
isError: userSubscriptionPlanError
} = api.stripe.getUserSubscriptionPlan.useQuery(undefined, {
refetchInterval: billingQueryInterval,
onSuccess: (newData) => {
if (userSubPlanQueryRetries > 60) {
toastError();
setBillingQueryInterval(false);
setUserSubPlanQueryRetries(0);
return;
}
if (JSON.stringify(newData) !== JSON.stringify(userSubscriptionPlan)) {
if (isNumber(billingQueryInterval) && billingQueryInterval > 0) { // Condition to ensure toast runs only after user interaction.
toastSubUpdateSuccess();
}
setBillingQueryInterval(false);
setUserSubPlanQueryRetries(0);
return;
}
if (billingQueryInterval !== false) {
setUserSubPlanQueryRetries(prevCount => prevCount + 1)
}
},
});
In my updateSub component, I have:
const setBillingQueryInterval = useBillingQueryIntervalUpdate();
const errorToast = useErrorToast();
const { isLoading: updateIsLoading, mutateAsync: updateSubscriptionProcedure } = api.stripe.updateSubscription.useMutation({
onSuccess() {
setBillingQueryInterval(1000);
}
});

const btnDisabled = useBillingDisabled();
const setBillingQueryInterval = useBillingQueryIntervalUpdate();
const errorToast = useErrorToast();
const { isLoading: updateIsLoading, mutateAsync: updateSubscriptionProcedure } = api.stripe.updateSubscription.useMutation({
onSuccess() {
setBillingQueryInterval(1000);
}
});

const btnDisabled = useBillingDisabled();
8 replies
TTCTheo's Typesafe Cult
Created by Filip on 6/20/2023 in #questions
How to invalidate routes/router in backend/webhook?
Alright! Thanks! I'll start looking into it and post my solution here once I have something that fits my use case.
8 replies
TTCTheo's Typesafe Cult
Created by Benja on 4/23/2023 in #questions
Layout management - T3 Stack - Next.js v13
Finally, in index.tsx, I have (which I reckon needs to be replicated for each page that's going to use the layout).
import Head from "next/head";
import { type ReactElement } from 'react';
import { signIn, signOut, useSession } from "next-auth/react";
import { api } from "~/utils/api";
import Layout from "~/components/Layout";
import { type NextPageWithLayout } from "./_app";

const Home: NextPageWithLayout = () => {
const hello = api.example.hello.useQuery({ text: "from tRPC" }, {refetchOnWindowFocus: false,});

return (
<>
<Head>
...omitted for brevity...
</Head>
<main>
...omitted...
</main>
</>
);
};

Home.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>
{page}
</Layout>
);
};

export default Home;
import Head from "next/head";
import { type ReactElement } from 'react';
import { signIn, signOut, useSession } from "next-auth/react";
import { api } from "~/utils/api";
import Layout from "~/components/Layout";
import { type NextPageWithLayout } from "./_app";

const Home: NextPageWithLayout = () => {
const hello = api.example.hello.useQuery({ text: "from tRPC" }, {refetchOnWindowFocus: false,});

return (
<>
<Head>
...omitted for brevity...
</Head>
<main>
...omitted...
</main>
</>
);
};

Home.getLayout = function getLayout(page: ReactElement) {
return (
<Layout>
{page}
</Layout>
);
};

export default Home;
24 replies
TTCTheo's Typesafe Cult
Created by Benja on 4/23/2023 in #questions
Layout management - T3 Stack - Next.js v13
In my Layout component, I have:
import React, { ReactNode } from 'react';
import { NavbarMain } from './NavbarMain';
import { Footer } from './Footer';

interface LayoutProps {
children: ReactNode;
}

export default function Layout({ children }: LayoutProps) {
return (
<div>
<NavbarMain />
<main>{children}</main>
<Footer />
</div>
);
}
import React, { ReactNode } from 'react';
import { NavbarMain } from './NavbarMain';
import { Footer } from './Footer';

interface LayoutProps {
children: ReactNode;
}

export default function Layout({ children }: LayoutProps) {
return (
<div>
<NavbarMain />
<main>{children}</main>
<Footer />
</div>
);
}
24 replies
TTCTheo's Typesafe Cult
Created by Benja on 4/23/2023 in #questions
Layout management - T3 Stack - Next.js v13
For anyone who wants to get the layout working with types, I've cobbled together the following through Benja's superb advice above, Nextjs's docs and some other searches. I'm fairly new to TS, take this with a grain of salt but it appears to be working. Using the pages router, in _app.tsx, I have the following:
import { type Session } from "next-auth";
import { type NextPage } from "next";
import { type AppProps } from "next/app";
import { type ReactElement, ReactNode } from "react";
import { SessionProvider } from "next-auth/react";
import { api } from "~/utils/api";

import "~/styles/globals.css";
import "~/styles/Calendar.css";
import "~/styles/Data-title.css";

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout<P = {}> = AppProps & {
Component: NextPageWithLayout<P>;
};

const MyApp: React.FC<AppPropsWithLayout<{ session: Session | null }>> = ({
Component,
pageProps: { session, ...pageProps },
}) => {
const getLayout = Component.getLayout ?? ((page) => page);

return (
<SessionProvider session={session}>
{
getLayout(<Component {...pageProps} />)
}
</SessionProvider>
);
};

export default api.withTRPC(MyApp);
import { type Session } from "next-auth";
import { type NextPage } from "next";
import { type AppProps } from "next/app";
import { type ReactElement, ReactNode } from "react";
import { SessionProvider } from "next-auth/react";
import { api } from "~/utils/api";

import "~/styles/globals.css";
import "~/styles/Calendar.css";
import "~/styles/Data-title.css";

export type NextPageWithLayout<P = {}, IP = P> = NextPage<P, IP> & {
getLayout?: (page: ReactElement) => ReactNode;
};

type AppPropsWithLayout<P = {}> = AppProps & {
Component: NextPageWithLayout<P>;
};

const MyApp: React.FC<AppPropsWithLayout<{ session: Session | null }>> = ({
Component,
pageProps: { session, ...pageProps },
}) => {
const getLayout = Component.getLayout ?? ((page) => page);

return (
<SessionProvider session={session}>
{
getLayout(<Component {...pageProps} />)
}
</SessionProvider>
);
};

export default api.withTRPC(MyApp);
24 replies