diff --git a/README.md b/README.md
index 6e435902..127c1ef0 100644
--- a/README.md
+++ b/README.md
@@ -54,8 +54,6 @@ There are several ways to provide a token to your app, as showcased in some of t
* Set the `MapboxAccessToken` environment variable (or set `REACT_APP_MAPBOX_ACCESS_TOKEN` if you are using Create React App)
* Provide it in the URL, e.g `?access_token=TOKEN`
-But we would recommend using something like [dotenv](https://github.com/motdotla/dotenv) and put your key in an untracked `.env` file, that will then expose it as a `process.env` variable, with much less leaking risks.
-
### Contribute
diff --git a/docs/get-started/adding-custom-data.md b/docs/get-started/adding-custom-data.md
index 3eb2e49c..c95e39a8 100644
--- a/docs/get-started/adding-custom-data.md
+++ b/docs/get-started/adding-custom-data.md
@@ -45,6 +45,11 @@ For details about data sources and layer configuration, check out the [Mapbox st
For dynamically updating data sources and layers, check out the [GeoJSON](http://visgl.github.io/react-map-gl/examples/geojson) and [GeoJSON animation](http://visgl.github.io/react-map-gl/examples/geojson-animation) examples.
+## Custom Overlays
+
+You can implement a custom HTML or SVG overlay on top of the map that redraws whenever the camera changes. By calling `map.project()` you can adjust the DOM or CSS properties so that the customly-drawn features are always aligned with the map. See a full example [here](https://github.com/visgl/react-map-gl/tree/7.0-release/examples/custom-overlay).
+
+
## Other vis.gl Libraries
For more feature rich and performant data visualization overlay use cases, you may consider using other visualization libraries. react-map-gl is part of the [vis.gl](https://www.github.com/visgl) ecosystem, a suite of high-performance data visualization tools for the Web.
diff --git a/docs/get-started/mapbox-tokens.md b/docs/get-started/mapbox-tokens.md
index f0d2570f..994446b8 100644
--- a/docs/get-started/mapbox-tokens.md
+++ b/docs/get-started/mapbox-tokens.md
@@ -10,11 +10,11 @@ To get a Mapbox token, you will need to register on [their website](https://www.
There are several ways to provide a token to your app, as showcased in some of the example folders:
-* Provide a `mapboxApiAccessToken` prop to the map component
+* Provide a `mapboxAccessToken` prop to the map component
* Set the `MapboxAccessToken` environment variable (or set `REACT_APP_MAPBOX_ACCESS_TOKEN` if you are using Create React App)
* Provide it in the URL, e.g `?access_token=TOKEN`
-But we would recommend using something like [dotenv](https://github.com/motdotla/dotenv) and put your key in an untracked `.env` file, that will then expose it as a `process.env` variable, with much less leaking risks.
+We recommend using an environment variable to minimize leaking risks. See [securing Mapbox token](/docs/get-started/tips-and-tricks.md#securing-mapbox-token) for examples.
## Display Maps Without A Mapbox Token
diff --git a/docs/get-started/tips-and-tricks.md b/docs/get-started/tips-and-tricks.md
new file mode 100644
index 00000000..352f1502
--- /dev/null
+++ b/docs/get-started/tips-and-tricks.md
@@ -0,0 +1,175 @@
+# Tips and Tricks
+
+## Securing Mapbox Token
+
+Because Mapbox tokens are required for the client application to make requests to Mapbox servers, you have to distribute it with your app. It is not possible to stop a visitor to your site from scraping the token. The practice outlined below can help you protect your token from being abused.
+
+- Never commit your token in clear text into GitHub or other source control.
+- In your local dev environment, define the token in an environment variable e.g. `MapboxAccessTokenDev=...` in the command line, or use something like [dotenv](https://github.com/motdotla/dotenv) and put `MapboxAccessTokenDev=...` in a `.env` file. Add `.env` to `.gitignore` so it's never tracked. If your app is deployed by a continuous integration pipeline, follow its documentation and set a secret environment variable.
+- Create separate tokens for development (often times on `http://localhost`), public code snippet (Gist, Codepen etc.) and production (deployed to `https://mycompany.com`). The public token should be rotated regularly. The production token should have strict [scope and URL restrictions](https://docs.mapbox.com/help/troubleshooting/how-to-use-mapbox-securely/#access-tokens) that only allows it to be used on a domain that you own.
+- Add the following to your bundler config:
+
+ ```js
+ /// webpack.config.js
+ const {DefinePlugin} = require('webpack');
+
+ module.exports = {
+ ...
+ plugins: [
+ new DefinePlugin({
+ 'process.env.MapboxAccessToken': JSON.stringify(process.env.NODE_ENV == 'production' ? process.env.MapboxAccessTokenProd : process.env.MapboxAccessTokenDev)
+ })
+ ]
+ };
+ ```
+
+ ```js
+ /// rollup.config.js
+ const replace = require('@rollup/plugin-replace').default;
+
+ module.exports = {
+ ...
+ plugins: [
+ replace({
+ 'process.env.MapboxAccessToken': JSON.stringify(process.env.NODE_ENV == 'production' ? process.env.MapboxAccessTokenProd : process.env.MapboxAccessTokenDev)
+ })
+ ]
+ };
+ ```
+
+ react-map-gl automatically picks up `process.env.MapboxAccessToken` or `process.env.REACT_APP_MAPBOX_ACCESS_TOKEN` if they are defined. Alternatively, you can use your own variable name (e.g. `__SUPER_SECRET_TOKEN__`) and pass it in manually with `mapboxAccessToken={__SUPER_SECRET_TOKEN__}`.
+
+
+## Minimize Cost from Frequent Re-mounting
+
+In a moderately complex single-page app, as the user navigates through the UI, a map component may unmount and mount again many times during a session. Consider the following layout:
+
+```jsx
+/// Example using Tabs from Material UI
+
+
+
+
+
+
+
+
+
+
+ {items.map(renderRow)}
+
+
+
+```
+
+Every time the user clicks the "table" tab, the map is unmounted. When they click the "map" tab, the map is mounted again. As of v2.0, mapbox-gl generates a [billable event](https://www.mapbox.com/pricing#maploads) every time a Map object is initialized. It is obviously not idea to get billed for just collapsing and expanding part of the UI.
+
+In this case, it is recommended that you set the [reuseMaps](/docs/api-reference/map.md#reuseMaps) prop to `true`:
+
+```jsx
+
+
+
+```
+
+This bypasses the initialization when a map is removed then added back.
+
+## Performance with Many Markers
+
+If your application uses externally managed camera state, like with Redux, the number of React rerenders may be very high when the user is interacting with the map. Consider the following setup:
+
+```jsx
+import {useSelector, useDispatch} from 'react-redux';
+import Map, {Marker} from 'react-map-gl';
+
+function MapView() {
+ const viewState = useSelector(s => s.viewState);
+ const vehicles = useSelector(s => s.vehicles);
+ const dispatch = useDispatch();
+
+ const onMove = useCallback(evt => {
+ dispatch({type: 'setViewState', payload: evt.viewState});
+ }, []);
+
+ return (
+
+ );
+}
+```
+
+This component is rerendered on every animation frame when the user is dragging the map. If it's trying to render hundreds of markers, the performance lag will become quite visible.
+
+One way to improve the performance is `useMemo`:
+
+```jsx
+ const markers = useMemo(() => vehicles.map(vehicle => (
+
+
+ )
+ )}, [vehicles]);
+
+ return (
+
+ );
+}
+```
+
+This prevents React from rerendering the markers unless they have changed.
+
+If your application can do without complicated DOM objects and CSS styling, consider switching to a [symbol layer](https://docs.mapbox.com/mapbox-gl-js/style-spec/layers/#symbol). Layers are rendered in WebGL and are much more performant than markers:
+
+```jsx
+ const vehiclesGeoJSON = useMemo(() => {
+ return {
+ type: 'FeatureCollection',
+ features: vehicles.map(vehicle => turf.point(vehicle.coordinates, vehicle))
+ };
+ }, [vehicles]);
+
+ return (
+
+ );
+```
diff --git a/docs/table-of-contents.json b/docs/table-of-contents.json
index de624371..d14d7c82 100644
--- a/docs/table-of-contents.json
+++ b/docs/table-of-contents.json
@@ -17,6 +17,7 @@
{ "entry": "docs/get-started/mapbox-tokens" },
{ "entry": "docs/get-started/state-management" },
{ "entry": "docs/get-started/adding-custom-data" }
+ { "entry": "docs/get-started/tips-and-tricks" }
]
},
{
diff --git a/examples/geocoder/src/app.tsx b/examples/geocoder/src/app.tsx
index a9265419..d77b7fce 100644
--- a/examples/geocoder/src/app.tsx
+++ b/examples/geocoder/src/app.tsx
@@ -5,6 +5,7 @@ import Map from 'react-map-gl';
import GeocoderControl from './geocoder-control';
import ControlPanel from './control-panel';
+// eslint-disable-next-line
const TOKEN = process.env.MapboxAccessToken; // Set your mapbox token here
export default function App() {
diff --git a/examples/geocoder/src/geocoder-control.tsx b/examples/geocoder/src/geocoder-control.tsx
index d846c8c3..d7dde1c6 100644
--- a/examples/geocoder/src/geocoder-control.tsx
+++ b/examples/geocoder/src/geocoder-control.tsx
@@ -47,6 +47,7 @@ type GeocoderControlProps = {
onError?: (e: object) => void;
};
+/* eslint-disable complexity,max-statements */
export default function GeocoderControl(props: GeocoderControlProps) {
const [marker, setMarker] = useState(null);
@@ -132,7 +133,7 @@ export default function GeocoderControl(props: GeocoderControlProps) {
return marker;
}
-function noop() {}
+const noop = () => {};
GeocoderControl.defaultProps = {
onLoading: noop,
diff --git a/examples/geojson/src/app.tsx b/examples/geojson/src/app.tsx
index 4a08882f..1cd8b3a7 100644
--- a/examples/geojson/src/app.tsx
+++ b/examples/geojson/src/app.tsx
@@ -20,7 +20,8 @@ export default function App() {
'https://raw.githubusercontent.com/uber/react-map-gl/master/examples/.data/us-income.geojson'
)
.then(resp => resp.json())
- .then(json => setAllData(json));
+ .then(json => setAllData(json))
+ .catch(err => console.error('Could not load data', err)); // eslint-disable-line
}, []);
const onHover = useCallback(event => {
diff --git a/examples/heatmap/src/app.tsx b/examples/heatmap/src/app.tsx
index 7deb8d22..f92b767b 100644
--- a/examples/heatmap/src/app.tsx
+++ b/examples/heatmap/src/app.tsx
@@ -43,7 +43,8 @@ export default function App() {
setTimeRange([startTime, endTime]);
setEarthQuakes(json);
selectTime(endTime);
- });
+ })
+ .catch(err => console.error('Could not load data', err)); // eslint-disable-line
}, []);
const data = useMemo(() => {
diff --git a/src/components/marker.ts b/src/components/marker.ts
index 70615bee..105d7d52 100644
--- a/src/components/marker.ts
+++ b/src/components/marker.ts
@@ -73,6 +73,7 @@ const defaultProps: Partial = {
pitchAlignment: 'auto'
};
+/* eslint-disable complexity,max-statements */
function Marker(props: MarkerProps) {
const {map, mapLib} = useContext(MapContext);
const thisRef = useRef({props});
diff --git a/src/components/popup.ts b/src/components/popup.ts
index 3f972a57..dd8f6412 100644
--- a/src/components/popup.ts
+++ b/src/components/popup.ts
@@ -69,6 +69,7 @@ function getClassList(className: string) {
return new Set(className ? className.trim().split(/\s+/) : []);
}
+/* eslint-disable complexity,max-statements */
function Popup(props: PopupProps) {
const {map, mapLib} = useContext(MapContext);
const container = useMemo(() => {
diff --git a/src/mapbox/mapbox.ts b/src/mapbox/mapbox.ts
index 4fedfe49..47eb19a6 100644
--- a/src/mapbox/mapbox.ts
+++ b/src/mapbox/mapbox.ts
@@ -502,6 +502,7 @@ export default class Mapbox {
return that;
}
+ /* eslint-disable complexity,max-statements */
_initialize(container: HTMLDivElement) {
const {props} = this;
const mapOptions = {
@@ -575,6 +576,7 @@ export default class Mapbox {
}
this._map = map;
}
+ /* eslint-enable complexity,max-statements */
recycle() {
Mapbox.savedMaps.push(this);
diff --git a/src/utils/deep-equal.ts b/src/utils/deep-equal.ts
index 663e7a2d..abb2499f 100644
--- a/src/utils/deep-equal.ts
+++ b/src/utils/deep-equal.ts
@@ -14,6 +14,7 @@ export function arePointsEqual(a?: PointLike, b?: PointLike): boolean {
return ax === bx && ay === by;
}
+/* eslint-disable complexity */
/**
* Compare any two objects
* @param a
diff --git a/src/utils/transform.ts b/src/utils/transform.ts
index 90eef252..4d286916 100644
--- a/src/utils/transform.ts
+++ b/src/utils/transform.ts
@@ -17,6 +17,7 @@ export function transformToViewState(tr: Transform): ViewState {
};
}
+/* eslint-disable complexity */
/**
* Mutate a transform to match the given view state
* @param transform