• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visgl / react-map-gl / 14340993733

08 Apr 2025 06:31PM UTC coverage: 85.047% (+0.09%) from 84.959%
14340993733

Pull #2524

github

web-flow
Merge c5bf25568 into e4d2dfa73
Pull Request #2524: Fix addSource condition

951 of 1175 branches covered (80.94%)

Branch coverage included in aggregate %.

18 of 18 new or added lines in 1 file covered. (100.0%)

1 existing line in 1 file now uncovered.

5584 of 6509 relevant lines covered (85.79%)

69.19 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.43
/modules/react-mapbox/src/components/source.ts
1
import * as React from 'react';
1✔
2
import {useContext, useEffect, useMemo, useCallback, useState, useRef, cloneElement} from 'react';
1✔
3
import {MapContext} from './map';
1✔
4
import assert from '../utils/assert';
1✔
5
import {deepEqual} from '../utils/deep-equal';
1✔
6

1✔
7
import type {
1✔
8
  SourceSpecification,
1✔
9
  CanvasSourceSpecification,
1✔
10
  ImageSourceSpecification,
1✔
11
  VectorSourceSpecification
1✔
12
} from '../types/style-spec';
1✔
13
import type {MapInstance} from '../types/lib';
1✔
14
import type {
1✔
15
  GeoJSONSourceImplementation,
1✔
16
  ImageSourceImplementation,
1✔
17
  AnySourceImplementation
1✔
18
} from '../types/internal';
1✔
19

1✔
20
export type SourceProps = (SourceSpecification | CanvasSourceSpecification) & {
1✔
21
  id?: string;
1✔
22
  children?: any;
1✔
23
};
1✔
24

1✔
25
let sourceCounter = 0;
1✔
26

1✔
27
function createSource(map: MapInstance, id: string, props: SourceProps) {
6✔
28
  if (map.isStyleLoaded()) {
6✔
29
    const options = {...props};
4✔
30
    delete options.id;
4✔
31
    delete options.children;
4✔
32
    // @ts-ignore
4✔
33
    map.addSource(id, options);
4✔
34
    return map.getSource(id);
4✔
35
  }
4✔
36
  return null;
2✔
37
}
2✔
38

1✔
39
/* eslint-disable complexity */
1✔
40
function updateSource(source: AnySourceImplementation, props: SourceProps, prevProps: SourceProps) {
10✔
41
  assert(props.id === prevProps.id, 'source id changed');
10✔
42
  assert(props.type === prevProps.type, 'source type changed');
10✔
43

10✔
44
  let changedKey = '';
10✔
45
  let changedKeyCount = 0;
10✔
46

10✔
47
  for (const key in props) {
10✔
48
    if (key !== 'children' && key !== 'id' && !deepEqual(prevProps[key], props[key])) {
36✔
49
      changedKey = key;
1✔
50
      changedKeyCount++;
1✔
51
    }
1✔
52
  }
36✔
53

10✔
54
  if (!changedKeyCount) {
10✔
55
    return;
9✔
56
  }
9✔
57

1✔
58
  const type = props.type;
1✔
59

1✔
60
  if (type === 'geojson') {
1✔
61
    (source as GeoJSONSourceImplementation).setData(props.data);
1✔
62
  } else if (type === 'image') {
10!
63
    (source as ImageSourceImplementation).updateImage({
×
64
      url: props.url,
×
65
      coordinates: props.coordinates
×
66
    });
×
67
  } else if ('setCoordinates' in source && changedKeyCount === 1 && changedKey === 'coordinates') {
×
68
    source.setCoordinates((props as unknown as ImageSourceSpecification).coordinates);
×
69
  } else if ('setUrl' in source && changedKey === 'url') {
×
70
    source.setUrl((props as VectorSourceSpecification).url);
×
71
  } else if ('setTiles' in source && changedKey === 'tiles') {
×
72
    source.setTiles((props as VectorSourceSpecification).tiles);
×
73
  } else {
×
74
    // eslint-disable-next-line
×
75
    console.warn(`Unable to update <Source> prop: ${changedKey}`);
×
76
  }
×
77
}
10✔
78
/* eslint-enable complexity */
1✔
79

1✔
80
export function Source(props: SourceProps) {
1✔
81
  const map = useContext(MapContext).map.getMap();
16✔
82
  const propsRef = useRef(props);
16✔
83
  const [, setStyleLoaded] = useState(0);
16✔
84

16✔
85
  const id = useMemo(() => props.id || `jsx-source-${sourceCounter++}`, []);
16!
86
  /* global setTimeout */
16✔
87
  const forceUpdate = useCallback(
16✔
88
    () => setTimeout(() => setStyleLoaded(version => version + 1), 0),
16✔
89
    []
16✔
90
  );
16✔
91

16✔
92
  useEffect(() => {
16✔
93
    if (map) {
2✔
94
      // Fired on initial load signaling the map is ready to add custom sources
2✔
95
      // Subsequently fired on style changes
2✔
96
      map.on('styledata', forceUpdate);
2✔
97
      forceUpdate();
2✔
98

2✔
99
      return () => {
2✔
100
        map.off('styledata', forceUpdate);
2✔
101
        // @ts-ignore
2✔
102
        if (map.style && map.style._loaded && map.getSource(id)) {
2✔
103
          // Parent effects are destroyed before child ones, see
2✔
104
          // https://github.com/facebook/react/issues/16728
2✔
105
          // Source can only be removed after all child layers are removed
2✔
106
          const allLayers = map.getStyle()?.layers;
2✔
107
          if (allLayers) {
2✔
108
            for (const layer of allLayers) {
2✔
109
              // @ts-ignore (2339) source does not exist on all layer types
1✔
110
              if (layer.source === id) {
1✔
111
                map.removeLayer(layer.id);
1✔
112
              }
1✔
113
            }
1✔
114
          }
2✔
115
          map.removeSource(id);
2✔
116
        }
2✔
117
      };
2✔
118
    }
2!
UNCOV
119
    return undefined;
×
120
  }, [map]);
16✔
121

16✔
122
  // @ts-ignore
16✔
123
  let source = map && map.style && map.getSource(id);
16✔
124
  if (source) {
16✔
125
    updateSource(source, props, propsRef.current);
10✔
126
  } else {
16✔
127
    source = createSource(map, id, props);
6✔
128
  }
6✔
129
  propsRef.current = props;
16✔
130

16✔
131
  useEffect(() => {
16✔
132
    if (!source) {
5✔
133
      // on `styledata` event, `map.isStyleLoaded()` still returns false.
1✔
134
      // `load` and `style.load` only fire once and not when `isStyleLoaded` changes from true to false to true.
1✔
135
      // `sourcedata` potentially suggests that `isStyleLoaded` has changed. But it fires on every tile load.
1✔
136
      // Unsubscribe once source is added.
1✔
137
      map.on('sourcedata', forceUpdate);
1✔
138
      return () => {
1✔
139
        map.off('sourcedata', forceUpdate);
1✔
140
      };
1✔
141
    }
1✔
142
    return undefined;
4✔
143
  }, [map, source]);
16✔
144

16✔
145
  return (
16✔
146
    (source &&
16✔
147
      React.Children.map(
14✔
148
        props.children,
14✔
149
        child =>
14✔
150
          child &&
8✔
151
          cloneElement(child, {
8✔
152
            source: id
8✔
153
          })
8✔
154
      )) ||
14✔
155
    null
8✔
156
  );
16✔
157
}
16✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc