2

I have a React/GraphQL small application working fine, and I'm trying to add TypeScript to it, as I'm new to TypeScript and trying to learn it. I have a GraphQL API endpoint that is returning products and information on those products. Using React, I'm storing products in a state and then rendering each product using JSX.

I created a type Product with the information that I expect the API to return, but it's also returning nested objects, and everything I tried triggers a TypeScript error. This is a simplified example of what the API is returning:

{
    "data": {
        "products": [
            {
                "productName": "Soda",
                "code": "sod",
                "prices": {
                    "nationalPrices": {
                        "localPrices": [
                            {
                                "pricePerUnit": "1.99"
                            }
                        ],
                        "regionalPrices": [
                            {
                                "pricePerUnit": "2.49"
                            }
                        ]
                    },
                    "vendorPrices": {
                        "localPrices": [
                            {
                                "pricePerUnit": "1.49"
                            }
                        ]
                    }
                }
            },
            // more products... 
        ]
    }
}

And this is the solution I currently have. code, productName and prices are working fine, but nationalPrices is triggering a TypeScript error property nationalPrices does not exist on type 'object'. What can I do to fix this error?

type nationalPricesObject = {
    localPrices: object[];
}

type Product = {
    code: string;
    productName: string;
    prices: object;
    nationalPrices: nationalPricesObject;
}


function ProductList() {
    const [products, setProducts] = useState < Product[] > ([]);
    const { loading, data, error } = useQuery(LOAD_PRODUCTS);

    useEffect(() => {
        if (data) {
            setProducts(data.products);
        }
    }, [data]);

    return (
        <>
            <div>
                {products.map(p =>
                (
                    <div>
                        <ProductCard
                            productName={p.displayName}
                            code={p.code}
                            // simplified code below for sake of clearity 
                            pricePerUnit={p.prices.nationalPrices.localPrices[0]['pricePerUnit']}
                        />
                    </div>
                ))}
            </div>
        </>
    )
}
2
  • 1
    Side note, you'll love this, saves all the hassle with typescript types when working with GraphQl... npmjs.com/package/@graphql-codegen/cli Commented Nov 19, 2021 at 15:58
  • In the template, you're referencing product.prices.nationalPrices which doesn't exist according to your types above. You typed Product.prices as just being a plain object. Following @James answer will fix this. Commented Nov 19, 2021 at 16:09

2 Answers 2

2

Product doesn't contain a nationalPrices property. Product.prices does. We can set a type Prices which contains the stuff expected in Product.prices, and use that to define the prices property of the Product object.

type Prices = {
    nationalPrices: nationalPricesObject;
    vendorPrices: someOtherPricesObject;
}

type Product = {
    code: string;
    productName: string;
    prices: Prices;
}
Sign up to request clarification or add additional context in comments.

Comments

2

Usage of object is tricky in typescript, and generally not recommended. You have two problems here:

  1. object is an ambiguous type, so you cannot use prices.nationalPrices because nationalPrices does not exist on type object
  2. Once you fix this, you will also encounter an error that localPrices[0] which is of type object does not have pricePerUnit

To fix these, make you types more specific:

type nationalPricesObject = {
    localPrices: { pricePerUnit: number; }[]; // assuming this is number?
}

type Product = {
    code: string;
    productName: string;
    prices: nationalPricesObject;
}

Lastly, by convention, types in typescript should be PascalCase, and interfaces should be preferred. So, I would change your code to look like:

interface Price {
  pricePerUnit: number;
}

interface NationalPrices {
    localPrices: Price[];
}

interface Product {
    code: string;
    productName: string;
    prices: NationalPrices;
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.