Usage with React | MapGL | 2GIS Documentation

Usage with React

There are two main difficulties in using MapGL with React:

  1. Rerendering of the map
  2. Accessing the map from other components

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. Do not forget to change the JavaScript Preprocessor to "Babel" if you are trying to run it on CodePen.

<!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>

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. Do not forget to change the JavaScript Preprocessor to "Babel" if you are trying to run it on CodePen.

<!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>

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.

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.