3

I got error TS2322: Type 'unknown' is not assignable to type 'Country[]'

Error

pages/Countries/index.tsx

Full code: https://gitlab.com/fResult/countries-workshop/-/blob/bug/src/pages/Countries/index.tsx

import * as ApiCountries from '../../services/APIs/countries.api'

function Countries() {
  const findCountriesCallback = useCallback(() => ApiCountries.findAll(), [])
  const { execute: fetchCountries, value: countries } = useAsync(findCountriesCallback)

  useEffect(() => {
    ;(async () => await fetchCountries())()
  }, [fetchCountries])

  return (
    <InfiniteScroll
      items={countries}     // I don't understand why `countries` is `unknown` type
      renderEmptyList={() => (
        <div className="text-light-text dark:text-dark-text">
          No Content
        </div>
      )}
      keyExtractor={({ alpha3Code }) => alpha3Code}
      renderItem={renderItem}
      className="flex flex-col mt-8 md:flex-row md:flex-wrap gap-14 justify-between"
    />
  )
}

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

These are three files that I think according.

services/APIs/countries.api.ts

(API from https://restcountries.eu/)

const BASE_URL = 'https://restcountries.eu/rest/v2'
export async function findAll() {
  return await fetch(`${BASE_URL}/all`)
}

export async function findByName(name: string) {
  return await fetch(`${BASE_URL}/name/${name}`)
}

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

useAsync.tsx

(this hook implement to handle success and error of async function)
Full code: https://gitlab.com/fResult/countries-workshop/-/blob/bug/src/cores/hooks/useAsync.tsx

function useAsync<T, E>(
  asyncFn: (params?: any) => Promise<Response>,
  immediate: boolean = true
) {
  const [value, setValue] = useState<T | Array<T> | null>(null)
  const [error, setError] = useState<E | Response | null>(null)
  const [pending, setPending] = useState(false)

  const execute = useCallback(async (params?) => {
    setPending(true)
    try {
      const response: Response = await asyncFn(params)
      if (!response.ok) {
        setError(response)
        return
      }

      setValue(await response?.json())
    } catch (err) {
      setError(err)
    }
  }, [asyncFn])

  useEffect(() => {
    if (immediate) {
      ;(async () => await execute())()
    }
  }, [execute, immediate])

  return { execute, value, pending, error }
}

export default useAsync

xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

components/InfiniteScroll.tsx

type InfiniteScrollProps<T> = {
  items: Array<T>
  renderItem: (
    itemProps: {
      item: T
      idx: number
      array: Array<T>
      key: string | number
    },
  ) => React.ReactNode
  renderEmptyList?: () => React.ReactNode
  keyExtractor: (item: T, idx: number) => string
  className?: string
}

function InfiniteScroll<T>({
  items,
  keyExtractor,
  renderItem,
  renderEmptyList,
  className
}: InfiniteScrollProps<T>) {
  console.log({items})
  return (
    <div className={`${className ? className: ''} infinite-scroll`}>
      {renderEmptyList && !items?.length && renderEmptyList()}
      {items?.map((item, idx, array) => {
        return renderItem({ item, idx, array, key: keyExtractor(item, idx) })
      })}
    </div>
  )
}

export default InfiniteScroll

Who know how to fix this error ?

================================

Addition question after fixed above error:

I have a new question about type casting for strong type. Now, I refactor code of api function and useAsync hook to be below... but I still have the same error if I don't use an as keyword

<InfiniteScroll
  items={countries as Array<Country>}
/>

services/countries.api.ts

import axios from 'axios'

export async function findAll(params?: {
  searchField?: string
  region?: string
}) {
  if (params?.searchField || params?.region) {
    return await axios.get(`${BASE_URL}/name/${params.searchField}`)
  }
  return await axios.get(`${BASE_URL}/all`)
}

hooks/useAsync.tsx

(I have already casted strong type to be Array<T> | T before return value from this hook)

function useAsync<T, E>(
  asyncFn: (params?: any) => Promise<any>,
  immediate: boolean = true
) {
  const [value, setValue] = useState<T | Array<T> | null>(null)
  const [error, setError] = useState<E | null>(null)
  const [pending, setPending] = useState(false)

  const execute = useCallback(
    async (params?) => {
      setPending(true)
      try {
        // Type casting this line before return value from this hook
        const { data }: { data: Array<T> | T } = await asyncFn(params)
        setValue(data)
      } catch (err) {
        setError(err)
        console.error('❌ Error', err.message)
      }
    },
    [asyncFn]
  )

  useEffect(() => {
    if (immediate) {
      ;(async () => await execute())()
    }
  }, [execute, immediate])

  return { execute, value, pending, error }
}
fResult
  • 477
  • 1
  • 5
  • 17

1 Answers1

5

You need to cast to any then assign it to type

const  variable = countries as any as yourtypearray
Sam
  • 1,040
  • 1
  • 7
  • 11
  • 1
    It is worked... and I feel like it hacking at the leaves. do you have how to solve this problem at the root ? – fResult Jan 09 '21 at 18:58
  • I meant when another developer use my `InfiniteScroll` component. They must hack like this every times. `} /> ` – fResult Jan 09 '21 at 19:02
  • Yes your api findall function returns any or unknown, you can cast it your array to strong type array on that function. – Sam Jan 09 '21 at 20:02
  • I have a new question on the post. can you answer or suggest more? – fResult Jan 09 '21 at 20:54
  • 1
    Ok , You need to change to api method to generic type, which will return T[] .let me know if you need sample code. – Sam Jan 09 '21 at 21:37