Cody
Cody
TTCTheo's Typesafe Cult
Created by Cody on 9/25/2024 in #questions
Weird React Behaviour, lost on how to debug
Hey guys, I've been losing hair over this and am a bit lost, hoping someone can help. I have this modal component which I can call, await and get a value from:
import { Button } from "../buttons/Button";
import Modal from "./Modal";

type AsyncModalProps = {
title: string;
content: ReactNode;
confirmText?: string;
cancelText?: string;
};

type AsyncModalRef = {
showModal: () => Promise<boolean>;
};

export const AsyncModal = React.forwardRef<AsyncModalRef, AsyncModalProps>(
({ title, content, confirmText = "Confirm", cancelText = "Cancel" }, ref) => {
const [isOpen, setIsOpen] = useState(false);
const [resolver, setResolver] = useState<((value: boolean) => void) | null>(
null
);

const showModal = useCallback(() => {
setIsOpen(true);
return new Promise<boolean>((resolve) => {
setResolver(() => resolve);
});
}, []);

const handleConfirm = useCallback(() => {
setIsOpen(false);
if (resolver) resolver(true);
}, [resolver]);

const handleCancel = useCallback(() => {
setIsOpen(false);
if (resolver) resolver(false);
}, [resolver]);

React.useImperativeHandle(ref, () => ({
showModal,
}));

return (
<Modal
title={title}
isOpen={isOpen}
onOpenChange={setIsOpen}
width="min-w-3xl"
>
<>
<div className="p-10">{content}</div>
<div >
<Button onClick={handleCancel}>
{cancelText}
</Button>
<Button onClick={handleConfirm}>
{confirmText}
</Button>
</div>
</>
</Modal>
);
}
);

AsyncModal.displayName = "AsyncModal";

type CreateAsyncModalResult = [React.FC, () => Promise<boolean>];

export const useAsyncModal = (
modalProps: AsyncModalProps
): CreateAsyncModalResult => {
const modalRef = React.createRef<AsyncModalRef>();

const showModal = () => {
if (modalRef.current) {
return modalRef.current.showModal();
}
throw new Error("Modal component not initialized");
};

const ModalWrapper: React.FC = () => {
return <AsyncModal {...modalProps} ref={modalRef} />;
};

return [ModalWrapper, showModal];
};
import { Button } from "../buttons/Button";
import Modal from "./Modal";

type AsyncModalProps = {
title: string;
content: ReactNode;
confirmText?: string;
cancelText?: string;
};

type AsyncModalRef = {
showModal: () => Promise<boolean>;
};

export const AsyncModal = React.forwardRef<AsyncModalRef, AsyncModalProps>(
({ title, content, confirmText = "Confirm", cancelText = "Cancel" }, ref) => {
const [isOpen, setIsOpen] = useState(false);
const [resolver, setResolver] = useState<((value: boolean) => void) | null>(
null
);

const showModal = useCallback(() => {
setIsOpen(true);
return new Promise<boolean>((resolve) => {
setResolver(() => resolve);
});
}, []);

const handleConfirm = useCallback(() => {
setIsOpen(false);
if (resolver) resolver(true);
}, [resolver]);

const handleCancel = useCallback(() => {
setIsOpen(false);
if (resolver) resolver(false);
}, [resolver]);

React.useImperativeHandle(ref, () => ({
showModal,
}));

return (
<Modal
title={title}
isOpen={isOpen}
onOpenChange={setIsOpen}
width="min-w-3xl"
>
<>
<div className="p-10">{content}</div>
<div >
<Button onClick={handleCancel}>
{cancelText}
</Button>
<Button onClick={handleConfirm}>
{confirmText}
</Button>
</div>
</>
</Modal>
);
}
);

AsyncModal.displayName = "AsyncModal";

type CreateAsyncModalResult = [React.FC, () => Promise<boolean>];

export const useAsyncModal = (
modalProps: AsyncModalProps
): CreateAsyncModalResult => {
const modalRef = React.createRef<AsyncModalRef>();

const showModal = () => {
if (modalRef.current) {
return modalRef.current.showModal();
}
throw new Error("Modal component not initialized");
};

const ModalWrapper: React.FC = () => {
return <AsyncModal {...modalProps} ref={modalRef} />;
};

return [ModalWrapper, showModal];
};
I like this pattern, it allows me to show some UI conditionally typically during a validation process. Kind of like window.confirm, but with my own UI. I am using it in my app here:
function CampaignEditor() {
//...un important code
const [OptOutWarningModal, showOptOutWarningModal] = useAsyncModal({
title: "Opt out warning",
content:
"You have not provided instructions to opt out. Are you sure you want to continue?",
confirmText: "Yes",
cancelText: "No",
});

const {
control,
handleSubmit,
setValue,
watch,
formState: { errors },
} = useForm<CampaignFormData>({
resolver: zodResolver(campaignSchema),
defaultValues: {
...initialCampaign,
Status: initialCampaign.Status || "",
},
});

const onSubmit = async (data: CampaignFormData) => {
//...unimportant code

// Warn user if they have not included an opt out instruction
if (!data.Message.toLowerCase().includes("to opt out reply stop")) {
const optOutWarning = await showOptOutWarningModal();
if (!optOutWarning) return;
}

//... more not important logic
};
function CampaignEditor() {
//...un important code
const [OptOutWarningModal, showOptOutWarningModal] = useAsyncModal({
title: "Opt out warning",
content:
"You have not provided instructions to opt out. Are you sure you want to continue?",
confirmText: "Yes",
cancelText: "No",
});

const {
control,
handleSubmit,
setValue,
watch,
formState: { errors },
} = useForm<CampaignFormData>({
resolver: zodResolver(campaignSchema),
defaultValues: {
...initialCampaign,
Status: initialCampaign.Status || "",
},
});

const onSubmit = async (data: CampaignFormData) => {
//...unimportant code

// Warn user if they have not included an opt out instruction
if (!data.Message.toLowerCase().includes("to opt out reply stop")) {
const optOutWarning = await showOptOutWarningModal();
if (!optOutWarning) return;
}

//... more not important logic
};
Finally, I mount my component:
return (
<div >
<h2>New campaign</h2>
<form
onSubmit={handleSubmit(onSubmit)}
>
<OptOutWarningModal />
//.... my form
<Button onClick={() => setCloseForm(true)} type="submit">
Save & Close
</Button>
<Button type="submit">Save</Button>
<Button onClick={() => navigate({ to: "/" })}>Cancel</Button>
</div>
</form>
</div>
);
return (
<div >
<h2>New campaign</h2>
<form
onSubmit={handleSubmit(onSubmit)}
>
<OptOutWarningModal />
//.... my form
<Button onClick={() => setCloseForm(true)} type="submit">
Save & Close
</Button>
<Button type="submit">Save</Button>
<Button onClick={() => navigate({ to: "/" })}>Cancel</Button>
</div>
</form>
</div>
);
6 replies
TTCTheo's Typesafe Cult
Created by Cody on 7/26/2024 in #questions
Tree shaking object based API SDK
Hey guys, I use this internal sdk I built all the time, it works great but Im trying to get it treehsaken correctly. Heres a small snippet of what it looks like
export const api = {
get: {
one: async (endpoint:string) => //..... get one function,
many: async (endpoint:string) => // ..... get many function,
//.... many more get functions
},
document: {
getByPath: async (path:string) => // ....Get by path function
},
// .... many more functions
}
export const api = {
get: {
one: async (endpoint:string) => //..... get one function,
many: async (endpoint:string) => // ..... get many function,
//.... many more get functions
},
document: {
getByPath: async (path:string) => // ....Get by path function
},
// .... many more functions
}
Then in my code, I import my api and use a function from the object
import {api} from "api"
const records = await api.get.many("party")
import {api} from "api"
const records = await api.get.many("party")
^^^ This is a great dx and works great for our use case. However, in the above example, in the built JS I get the API object with every function on there, including the ones I didn't use (get:many, document:getByPath and everything else on the API obj) I understand this may be an unconventional way of developing a SDK, I think classes are more common here, and maybe treeshake better? I just avoid classes at all costs. Is there a way to achieve treeshaking with this achitecture? Obviously, I could just have each function on the api exported without the api object, but the benefit of being able to type api. and having autocomplete for every function on the object is too good
7 replies
TTCTheo's Typesafe Cult
Created by Cody on 9/16/2023 in #questions
Infer useQuery .data type from TRPC
No description
3 replies
TTCTheo's Typesafe Cult
Created by Cody on 3/11/2023 in #questions
ESLint rule for not passing type into useState?
Hey guys, is there a ESLint rule I can enable to give me a error when Im not passing a type into useState? I thought this would have been standard, so maybe this isn't the correct pattern, but I would have thought best practice would be to enforce type declaration on state? Example:
const [prevBtnEnabled, setPrevBtnEnabled] = useState<boolean>(false);
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
const [prevBtnEnabled, setPrevBtnEnabled] = useState<boolean>(false);
const [nextBtnEnabled, setNextBtnEnabled] = useState(false);
I would expect the bottom one to have an error, but it doesn't. Is the top the correct pattern, or should I be doing something different?
4 replies
TTCTheo's Typesafe Cult
Created by Cody on 3/2/2023 in #questions
Using Prisma types elsewhere
This might be a really silly question, Im sure the answer is super simple, I can't find the answer in docs though (thinking about it this is probably a more Typescript question).. I have this TRPC call which returns some data with multiple relations: const parts = trpc.partDetails.getAll.useQuery(); The type for parts.data is
PartDetail & {
partTypes: PartTypes[];
parts: Part[];
cars: Car[];
})[]`
PartDetail & {
partTypes: PartTypes[];
parts: Part[];
cars: Car[];
})[]`
This works great when working with parts.data, but I want to use that type elsewhere. Right now (embarrassingly) I just paste out the entire type where I want to use it. Example:
const [selectedPart, setSelectedPart] = useState<
| (PartDetail & {
partTypes: PartTypes[];
parts: Part[];
cars: Car[];
})
| null
>(null);
const [selectedPart, setSelectedPart] = useState<
| (PartDetail & {
partTypes: PartTypes[];
parts: Part[];
cars: Car[];
})
| null
>(null);
I KNOW this can't be the right way to do it, but how can I just make the state use the type from parts.data? The closest I've got is const [selectedPart, setSelectedPart] = useState<typeof parts.data | null>(null); but I can't get it right!
1 replies
TTCTheo's Typesafe Cult
Created by Cody on 2/21/2023 in #questions
T3 app/Vercel/Planetscale Slow fetches
Hey guys, trying to troubleshoot some very slow load times on my current deployment, using full T3 stack (Planetscale too) I am just using TRPC queries in my components for fetches, (no SSR anywhere at the moment) but even then fetching is really slow, considering the quite small payload we end up returning. Im quite new to this stack so wondering what are the areas I should be looking at first. Here is an example page (ignore the page layout its broken af atm) https://parted-euro.vercel.app/listings/listing?id=clecuhyxm0000mq08m19m4cmk But the content takes so long to load in, and looking at the network tab, theres not much there. The query is quite simple too (I can share this if it might help) Here is how Im fetching the listing on the page
const router = useRouter();
const listing = trpc.listings.getListing.useQuery(
{
id: router.query.id as string,
},
{
enabled: !!router.query.id,
onSuccess: (data) => {
setMainImage(data?.images[0]?.url || "");
},
}
);
const router = useRouter();
const listing = trpc.listings.getListing.useQuery(
{
id: router.query.id as string,
},
{
enabled: !!router.query.id,
onSuccess: (data) => {
setMainImage(data?.images[0]?.url || "");
},
}
);
8 replies
TTCTheo's Typesafe Cult
Created by Cody on 1/29/2023 in #questions
React Table getting weird key error
3 replies
TTCTheo's Typesafe Cult
Created by Cody on 1/25/2023 in #questions
Get Vercel Server IP
9 replies
TTCTheo's Typesafe Cult
Created by Cody on 1/11/2023 in #questions
Vercel failing build on empty arrow function in context
6 replies
TTCTheo's Typesafe Cult
Created by Cody on 1/7/2023 in #questions
How to not pass first argument into useQuery
4 replies
TTCTheo's Typesafe Cult
Created by Cody on 12/30/2022 in #questions
How to add extra user DB fields into context
Hey guys, trying to solve authorisation. I have an isAdmin field added to my user. My idea was I would just create a TRPC middleware similar to protected procedure but check ctx.session.user.isAdmin. Issue is, only name, email, id and image is returned on ctx.session.user: I watched the Theo vid on authorisation, but thought it would be cleaner rather than an extra separate api call to just include it in the context and middleware. How can I achieve this?
6 replies
TTCTheo's Typesafe Cult
Created by Cody on 12/28/2022 in #questions
How to use useQuery...?
Uber noob question here, I seem to have a misunderstanding of useQuery. Here is all im trying to do - use it to return an array from my database, console log the data. Here is my component, when it loads it doesn't log anything (because getAllCars.data hasn't resolved yet?), as if I need to await the data (what is the pattern for this in TRPC?). If I wasn't using TRPC, I would have the cars data set to a state so I can put the loop inside a useEffect with cars state as a dependancy, however I understand the TRPC patterns are different. Looking at the useQuery docs, the useQuery example doesn't help. The data just seems to be there? Anyway the solution is presumably. very simple, hoping someone can point me in the right direction
const AddPart: React.FC<AddPartProps> = () => {

const getAllCars = trpc.cars.getAll.useQuery();
const savePart = trpc.parts.createPart.useMutation();

useEffect(() => {
getAllCars.data?.forEach((car: ICar) => {
console.log(car.make)
});

}, []);


return (
<div>Test</div>
)
export default AddPart
const AddPart: React.FC<AddPartProps> = () => {

const getAllCars = trpc.cars.getAll.useQuery();
const savePart = trpc.parts.createPart.useMutation();

useEffect(() => {
getAllCars.data?.forEach((car: ICar) => {
console.log(car.make)
});

}, []);


return (
<div>Test</div>
)
export default AddPart
11 replies
TTCTheo's Typesafe Cult
Created by Cody on 12/24/2022 in #questions
Prisma allowing empty DB fields
10 replies