newline-sandbox / petfinder-nextjs

A statically generated Next.js application built with TypeScript and the Petfinder API.
4 stars 0 forks source link

Type error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type #1

Closed hellojennifertran closed 2 years ago

hellojennifertran commented 2 years ago

Am I missing something? I'm on part II and get this when I run npm run build (note: it runs okay with npm run dev):

./components/TypeCard.tsx:21:35
Type error: Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ Dog: { image: { url: string; }; photographer: { name: string; url: string; }; }; Cat: { image: { url: string; styles: { backgroundPosition: string; }; }; photographer: { name: string; url: string; }; }; Rabbit: { ...; }; ... 4 more ...; Barnyard: { ...; }; }'.
  No index signature with a parameter of type 'string' was found on type '{ Dog: { image: { url: string; }; photographer: { name: string; url: string; }; }; Cat: { image: { url: string; styles: { backgroundPosition: string; }; }; photographer: { name: string; url: string; }; }; Rabbit: { ...; }; ... 4 more ...; Barnyard: { ...; }; }'.

  19 |         className="w-40 h-40 flex-shrink-0 mx-auto rounded-full bg-cover bg-no-repeat"
  20 |         style={{
> 21 |           backgroundImage: `url(${ANIMAL_TYPES[type.name].image.url})`,
     |                                   ^
  22 |           backgroundPosition: "center",
  23 |           ...ANIMAL_TYPES[type.name].image.styles,
  24 |         }}
kenchandev commented 2 years ago

Hmm, I tried reproducing this issue by cloning the repository, changing the branch to part-1 and following the steps in the second blog post, but I was unable to reproduce it.

Are you able to provide a link to the project's repository?

Double check that TypeCardProps interface is defined correctly in components/TypeCard.tsx:

export interface TypeCardProps {
  className?: string;
  type: AnimalType;
}

And AnimalType interface in shared/interfaces/petfinder.interface.ts:

export interface AnimalType {
  id?: string;
  name: any;
  coats: string[];
  colors: string[];
  genders: string[];
  breeds?: AnimalTypeBreed[];
  _links: {
    self: {
      href: string;
    };
    breeds: {
      href: string;
    };
  };
}

Also log the types returned from the Petfinder API to verify that the application receives valid data (in pages/index.tsx):

console.log(types);

return {
    props: {
      types:
        types.length > 0
          ? types.map((type) => ({
              ...type,
              id: type._links.self.href.match(/\/types\/([\w-]+)$/)[1],
            }))
          : types,
    },
  };

If everything above looks good, then off the top of my head, one way you can solve this problem is to specify that type.name can be a key of the type of ANIMAL_TYPES, like so:

backgroundImage: `url(${ANIMAL_TYPES[type.name as keyof typeof ANIMAL_TYPES].image.url})`

But then, the TS compiler might complain about the remaining lines in the file that access ANIMAL_TYPES[type.name].

So you can also try this solution, which explicitly implements an interface for the ANIMAL_TYPES enum (in enums/index.ts):

interface AnimalTypesEnum {
  [key: string]: {
    image: {
      url: string;
      styles?: {
        backgroundPosition?: string;
        objectPosition?: string;
      };
    };
    photographer: {
      name: string;
      url: string;
    };
  };
}

export const ANIMAL_TYPES: AnimalTypesEnum = {
  // ...
};
hellojennifertran commented 2 years ago

Thanks for your response!

For part 1, I ran the command npx create-next-app@latest --ts to automatically scafford a Next.js project with TypeScript then proceeded with the steps in your blog (so I didn't clone the repo). Maybe something went wrong along the way there, but here's the repo: https://github.com/hellojennifertran/petfinder-nextjs

Don't worry about spending effort on this though! I can try cloning your repo and go from there (although I do get a 401 when I do this, despite having the correct creds). But will work through that later.

kenchandev commented 2 years ago

@hellojennifertran Ah, I see what the cause is. My tsconfig.json must be outdated since the strict compiler option is not set to true.

So the fix here is to explicitly annotate the ANIMAL_TYPES enum:

(enums/index.ts)

interface AnimalTypesEnum {
  [key: string]: {
    image: {
      url: string;
      styles?: {
        backgroundPosition?: string;
        objectPosition?: string;
      };
    };
    photographer: {
      name: string;
      url: string;
    };
  };
}

export const ANIMAL_TYPES: AnimalTypesEnum = {
  // ...
};

I'll push a fix for this. Thanks for pointing this out!

wonkenstein commented 2 years ago

Hey,

Thanks @kenchandev! I had the same issue and your suggestions in comment fixed it

I did have another type issue after this though with pages/_app.tsx and adding it here in case anyone hits the same issue.

  1 | import "../styles/globals.css"; // If you have not yet installed TailwindCSS in the Next.js application, then consult the documentation for installing TailwindCSS in a Next.js application: https://tailwindcss.com/docs/guides/nextjs.
  2 | 
> 3 | function App({ Component, pageProps }) {
    |                ^
  4 |     return (
  5 |         <div className="bg-gray-100 py-8 min-h-screen">
  6 |             <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">

Importing AppProps and setting the type fixed it

import "../styles/globals.css";
import type { AppProps } from 'next/app'

function App({ Component, pageProps }: AppProps) {

It's all working now with npm run build

kenchandev commented 2 years ago

Moving forward, the tutorial series will make use of strict: true in the TypeScript configuration so that the project aligns as if it was scaffolded with create-next-app. The blog posts, and along with the code in their respective branches, have been updated with the changes needed for strict: true.