reactjstypescript

Argument of type 'unknown' is not assignable to parameter of type '{}'


Here is my code

 const Res = await fetch(`https://foo0022.firebaseio.com/.json`);
        const ResObj = await Res.json();
        if (!Res.ok || !ResObj) { 
          throw new Error("Page Not Found 404");
        } 
        const ResArr = await Object.values(ResObj)
            .map(v => Object.values(v).flat())//error
            .flat()
            .filter(({ title }) => title.includes(Search))

In the line In the line I get this error ".map (v => Object.values (v) .flat ())" I get this error Argument of type 'unknown' is not assignable to parameter of type '{}'.How can this problem be solved?


Solution

  • The problem here is that you need to help TypeScript understand the types of the objects you're dealing with. The fetch API can't know in advance what the shape of the returned objects are going to be, so you have to define it and assert that the results conform to it.

    Looking at what's at https://foo0022.firebaseio.com/.json, I'd suggest something like the following:

    interface ResObj {
      Mens: {
        Hat: Clothing[];
        Jacket: Clothing[];
        Pants: Clothing[];
        Shoes: Clothing[];
        Suit: Clothing[];
      };
      New: Clothing[];
    }
    interface Clothing {
      agility: boolean[];
      alt: string;
      color: string[][];
      id: string;
      location?: string; // fix this
      Location?: string; // fix this
      material: string;
      price: string[][];
      prodState: string;
      saiz: string[][];
      shipping: string;
      sold: string;
      src: string[][];
      title: string;
      to: string;
    }
    

    But of course whether or not that's accurate depends on some kind of API documentation. Assuming that's right, you can go a bit further:

      const Res = await fetch(`https://foo0022.firebaseio.com/.json`);
      const ResObj: ResObj | undefined = await Res.json();
      if (!Res.ok || !ResObj) {
        throw new Error("Page Not Found 404");
      }
    

    Now ResObj will be known as type ResObj and you can start manipulating it. One issue is that the standard library's typings for Object.values() and Array.prototype.flat() don't reflect what you're doing with them. We can build some custom typings for them... but in this case I'll just wrap them with new functions whose types match:

      // return an array of all object values...
      // if the object is already an array, the output is the same type.
      // otherwise it's the union of all the known property types
      function vals<T extends object>(
        arr: T
      ): Array<T extends Array<infer U> ? U : T[keyof T]> {
        return Object.values(arr); // need es2017 lib for this
      }
    
      // Flatten an array by one level... 
      function flat<T>(
        arr: Array<T>
      ): Array<Extract<T, any[]>[number] | Exclude<T, any[]>> {
        return arr.flat(); // need esnext lib for this
      }
    

    Those functions typings might be confusing if you've never used TypeScript before, especially since they rely on conditional types to tease out the array properties.

    Then we can rewrite your code like this:

      const ResArr = flat(vals(ResObj).map(v => flat(vals(v)))).filter(
        ({ title }) => title.includes(Search)
      );
    

    And there are no errors, and the compiler understands that ResArr is an array of Clothing objects.

    Link to code

    Okay, hope that helps; good luck!