Search code examples
typescriptnext.jsprismaradix-ui

How can I get TypeScript to stop complaining about a React rendered value being turned into a Date object?


Next.js, TypeScript, Prisma, Radix-UI project. So I have a loan object that looks something like this

{
    "id": 25,
    "borrowerName": "Test Borrower 1",
    "pipelineStage": "PROSPECT",
    "loanAmount": 500000,
    "transactionType": "PURCHASE",
    "referralSource": "Realtor Lenny",
    "borrowerEmail": "[email protected]",
    "propertyAddress": "1234 Test Way",
    "borrowerPhone": "789-546-3142",
    "purchasePrice": 700000,
    "creditScore": 700,
    "createdAt": "2024-03-15T21:46:18.347Z",
    "updatedAt": "2024-03-15T21:46:18.347Z"
}

I'm trying to build a "view single loan" page. I'm using Prisma, so createdAt and updatedAt are by default strings. Here's my code for looping over the keys and values in one go:

{Object.keys(loan || {}).map((loanKey) => {
                if (loanKey === "id") return null;

                if (loan && loanKey in loan) {
                  let value: string | number | Date | null =
                    loan[loanKey as keyof typeof loan];

                  if (loanKey === "createdAt" || loanKey === "updatedAt") {
                    value = new Date(value!).toLocaleString(undefined, {
                      year: "numeric",
                      month: "long",
                      day: "numeric",
                      hour: "numeric",
                      minute: "numeric",
                    });
                  }
                  return (
                    <Card className="text-black">
                      <Flex direction={"column"} align={"center"}>
                        <Text>{formatKeyDisplay(loanKey)}: </Text>
                        <Card>{value}</Card>
                      </Flex>
                    </Card>
                  );
                }
                return null;
              })}

I wanted to parse the createdAt and updatedAt strings out of ISO format and into something more user-friendly, so I thought to cast the value into a Date object then use .toLocaleString() to immediately cast it back into a string.

The issue with that is that I get

Type 'string | Date | null' is not assignable to type 'ReactNode'.
  Type 'Date' is not assignable to type 'ReactNode'.ts(2322)

on trying to render value presumably because TypeScript doesn't like that value becomes of type Date even if I'm parsing it back into a string immediately.

If I try to type value as so : let value: string | number | null = loan[loanKey as keyof typeof loan]; TypeScript will just complain that

Type 'string | number | Date | null' is not assignable to type 'string | number | null'.
  Type 'Date' is not assignable to type 'string | number | null'.ts(2322)

Solution

  • Use a separate variable for the unprocessed value and the processed value that you know won't be a Date:

    let rawValue: string | number | Date | null =
      loan[loanKey as keyof typeof loan];
    let value: string | number | null;
    
    if (loanKey === "createdAt" || loanKey === "updatedAt") {
      value = new Date(rawValue!).toLocaleString(undefined, {
        year: "numeric",
        month: "long",
        day: "numeric",
        hour: "numeric",
        minute: "numeric",
      });
    } else if (rawValue instanceof Date) {
      // Throw error, since the keys above are the only ones that
      // show have dates
      throw new Error(`Unexpected Date for field "${loanKey}"`);
    } else {
      // This assignment should be fine, because we weeded out `Date` above
      value = rawValue;
    }
    

    Playground link