-1

I'm pretty new to TypeScript, as well as using the T3 stack (React Query / Tanstack Query). I'm trying to type companyId as string, so that I don't have to type companyId as string every time I use it later on it in the code, but I can't figure out how to best to that or what the best practice is with this stack... I'm used to plain old JavaScript and useEffects to do API calls (and probably writing worse code).

Note: the following code exists at /pages/companies/[id].tsx

The following is my first attempt, but I get a "Rendered more hooks than during the previous render" error at "const { data: company} ...", which makes sense:

const CompanyPage: NextPage = () => {
  const router = useRouter()

  const companyId = router.query.id
  if (!companyId || Array.isArray(companyId)) return <div>Loading...</div> // have to check for Array.isArray because of NextJS/Typescript bug

  const { data: company } = api.companies.getSingleById.useQuery({companyId: companyId});
  if (!company ) return <div>Loading...</div>

  ...
  return (...)

I tried doing the following, but I was not allowed because the type for companyId from router.query.id is string | string[] | undefined.

const CompanyPage: NextPage = () => {
  const router = useRouter()

  const companyId: string = router.query.id // Type 'string | string[] | undefined' is not assignable to type 'string'

  const { data: company } = api.companies.getSingleById.useQuery({companyId: companyId});
  if (!company ) return <div>Loading...</div>

  ...
  return (...)

UPDATE:

I changed it to the following now, which seems to work, but it doesn't feel quite right it's the correct way to do things. (With this method, I only have to write companyId as string once, which is fine.)

const CompanyPage: NextPage = () => {
  const router = useRouter()

  const companyId = router.query.id
  const { data: company } = api.companies.getSingleById.useQuery({companyId: companyId as string});

  if (!companyId || Array.isArray(companyId)) return <div>Loading...</div> // have to check for Array.isArray because of NextJS/Typescript bug
  if (!company ) return <div>Loading...</div>

  ...
  return (...)

ANSWER:

Thank you to Fabio for the accepted answer.

I'm destructuring router.query into multiple variables on other routes, so this is an example of doing that based on the accepted answer:

const { companyId, locationId } = useMemo(() => ({
  companyId: router.query?.companyId?.toString() ?? "",
  locationId: router.query?.locationId?.toString() ?? "",
}), [router.query?.companyId, router.query?.locationId]);
amota
  • 141
  • 8
  • You've got the `useQuery` hook call after your early returns. You can't do that, it violates the rules of hooks. In your fixed version you moved it above the early returns. – Jared Smith Jun 01 '23 at 14:15
  • Does this answer your question? [Uncaught Invariant Violation: Rendered more hooks than during the previous render](https://stackoverflow.com/questions/55622768/uncaught-invariant-violation-rendered-more-hooks-than-during-the-previous-rende) – Jared Smith Jun 01 '23 at 14:18
  • @JaredSmith it explains why the first error is happening, which I do already understand. The question is more about the typing than it is that error, so it doesn't adequately answer the question. Thanks though! – amota Jun 02 '23 at 16:45

1 Answers1

2

You can use optional chaining and nullish coalescing in combination with the query params' toString() method, heres an example based on your code snippet:

const CompanyPage: NextPage = () => {
  const router = useRouter();

  // this result will be of type string
  const companyId = useMemo(() => {
    return router.query?.id?.toString?.() ?? "";
  }, [router.query?.id]);

  const { data: company } = api.companies.getSingleById.useQuery({
    companyId: companyId,
  });

  if (!company) return <div>Loading...</div>;

  return; // ...
};

The reason why the query parameters are of type string | string[] is because if you had a catch-all segment each of the url parts will be split by / and presented to you as an array.

Example

If you had the route /some-segment/[...href] and the URL would be /some-segment/200/my-post following would apply:

  • The contents of the href query variable would be ["200", "my-post"]
  • Calling href?.toString?.() would return you /some-segment/200/my-post.
Fabio Nettis
  • 1,150
  • 6
  • 18
  • You've still got the useQuery hook call after the conditional early return. That will fail in exactly the same way as the OPs code even if it is cleaner and neater. – Jared Smith Jun 01 '23 at 14:15
  • @JaredSmith Is that a hook as well? Wasn't quite clear syntactically. I have updated the original answer. – Fabio Nettis Jun 01 '23 at 14:23
  • It is, I've seen react query code like that before. – Jared Smith Jun 01 '23 at 14:40
  • This is a perfect answer, thank you. Adding the useMemo was nice, I don't use that as often as I should. Thank you! Also, let me know if my version of destructuring router.query into multiple variables is how you would do it in a similar fashion, or if you would start changing things slightly (added to post). Thanks! – amota Jun 02 '23 at 16:46