Usage with React | MapGL | 2GIS Documentation

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?

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>

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>