jack
jack
Explore posts from servers
SSolidJS
Created by jack on 8/26/2024 in #support
useSubmission pending state resolves on url change?
i have some optimistic ui, with roughly the following structure:
view = "edit" | "read", based on searchParams
data = fetch data from server
editable = copy of data we can write to (what we render)

when the user clicks save =>
invoke save fn() (which is an action())
**set view -> "read" (rest of ui is already optimistically set)

if no error thrown (via try)
await revalidate() // wan't to make sure we have server state befoe confirming success
toast("good")
if error thrown (via catch)
await revalidate() // reset to good state
toast("bad")

view = "edit" | "read", based on searchParams
data = fetch data from server
editable = copy of data we can write to (what we render)

when the user clicks save =>
invoke save fn() (which is an action())
**set view -> "read" (rest of ui is already optimistically set)

if no error thrown (via try)
await revalidate() // wan't to make sure we have server state befoe confirming success
toast("good")
if error thrown (via catch)
await revalidate() // reset to good state
toast("bad")

then somewhere else i use useSubmission(save) and disable buttons based on pending state as i don't want user's editing lists that aren't accurate to server state. the weird thing is, when i include the logic above with the **, the useSubmission state basically dies. it returns to undefined, and my buttons are don't disabled if instead i wait to set view -> "read" until after i've checked the result of the server call, then the pending state works perfectly (which i can't really do, otherwise the illusion of the optimistic ui is gone) wondering if this is expected, or perhaps i'm doing something wrong? I can post code tomorrow if this doesn't make sense, thanks!
6 replies
SSolidJS
Created by jack on 8/26/2024 in #support
does any sort of primitive similar to Show for non-jsx use exist?
i had Claude generate most of this function for me. wondering if there's a solution/pattern for this problem, as well as if this is a horrible idea and i'm missing some case as to why
export function depends<T, U>(
v: () => T | undefined,
fn: (value: NonNullable<T>) => Promise<U>
): () => Promise<U | undefined> {
return async () => {
const value = v();
if (value !== undefined && value !== null) {
return fn(value as NonNullable<T>);
}
return undefined;
};
}
export function depends<T, U>(
v: () => T | undefined,
fn: (value: NonNullable<T>) => Promise<U>
): () => Promise<U | undefined> {
return async () => {
const value = v();
if (value !== undefined && value !== null) {
return fn(value as NonNullable<T>);
}
return undefined;
};
}
so that i can write signals with async inputs a bit easier:
const selections = createAsync(
depends(
() => user()?.id,
async (userId) => getSelections(userId)
)
);
const selections = createAsync(
depends(
() => user()?.id,
async (userId) => getSelections(userId)
)
);
previously i'd have to either do some sort of type guard/narrow manually each time in the function passed here which isn't great, or I just do like user()?id ?? "", but i don't really want to be passing empty string down as a fallback, i'd rather do nothing until user()?.id is valid
1 replies
SSolidJS
Created by jack on 8/25/2024 in #support
use of cache causing page not to render at all on client nav
been struggling with this for like 2 hours and finally narrowed down that if any single cache() call exists in my file, my page just won't render it's content (at least on client) i have no idea why this is happening, and i'm not really sure at what point this began to happen. wondering if there are any common symptoms for this
4 replies
SSolidJS
Created by jack on 8/25/2024 in #support
derived, but update-able, signal?
is it possible to have a piece of reactive code that can be updated directly, but is also tied to another piece of state? I have a list I fetch from server that is wrapped with createAsync. I want to use this to track server state, but then have a dup of that list that can be updated any number of times in the app. The user then hits save, and we update the list on server and revalidate I originally just tried a derived signal, but I can't directly update this. Then I was thinking of just making another signal and having it's initial value be a call to its initial value, but I'm not sure if this is a good thing to do any idea?
47 replies
SSolidJS
Created by jack on 8/10/2024 in #support
typing Page props ?
is it possible to type the props for the page? I'm tryna to block the page until render, and the best I've come up with is:
const load = cache(
(userId: string) => api.getSelectionsByUserId(userId),
"selections"
);

export const route = {
preload: () => load,
} satisfies RouteDefinition;

export default function Username(props: ManuallyTypePropsAfterLookingAtConsoleLog) {
const selections = createAsync(() => load(users[0].id), {
deferStream: true,
initialValue: props.data,
});
//...
const load = cache(
(userId: string) => api.getSelectionsByUserId(userId),
"selections"
);

export const route = {
preload: () => load,
} satisfies RouteDefinition;

export default function Username(props: ManuallyTypePropsAfterLookingAtConsoleLog) {
const selections = createAsync(() => load(users[0].id), {
deferStream: true,
initialValue: props.data,
});
//...
but the props have to be manually typed
39 replies
SSolidJS
Created by jack on 8/9/2024 in #support
nested layout
is it possible to make the file structure like follows:
- routes
- (user)
- layout.tsx
- [username].tsx
- routes
- (user)
- layout.tsx
- [username].tsx
obviously layout.tsx isn't the proper naming convention, but just wondering if it's possible to achieve something like that. Currently I'm doing
- routes
- (user)
- [username].tsx
- (user).tsx // this is the layout
- routes
- (user)
- [username].tsx
- (user).tsx // this is the layout
but routes/ is gets pretty cluttered fast as I create route groups
3 replies
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
SSolidJS
Created by jack on 12/22/2023 in #support
type narrowing signals
I've got some state (I'm making a card game) that looks like
type GameInProgress = {
state: "playing" | "paused";
p1Flipped: boolean;
p2Flipped: boolean;
};

type GameOver = {
state: "over";
};

type GamePaused = {
state: "paused";
p1Flipped: true;
p2Flipped: true;
};

type Game = {
state: "playing" | "paused" | "over";
} & (GameInProgress | GamePaused | GameOver);
type GameInProgress = {
state: "playing" | "paused";
p1Flipped: boolean;
p2Flipped: boolean;
};

type GameOver = {
state: "over";
};

type GamePaused = {
state: "paused";
p1Flipped: true;
p2Flipped: true;
};

type Game = {
state: "playing" | "paused" | "over";
} & (GameInProgress | GamePaused | GameOver);
and then my signal to track game state ends up looking like
const [gameState, setGameState] = createSignal<Game>({
state: "over",
});
const [gameState, setGameState] = createSignal<Game>({
state: "over",
});
over time, i want to update this object. since i'm using discriminated types, if i want to reason about any of the props that only exist on GamePaused or GameOver, I need to do some narrowing. it seems like doing a if (gameState().state !== "playing") { return } ... gameState().___ doesn't successfully autocomplete. I'm assuming that typescript doesn't have enough info to guarantee that the 2nd getter invocation will be equivalent to that of the first. Next reasonable idea is to invoke the getter once and store in a variable, then use that to type narrow. But then when I do a setGameState(...) using that variable, I'm not following the nested reactivity pattern here https://www.solidjs.com/tutorial/stores_nested_reactivity Any thoughts/best practices here ?
16 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
TtRPC
Created by jack on 10/24/2023 in #❓-help
error route always getting 500 from trpc error? (next13/approuter)
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
2 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
DTDrizzle Team
Created by jack on 9/28/2023 in #help
dynamically insert either single object or array of objects breaking types
So here's 3 renditions of the same fn
const insertFighterSolo = async (fighterInsertData: FighterInsert) => {
return db.insert(fighters).values(fighterInsertData);
};

const insertFighterArray = async (fighterInsertData: FighterInsert[]) => {
return db.insert(fighters).values(fighterInsertData);
};

const insertFightersEither = async (fighterInsertData: FighterInsert | FighterInsert[]) => {
return db.insert(fighters).values(fighterInsertData);
};
const insertFighterSolo = async (fighterInsertData: FighterInsert) => {
return db.insert(fighters).values(fighterInsertData);
};

const insertFighterArray = async (fighterInsertData: FighterInsert[]) => {
return db.insert(fighters).values(fighterInsertData);
};

const insertFightersEither = async (fighterInsertData: FighterInsert | FighterInsert[]) => {
return db.insert(fighters).values(fighterInsertData);
};
Counterintuitively, the first two seem to work fine (not getting yelled at by typechecker), but the third, which I think to be entirely valid TypeScript, gives me a No overload matches this call. Overload 1 of 2,... Any idea on this? Is it something to do with the uncertain shape of the incoming parameter that it can't handle or something
1 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