Anthony Garcia
Anthony Garcia

Reputation: 13

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)

Upvotes: 0

Views: 58

Answers (3)

Aleks Marinič
Aleks Marinič

Reputation: 26

I tried to reproduce your problem on a sample, but I don't get the error.

import React from 'react';

const formatKeyDisplay = (key: string) => {
    return key.toString();
};

export default function App() {
    const loan = {
        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"
    };

    return (
        <div style={{backgroundColor: "white", width: "100%", minWidth: "500px"}}>
        {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 (
                    <div className="text-black" key={loanKey}>
                    <div style={{display: "flex", gap: "15px", direction: "row"}}>
                        <div>{formatKeyDisplay(loanKey)}: </div>
                        <div>{value}</div>
                    </div>
                    </div>
                );
            }
            return null;
        })}
        </div>
    );
}

Could you make a sample Component, so that we can actualy see the problem?

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1075149

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

Upvotes: 0

Guillaume Brunerie
Guillaume Brunerie

Reputation: 5371

Just define a different variable displayValue that contains the string you want to display, instead of reusing value. It can be hard for Typescript to keep up if you reassign existing variables with something of a different type.

Upvotes: 0

Related Questions