Usage with React
This might be not so trivial to add mapgl
to your React application. The main problem is that React controls your HTML, and mapgl needs its own static container — a div element, for example, which won't be changed by React. Another question is — how to interact with that map?
Initialization
Let's assume, that you have a simple React application:
import React from 'react';
import ReactDOM from 'react-dom';
import { App } from './App';
const rootElement = document.getElementById('root');
ReactDOM.render(<App />, rootElement);
And there is the code of App
component:
import React from 'react';
export const App = () => {
return <div>My App</div>;
};
App
component is mounted inside root
element in the HTML via ReactDOM.render. If you want to add a map to your application, you need to create a component for that map:
import React from 'react';
export const Map = () => {
return <div className="map">Map will be here</div>;
};
To download a script of map initialization you can use @2gis/mapgl package from npm or just add script with mapgl API manually. Let's add MapGL API script downloading inside useEffect of Map
component:
import { load } from '@2gis/mapgl';
export const Map = () => {
useEffect(() => {
let map;
load().then((mapglAPI) => {
// container — id of the div element in your html
map = new mapglAPI.Map('map-container', {
center: [55.31878, 25.23584],
zoom: 13,
key: 'Your API access key',
});
});
// Destroy the map on unmounted
return () => map && map.destroy();
}, []);
return <div>Map will be here</div>;
};
We can not render the map inside Map
component directly, because React will rerender Map
component and the div with the map instance can be destroyed. So, we need to create a component with <div id='map-container'></div>
, which won't be rerendered by React:
const MapWrapper = React.memo(
() => {
return <div id="map-container" style={{ width: '100%', height: '100%' }}></div>;
},
() => true,
);
The function in the second argument of React.memo returns true to prevent rerendering of the component. It's like shouldComponentUpdate
which returns false.
Let's update Map
component:
export const Map = () => {
const [mapInstance, setMapInstance] = useState();
// The code of useEffect is not changed
useEffect(...)
return (
<div style={{ width: '100%', height: '100%' }}>
<MapWrapper />
</div>
);
};
Everything is ready to use Map
in App
component:
export const App = () => {
return (
<div style={{ width: '100%', height: 400 }}>
<Map />
</div>
);
};
Full example of initialization of the map in a React app (do not forget to change language in js panel from "javascript" to "Babel + JSX").
<!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">
// If you view this demo in jsfiddle do not forget to change language in js panel from "javascript" to "Babel + JSX"
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>
Interaction with the map
Let's imagine, that we need to move the map to specific coordinates by using interface of the application, outside of the map interface. For example, by clicking a button. This click has to move the map to coordinates from initialization stage. You need to have an instance of the map to interact with the map. You can store it in a React context. Let's create such context:
const MapContext = React.createContext([undefined, () => {}]);
const MapProvider = (props) => {
const [mapInstance, setMapInstance] = React.useState();
return (
<MapContext.Provider value={[mapInstance, setMapInstance]}>
{props.children}
</MapContext.Provider>
);
};
Ok, now we need to wrap App
component with MapProvider to have access to the context inside children of App
component.
ReactDOM.render(
<MapProvider>
<App />
</MapProvider>,
rootElement,
);
Now, you can store the instance of the map in the context and get it in any component you need. Let's store the instance in useEffect of Map
component:
const [_, setMapInstance] = React.useContext(MapContext);
useEffect(() => {
let map;
load().then((mapglAPI) => {
...
});
// Set map instance inside context
setMapInstance(map)
...
}, [])
And now we can use it everywhere:
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>;
};
Full example of interactions with the map in a React app (do not forget to change language in js panel from "javascript" to "Babel + JSX").
<!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">
// If you view this demo in jsfiddle do not forget to change language in js panel from "javascript" to "Babel + JSX"
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>