Search code examples
reactjstypescripttypeerrormapboxreact-map-gl

Why is the react-map-gl Source not using an object?


I'm trying to add geoJSON into a react-map-gl map using typescript

here is my code

<Source id="my-data" type="geojson" data={data}>
    <Layer {...layerStyles}/>
</Source>

the data variable is a parsed JSON file so it is an object not

the error I get is

 Overload 1 of 2, '(props: SourceProps | Readonly<SourceProps>): Source', gave the following error.
    Type 'object' is not assignable to type 'string | Feature<Geometry, GeoJsonProperties> | FeatureCollection<Geometry, GeoJsonProperties> | undefined'.
      Type '{}' is missing the following properties from type 'FeatureCollection<Geometry, GeoJsonProperties>': type, features
  Overload 2 of 2, '(props: SourceProps, context: any): Source', gave the following error.
    Type 'object' is not assignable to type 'string | Feature<Geometry, GeoJsonProperties> | FeatureCollection<Geometry, GeoJsonProperties> | undefined'.  TS2769

    123 |             onViewportChange={(nextView:typeof viewport) => setViewport(nextView)}>
    124 |                 {/* GeoJSON */}
  > 125 |                 <Source id="my-data" type="geojson" data={data}>
        |                                                     ^
    126 |                  <Layer {...layerStyles}/>
    127 |                 </Source> 
    128 |                 {markers}

And the type of the data props in the Source component is supposed to be an object as you can see from the documentation https://visgl.github.io/react-map-gl/docs/api-reference/source

if you need any more information please ask


Solution

  • Explanation

    Let's pull apart this error:

    Overload 1 of 2, '(props: SourceProps | Readonly<SourceProps>): Source'
    ...
    Overload 2 of 2, '(props: SourceProps, context: any): Source
    

    At the top level it is showing the two possible ways that you can use the Source component. Assuming you aren't assigning it a context, let's continue looking at Overload 1

    Type 'object' is not assignable to type 'string | Feature<Geometry, GeoJsonProperties> | FeatureCollection<Geometry, GeoJsonProperties> | undefined'.
    

    This is telling us that typescript thinks data is an object, but it's expecting it to be specifically a string or a Feature or a FeatureCollection (specifically features of Geometry, GeoJsonProperties)
    This tells us that object is not a specific enough type to satisfy the compiler.

    Type '{}' is missing the following properties from type 'FeatureCollection<Geometry, GeoJsonProperties>': type, features
    

    Here's it's trying to be helpful by telling us what's missing in the object type. Note that it's reduced your type to an empty object without properties.

    Solution

    So how do you tell the compiler what data is?
    I'm going to assume that you are fetching the data and so typescript is unable to infer it's type automatically

    Option 1 - use a type guard to check the incoming data

    function isFeatureCollection(data: unknown): data is FeatureCollection<Geometry, GeoJsonProperties> {
        return (
            typeof data === 'object'
            && data !== null
            && 'type' in data
            && 'features' in data
        );
    }
    

    Then use the type guard to exit out of the render, and typescript will know that after this check that data will be the type expected by Source

    if (!isFeatureCollection(data)) return null;
    
    <Source id="my-data" type="geojson" data={data}>
        <Layer {...layerStyles}/>
    </Source>
    

    Option 2 - Cast the type using as to what you expect it to be:

    const data = await getData() as FeatureCollection<Geometry, GeoJsonProperties>;
    

    This isn't recommended as you're throwing away Typescript's type guarantee here