Usage with React
There are two main difficulties in using MapGL with React:
- Rerendering of the map
- Accessing the map from other components
Note |
---|
You can start from the sample React project |
Preventing rerendering
Let's consider a basic React app:
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
With a basic App component:
import React from 'react';
export const App = () => {
return <div>My App</div>;
};
To add a map to this app, we will create a component similar to this:
import { load } from '@2gis/mapgl';
export const Map = () => {
useEffect(() => {
let map;
load().then((mapglAPI) => {
map = new mapglAPI.Map('map-container', {
center: [55.31878, 25.23584],
zoom: 13,
key: 'Your API access key',
});
});
// Destroy the map on unmount
return () => map && map.destroy();
}, []);
return (
<div style={{ width: '100%', height: '100%' }}>
<MapWrapper />
</div>
);
};
Notice that this component does not include the container for the map (map-container
). To prevent the rerendering of the map, we will create a separate component and use React.memo to skip the rendering:
const MapWrapper = React.memo(
() => {
return <div id="map-container" style={{ width: '100%', height: '100%' }}></div>;
},
() => true,
);
The second argument is a function that determines if the last rendered result should be used instead of rendering the component again. In our case, it will always return true
.
Finally, we will update the App component and everything should be ready to go:
export const App = () => {
return (
<div style={{ width: '100%', height: 400 }}>
<Map />
</div>
);
};
You can find the full source code in the example below.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>2GIS Map API</title>
<meta name="description" content="Example of mapgl initialization in React application" />
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
html,
body,
#root {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="root" style="height: 100%;"></div>
<script type="text/babel">
const MapWrapper = React.memo(
() => {
return <div id="map-container" style={{ width: '100%', height: '100%' }}></div>;
},
() => true,
);
const MapGL = () => {
React.useEffect(() => {
const map = new mapgl.Map('map-container', {
center: [55.31878, 25.23584],
zoom: 13,
key: 'Your API access key',
});
// Destroy the map, if Map component is going to be unmounted
return () => map.destroy();
}, []);
return (
<div style={{ width: '100%', height: '100%' }}>
<MapWrapper />
</div>
);
};
const App = () => {
return (
<div style={{ width: '100%', height: '100%' }}>
<MapGL />
</div>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
</script>
</body>
</html>
Accessing the map from other components
The second difficulty is accessing the map component from another component.
Let's say, for example, that we want to make a button that will change the center of the map. This button will be in a separate component, so we need to access the map component from the button component. We can use the React Context API to do this.
First, let's create a new component and call React.createContext() to create a Context object:
const MapContext = React.createContext([undefined, () => {}]);
const MapProvider = (props) => {
const [mapInstance, setMapInstance] = React.useState();
return (
<MapContext.Provider value={[mapInstance, setMapInstance]}>
{props.children}
</MapContext.Provider>
);
};
Then we will wrap the App component with MapProvider. This way, the new context will be available to the App component and all its children components.
ReactDOM.render(
<MapProvider>
<App />
</MapProvider>,
rootElement,
);
Now that we have a context, we can use it to store the map instance. We will do that in our Map component:
export const Map = () => {
const [_, setMapInstance] = React.useContext(MapContext);
useEffect(() => {
let map;
load().then((mapglAPI) => {
map = new mapglAPI.Map('map-container', {
center: [55.31878, 25.23584],
zoom: 13,
key: 'Your API access key',
});
// Store map instance
setMapInstance(map);
});
// Destroy the map on unmount
return () => map && map.destroy();
}, []);
return (
<div style={{ width: '100%', height: '100%' }}>
<MapWrapper />
</div>
);
};
Finally, we will create a component that will use the stored map instance to recenter the map:
export const MoveMapButton = () => {
const [mapInstance] = React.useContext(MapContext);
const setInitialCenter = useCallback(() => {
if (mapInstance) {
mapInstance.setCenter([55.31878, 25.23584]);
}
}, [mapInstance]);
return <button onClick={setInitialCenter}>Set initial center</button>;
};
You can find the full source code in the example below.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>2GIS Map API</title>
<meta
name="description"
content="Example of interactions with a map in React application"
/>
<script src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script src="https://mapgl.2gis.com/api/js/v1"></script>
<!-- Don't use this in production: -->
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<style>
html,
body,
#root {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const MapContext = React.createContext([undefined, () => {}]);
const MapProvider = (props) => {
const [mapInstance, setMapInstance] = React.useState();
return (
<MapContext.Provider value={[mapInstance, setMapInstance]}>
{props.children}
</MapContext.Provider>
);
};
const MapWrapper = React.memo(
() => {
return <div id="map-container" style={{ width: '100%', height: '100%' }}></div>;
},
() => true,
);
const MapGL = () => {
const [_, setMapInstance] = React.useContext(MapContext);
React.useEffect(() => {
const map = new mapgl.Map('map-container', {
center: [55.31878, 25.23584],
zoom: 13,
key: 'Your API access key',
});
setMapInstance(map);
// Destroy the map, if Map component is going to be unmounted
return () => map.destroy();
}, []);
return (
<div style={{ width: '100%', height: '100%' }}>
<MapWrapper />
</div>
);
};
const MoveMapButton = () => {
const [mapInstance] = React.useContext(MapContext);
const setInitialCenter = React.useCallback(() => {
if (mapInstance) {
mapInstance.setCenter([55.31878, 25.23584]);
}
}, [mapInstance]);
return (
<button style={{ padding: '4px 10px' }} onClick={setInitialCenter}>
Set initial center
</button>
);
};
const App = () => {
return (
<div>
<div style={{ padding: '0 0 6px' }}>
Drag the map and then click the button <MoveMapButton />
</div>
<br />
<div style={{ width: '100%', height: 250 }}>
<MapGL />
</div>
</div>
);
};
ReactDOM.render(
<MapProvider>
<App />
</MapProvider>,
document.getElementById('root'),
);
</script>
</body>
</html>
Ready-to-use npm package
If you are using npm, take a look at the @2gis/mapgl package, which also includes TypeScript support.
You can find examples and more information in the Readme.
Example of using in side projects
The example of using MapGL in a side project you can find in github.com/city-mobil/frontend_react-2gis repository with a React wrapper implementation. Also there is a npm package of this wrapper - react-2gis.