jack
jack
Explore posts from servers
TTCTheo's Typesafe Cult
Created by jack on 7/26/2024 in #questions
<img /> flicker on transition when conditionally rendering separate lists
No description
2 replies
TTCTheo's Typesafe Cult
Created by jack on 3/17/2024 in #questions
revalidate cached data on page load app router?
if i fetch a user's profile data with unstable_cache, but in case a user updates their data since last cache, i want to get that update data. is their a way (i believe RQ worked this way) so that you rely on the cached data on page load, but then it refetches in the background and updates if it's new? i came up with this seemingly really hacky client comp that will revalidate the path on page load. i just mount it in the page component. wondering if anyone has a better approach. I might just default to not using unstable_cache, and just always fetch new data
"use client";

import { useEffect, useMemo } from "react";

const useRevalidateOnPageLoad = (revalidate: () => void) => {
useEffect(() => {
console.log("running");
(() => {
revalidate();
})();
}, []);
};

export const RevalOnLoad = (props: { reval: () => void }) => {
const reval = useMemo(() => props.reval, [props.reval]);
useRevalidateOnPageLoad(reval);

return null;
};
"use client";

import { useEffect, useMemo } from "react";

const useRevalidateOnPageLoad = (revalidate: () => void) => {
useEffect(() => {
console.log("running");
(() => {
revalidate();
})();
}, []);
};

export const RevalOnLoad = (props: { reval: () => void }) => {
const reval = useMemo(() => props.reval, [props.reval]);
useRevalidateOnPageLoad(reval);

return null;
};
3 replies
TTCTheo's Typesafe Cult
Created by jack on 2/24/2024 in #questions
app architecture help?
No description
2 replies
TTCTheo's Typesafe Cult
Created by jack on 12/27/2023 in #questions
discriminated union as props
is there anything noticeably wrong with this
type SidebareOpenContentsProps = {
showSkeletons: boolean;
sidebarItems: MockSidebarItems | {};
} & (
| {
showSkeletons: true;
sidebarItems: {};
}
| {
showSkeletons: false;
sidebarItems: MockSidebarItems;
}
);
type SidebareOpenContentsProps = {
showSkeletons: boolean;
sidebarItems: MockSidebarItems | {};
} & (
| {
showSkeletons: true;
sidebarItems: {};
}
| {
showSkeletons: false;
sidebarItems: MockSidebarItems;
}
);
, when used like
<SidebarOpenContents
sidebarItems={status === "success" ? sidebarItems : {}}
showSkeletons={status === "loading"}
/>
<SidebarOpenContents
sidebarItems={status === "success" ? sidebarItems : {}}
showSkeletons={status === "loading"}
/>
I'm getting a "false is not assignable to boolean" error, but I thought this would be the only way to use discrimnated union to type narrow properly here.
2 replies
TTCTheo's Typesafe Cult
Created by jack on 11/12/2023 in #questions
type wizard help needed for my fake trpc package
I kinda gave up on tRPC with app router cuz I got frustrated BUT I really like using tRPC. I appreciate that it's organized (but not too restrictive like a rest server) and the method by which inputs are passed and parsed. As such, I started working on a way in my app to define things almost exactly like a trpc router/procedure so that you still get input parsing with zod, type safety updates on definition and caller side, but just without the trpc stuff. At this point it's not even worth it, but I'm too invested and want it to work. This is majority of the code
type AsyncQuillQueryWithSchema<T> = (input: T) => Promise<unknown>;
type AsyncQuillQueryNoSchema = () => Promise<unknown>;

function createQuillRoute() {
const input = <T extends z.ZodTypeAny>(schema: T) => {
// define a fn that takes a query fn and returns a fn that parses it's input first
const typeSafeQueryInvoker = (
fn: AsyncQuillQueryWithSchema<z.infer<T>>,
) => {
return async (input: z.infer<typeof schema>) => {
try {
await schema.parseAsync(input);
return await fn(input);
} catch (error) {
throw error;
}
};
};

// return an object on that which is call-able
return {
query: typeSafeQueryInvoker,
// mutation: typeSafeMutationInvoker,
};
};

// we also want to define a query fn that doesn't take any input
const query = (fn: AsyncQuillQueryNoSchema) => {
return async () => fn();
};

return {
input,
query,
};
}

export const quill = () => ({
...createQuillRoute(),
});
type AsyncQuillQueryWithSchema<T> = (input: T) => Promise<unknown>;
type AsyncQuillQueryNoSchema = () => Promise<unknown>;

function createQuillRoute() {
const input = <T extends z.ZodTypeAny>(schema: T) => {
// define a fn that takes a query fn and returns a fn that parses it's input first
const typeSafeQueryInvoker = (
fn: AsyncQuillQueryWithSchema<z.infer<T>>,
) => {
return async (input: z.infer<typeof schema>) => {
try {
await schema.parseAsync(input);
return await fn(input);
} catch (error) {
throw error;
}
};
};

// return an object on that which is call-able
return {
query: typeSafeQueryInvoker,
// mutation: typeSafeMutationInvoker,
};
};

// we also want to define a query fn that doesn't take any input
const query = (fn: AsyncQuillQueryNoSchema) => {
return async () => fn();
};

return {
input,
query,
};
}

export const quill = () => ({
...createQuillRoute(),
});
which can then be used like this (where i nest it a few times)
const profile = await quillApi.user.getUserByProfileId({
username: props.params.username,
});
const profile = await quillApi.user.getUserByProfileId({
username: props.params.username,
});
this works surprisingly well, however, it's not inferring return type properly. I just get unknown, which makes sense since my fn types define the return as such. Anyone have thoughts?
3 replies
TTCTheo's Typesafe Cult
Created by jack on 11/11/2023 in #questions
upgraded to new ct3a; all trpc calls throwing 'Unexpected token r in JSON at position 0'
No description
13 replies
TTCTheo's Typesafe Cult
Created by jack on 11/8/2023 in #questions
including /edit path for profile data?
i have a url /username and this has a list of data that can be re-ordered and edited. my plan was to have it such that you click edit, the list becomes re-orderable and you reorder, then you click save and it persists to the server. thoughts on having the readonly state being /username and the editable state being /username/edit ? i'm pretty uncertain if i like this approach or not. another option is to have the list always in re-orderable state, and just save with a slight debounce or whatever
7 replies
TTCTheo's Typesafe Cult
Created by jack on 11/2/2023 in #questions
help interleaving server/client comps in layout?
No description
31 replies
TTCTheo's Typesafe Cult
Created by jack on 10/25/2023 in #questions
trpc/app-router surfacing trpc errors in `error.tsx`?
[i already posted this on trpc discord but not getting anything over there so figured i'd see if anyone here might know] I've got this condition for throwing in my trpc procedure:
if (!userClerkProfile) {
console.log("throwing"); // -> this is logging
throw new TRPCError({
code: "NOT_FOUND",
message: `Couldn't find the user with @${input.username}`,
});

if (!userClerkProfile) {
console.log("throwing"); // -> this is logging
throw new TRPCError({
code: "NOT_FOUND",
message: `Couldn't find the user with @${input.username}`,
});

and on client I have a next13 error.tsx route, in which I do:
const ErrorViewMap = {
[TRPC_ERROR_MAP.NOT_FOUND]: UsernameNotFoundErrorView,
DEFAULT: () => <p>Unexpected error, please try again</p>,
};

export default function ProfilePageError({
error,
reset,
}: PropsProfilePageError) {
const errorCode = getHTTPStatusCodeFromError(error);
const ErrorView = ErrorViewMap[errorCode] ?? ErrorViewMap.DEFAULT;

return <ErrorView message={"stock message"} resetFn={reset} />;
}
const ErrorViewMap = {
[TRPC_ERROR_MAP.NOT_FOUND]: UsernameNotFoundErrorView,
DEFAULT: () => <p>Unexpected error, please try again</p>,
};

export default function ProfilePageError({
error,
reset,
}: PropsProfilePageError) {
const errorCode = getHTTPStatusCodeFromError(error);
const ErrorView = ErrorViewMap[errorCode] ?? ErrorViewMap.DEFAULT;

return <ErrorView message={"stock message"} resetFn={reset} />;
}
errorCode is always 500 when we throw from that condition. No matter the code I pass when I throw
5 replies
TTCTheo's Typesafe Cult
Created by jack on 10/20/2023 in #questions
t3app app router; catching errors in trpc route called from rsc?
iirc, in old react/trpc (pages with trpc w/ RQ), the return object had all sorts of statuses etc, including an error status. From there, if trpc throws an error, you can just handle it on client with out muddying the trpc procedures with try/catch? i'm admittedly using the beta ct3a build from a month-ish ago, so this might be addressed, but right now if a dependency call (clerk sdk in this case) throws an error in my procedure, this just surfaces on the client. am i just forced to try/catch in one place or another since we don't get any of the RQ state magic on server side? if so, where would be the best place to handle it? I really liked the simplicity of trpc in pages router and just a quick client check to see if we have encountered an error or not. I guess this wouldn't change in this case? wrap my trpc call in try catch and render accordingly; even though this technically isn't on the client, it still maintains the majority of the mental model of call trpc from component -> handle error and show appropriate view. looking for suggestions on this!
3 replies
TTCTheo's Typesafe Cult
Created by jack on 7/4/2023 in #questions
easiest way to convert shadcn css vars to a different tailwind swatch?
i've got all of the css vars auto gen'd, and i'm frankly just too lazy to manually identify which value of slate that each of these correspond to and then manually replace them with a different swatch. is there a not super annoying way to do so? my next option is to write some cursed python parsing lol
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;

--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;

--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;

--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;

--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;

--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;

--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;

--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;

--destructive: 0 92% 38%;
--destructive-foreground: 210 40% 98%;

--ring: 215 20.2% 65.1%;

--radius: 0.5rem;
--background: 0 0% 100%;
--foreground: 222.2 47.4% 11.2%;

--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;

--popover: 0 0% 100%;
--popover-foreground: 222.2 47.4% 11.2%;

--card: 0 0% 100%;
--card-foreground: 222.2 47.4% 11.2%;

--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;

--primary: 222.2 47.4% 11.2%;
--primary-foreground: 210 40% 98%;

--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;

--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;

--destructive: 0 92% 38%;
--destructive-foreground: 210 40% 98%;

--ring: 215 20.2% 65.1%;

--radius: 0.5rem;
2 replies
TTCTheo's Typesafe Cult
Created by jack on 6/29/2023 in #questions
any typescript genius can help with writing a wrapper function for a SvelteKit loader ?
the way that the auth library i'm working with works, is that it recommends reading in the auth from the request (after being processed by hooks), and redirecting in the pager loader. so for a bunch of my pages, i'm gonna want almost the same structure, with computation specific to the page:
export const load = (async (req) => {
const auth = await req.locals.auth.validateUser();
if (!auth?.user) redirect(302, '/');

// do stuff here custom to each page
const customProps = 'customPropsThatIGetFromDoingStuff';

return {
customProps,
...auth?.user
};
}) satisfies PageServerLoad;
export const load = (async (req) => {
const auth = await req.locals.auth.validateUser();
if (!auth?.user) redirect(302, '/');

// do stuff here custom to each page
const customProps = 'customPropsThatIGetFromDoingStuff';

return {
customProps,
...auth?.user
};
}) satisfies PageServerLoad;
naturally, i'd like to extract the repetitive logic so i don't have to write it every time i make a page that's protected. i was thinking something such that it works like
export const load = protect((async (request) => {
// custom callback (the 'do stuff here custom to each page' from the prior snippet)
return stuff
}) satisfies PageServerLoad, { customRedirect: "/login" })
export const load = protect((async (request) => {
// custom callback (the 'do stuff here custom to each page' from the prior snippet)
return stuff
}) satisfies PageServerLoad, { customRedirect: "/login" })
any thoughts? essentially all implementations i'm able to come up with just entirely break Svelte's type system when loading the data in
4 replies
TTCTheo's Typesafe Cult
Created by jack on 4/7/2023 in #questions
constructing link with search parameters
So I have a navbar that links to a /results page. The situation is that if a user has preferences saved in localstorage, i want to embed those preferences within the link as search params like /results?sports=true&... currently, i have
const appendParams = (path: PagePaths): PagePaths => {
const searchParams = new URLSearchParams();

defaultPreferences.forEach((pref) => {
searchParams.append(pref, "true");
});

return `${path}?${searchParams.toString()}` as PagePaths;
};

const pages: Record<string, PageLink> = {
home: { title: "Home", href: "/" },
search: { title: "New Preferences", href: "/search" },
results: {
title: "Default Preferences",
href: appendParams("/results"),
},
};

useEffect(() => {
const savedPreferencesOrEmpty = localStorage.getItem("preferences") ?? "[]";

setDefaultPreferences(() => JSON.parse(savedPreferencesOrEmpty));
}, []);
const appendParams = (path: PagePaths): PagePaths => {
const searchParams = new URLSearchParams();

defaultPreferences.forEach((pref) => {
searchParams.append(pref, "true");
});

return `${path}?${searchParams.toString()}` as PagePaths;
};

const pages: Record<string, PageLink> = {
home: { title: "Home", href: "/" },
search: { title: "New Preferences", href: "/search" },
results: {
title: "Default Preferences",
href: appendParams("/results"),
},
};

useEffect(() => {
const savedPreferencesOrEmpty = localStorage.getItem("preferences") ?? "[]";

setDefaultPreferences(() => JSON.parse(savedPreferencesOrEmpty));
}, []);
, which i think? will work. but i feel like this is hacky and not really the right way to handle it (primary concern is the appendParams function. any thoughts? originally i was going to construct a URL object and use the searchParams.append method on the object to add the params, but next's router doesn't have a function that returns the actual, non-relative, base path
1 replies
TTCTheo's Typesafe Cult
Created by jack on 3/11/2023 in #questions
helpful tips to prepare for deploying/launching an app?
i've been working on an app for a few months now, and i'm getting ready to release it, just wondering if anyone has any tips/blog posts/etc. that were helpful to them when doing this. currently i've been in 'move-super-fast' mode, so im not really following any good conventions for working on a properly deployed app (i have one hosted db and im using it for dev and on the live site, for example). obviously this is kind of vague, but in the past ive run into issues where the app breaks on everybody and i have to rush to fix it, so any advice on this would be awesome. some things im wondering: handling databases for prod vs dev, oauth apps in prod vs dev (callback urls have to be different), prod vs staging branches, etc etc. thanks!
3 replies
TTCTheo's Typesafe Cult
Created by jack on 3/7/2023 in #questions
use github for oauth not as my personal profile
i'm working on an app where github is the authentication measure. currently i have it working, but i feel like when signing up, people might be hesitant to login when oauth prompts the user with allow <my name> to do x y and z with your account, or whatever the message is. i'm just wondering how people generally do this- new github account? or is there a way around this? (this question also sort of applies for other oauth providers as im planning on adding more than just github, eventually probably even revoking github)
12 replies
TTCTheo's Typesafe Cult
Created by jack on 3/4/2023 in #questions
writing a user search functionality (trpc+prisma+next.js)
does anyone have any articles/stack overflow posts on how i could implement a user search in my app? im less concerned with the server part of it (as in, best ranking records against the user search term), but more concerned with how to handle the client <-> server interaction. my thought process is: obviously i dont want to make http reqs on every keystroke, but also i want the results to be as responsive to the search term as possible. i was considering that after x keystrokes, i return a ranked list of users to the client, and then the client filters until the ranked list gets too small (whatever we define "too small" as), and then refetches from the server in that instance. having trouble wording the google search on this one, wondering if anybody has advice. thanks!
16 replies
TTCTheo's Typesafe Cult
Created by jack on 2/16/2023 in #questions
dnd-kit with trpc animation glitch on reorder
i'm having this issue with trpc and https://dndkit.com/ (sortable preset), where the reorder of an item works, but causes a weird jump in the card, originating from the top of the viewport. if anything, i think it has to do with react-query fetching behavior, but i'm entirely lost as to what could be causing this. i simplified the components below, and this is the general idea of what they look like:
// fetches and renders the list (near identical to the docs)
const BasicListEditor = () => {
const { data: selections } = api.selection.getOwnSelections.useQuery();
const utils = api.useContext();

function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;

if (active.id !== over?.id) {
utils.selection.getOwnSelections.setData(undefined, (prev) => {
if (!prev) return prev;

const oldIndex = prev?.findIndex((a) => a.id === active.id);
const newIndex = prev?.findIndex((a) => a.id === over?.id);

return arrayMove(prev, oldIndex, newIndex);
});
}
}

const sensors = useSensors(useSensor(PointerSensor));

return (
<DndContext
onDragEnd={handleDragEnd}
sensors={sensors}
collisionDetection={closestCenter}
>
<SortableContext items={selections} strategy={verticalListSortingStrategy} >
{selections.map((a) => ( <SortableItem key={a.id} id={a.id} /> ))}
</SortableContext>
</DndContext>
);
};

// item that gets sorted
function SortableItem({ id }: { id: string }) {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: id });

const style = { transform: CSS.Transform.toString(transform), transition };

return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners} >
<p className="text-xl text-blue-500">THIS IS LIST ITEM {id}</p>
</div>
);
};
// fetches and renders the list (near identical to the docs)
const BasicListEditor = () => {
const { data: selections } = api.selection.getOwnSelections.useQuery();
const utils = api.useContext();

function handleDragEnd(event: DragEndEvent) {
const { active, over } = event;

if (active.id !== over?.id) {
utils.selection.getOwnSelections.setData(undefined, (prev) => {
if (!prev) return prev;

const oldIndex = prev?.findIndex((a) => a.id === active.id);
const newIndex = prev?.findIndex((a) => a.id === over?.id);

return arrayMove(prev, oldIndex, newIndex);
});
}
}

const sensors = useSensors(useSensor(PointerSensor));

return (
<DndContext
onDragEnd={handleDragEnd}
sensors={sensors}
collisionDetection={closestCenter}
>
<SortableContext items={selections} strategy={verticalListSortingStrategy} >
{selections.map((a) => ( <SortableItem key={a.id} id={a.id} /> ))}
</SortableContext>
</DndContext>
);
};

// item that gets sorted
function SortableItem({ id }: { id: string }) {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({ id: id });

const style = { transform: CSS.Transform.toString(transform), transition };

return (
<div ref={setNodeRef} style={style} {...attributes} {...listeners} >
<p className="text-xl text-blue-500">THIS IS LIST ITEM {id}</p>
</div>
);
};
here's also a video of what's happening:
1 replies
TTCTheo's Typesafe Cult
Created by jack on 2/11/2023 in #questions
trpc/react-query render logic
12 replies
TTCTheo's Typesafe Cult
Created by jack on 2/11/2023 in #questions
getServersideProps not providing type inference, and session getting lost between server and client
I have this function (which I've used many times before),
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const auth = await getServerAuthSession(ctx);

if (!auth?.user) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

return {
props: {
session: auth,
},
};
};
export const getServerSideProps: GetServerSideProps = async (ctx) => {
const auth = await getServerAuthSession(ctx);

if (!auth?.user) {
return {
redirect: {
destination: "/login",
permanent: false,
},
};
}

return {
props: {
session: auth,
},
};
};
for some reason, however, type inference with the page is not working, typed as follows
const Me: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = (
props
) => {
//...
}
const Me: NextPage<InferGetServerSidePropsType<typeof getServerSideProps>> = (
props
) => {
//...
}
and on top of this, the session prop is getting lost, so when i log props on the client, the return is just an empty object. strangely enough it does work for auth purposes, redirecting unauthed users. i would just simply use useSession, however i have to conditionally render all of the data from this hook since its possible undefined or null, whereas if i got my session from getServerSideProps, I could ensure that its not null and avoid the conditional logic in my ui. i'm quite lost on this, and any help would be appreciated.
6 replies
TTCTheo's Typesafe Cult
Created by jack on 2/1/2023 in #questions
return relation in nested write in prisma
I have two models that looks as such:
model Card {
id String @id @default(uuid())
createdAt DateTime @default(now())
title String
tasks Task[]
}

model Task {
id String @id @default(uuid())
createdAt DateTime @default(now())
title String
cardId String
completed Boolean @default(false)
card Card @relation(fields: [cardId], references: [id])
}
model Card {
id String @id @default(uuid())
createdAt DateTime @default(now())
title String
tasks Task[]
}

model Task {
id String @id @default(uuid())
createdAt DateTime @default(now())
title String
cardId String
completed Boolean @default(false)
card Card @relation(fields: [cardId], references: [id])
}
i'm trying to create a task, but in doing so, return the card to which it belongs. i currently have this
const newCard = await ctx.p.task.create({
data: {
title: input.title,
cardId: input.card_id
},
select: {
card: {
include: {
tasks: true
}
}
}
});
const newCard = await ctx.p.task.create({
data: {
title: input.title,
cardId: input.card_id
},
select: {
card: {
include: {
tasks: true
}
}
}
});
but its returning something with the following type:
{
card: Card & {
tasks: Task[];
};
}
{
card: Card & {
tasks: Task[];
};
}
so essentially its giving me the return type that i want, but nested inside of the card property. is there any way around this ?
1 replies