React

Warning

Supported React version: 16 or higher.

There is a React analog for every imperative API class inherited from MMapEntity. To use the React API version, connect the @mappable-world/mappable-reactify module:

const mappableReactify = await mappable.import('@mappable-world/mappable-reactify');
const reactify = mappableReactify.reactify.bindTo(React, ReactDOM);
const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker} = reactify.module(mappable);

Note

The @mappable-world/mappable-reactify module provides a set of React access methods both for individual objects and modules/packages as a whole. The hierarchy of objects and initialization parameters are the same for most classes.

After connecting the module, use MMapEntity descendant objects as React components:

<MMap location={{center: [25.229762, 55.289311], zoom: 9}} mode="vector">
  <MMapDefaultSchemeLayer />
  <MMapDefaultFeaturesLayer />

  <MMapMarker coordinates={[25.229762, 55.289311]} draggable={true}>
    <section>
      <h1>You can drag this header</h1>
    </section>
  </MMapMarker>
</MMap>

Note

The JS API supports integration only with React JS.

React Native is currently not supported.

Quick start

Connecting via top-level-await

<!DOCTYPE html>
<html>
  <head>
    <!-- Replace YOUR_API_KEY with the real key -->
    <script src="https://js.api.mappable.world/v3/?apikey=YOUR_API_KEY&lang=en_US"></script>
  </head>
  <body>
    <div id="root"></div>
  </body>
</html>
import ReactDOM from 'react-dom/client';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement);
root.render(<App />);
import {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker, reactify} from './lib/mappable';
import type {MMapLocationRequest} from 'mappable';

const LOCATION: MMapLocationRequest = {
  center: [25.229762, 55.289311],
  zoom: 9
};

export default function App() {
  return (
    <div style={{width: '600px', height: '400px'}}>
      <MMap location={reactify.useDefault(LOCATION)}>
        <MMapDefaultSchemeLayer />
        <MMapDefaultFeaturesLayer />

        <MMapMarker coordinates={reactify.useDefault([25.229762, 55.289311])} draggable={true}>
          <section>
            <h1>You can drag this header</h1>
          </section>
        </MMapMarker>
      </MMap>
    </div>
  );
}
import React from 'react';
import ReactDom from 'react-dom';

const [mappableReact] = await Promise.all([mappable.import('@mappable-world/mappable-reactify'), mappable.ready]);

export const reactify = mappableReact.reactify.bindTo(React, ReactDom);
export const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer, MMapMarker} = reactify.module(mappable);
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "react-jsx",
    "typeRoots": ["./node_modules/@types", "./node_modules/@mappable-world/mappable-types"],
    "paths": {
      "mappable": ["./node_modules/@mappable-world/mappable-types"]
    }
  }
}
{
  "devDependencies": {
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@mappable-world/mappable-types": "^0.0.10",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-scripts": "5.0.1",
    "typescript": "^4.9.5"
  },
  "scripts": {
    "start": "react-scripts start"
  }
}

Set dependencies and run a local server:

npm install
npm start

Open the app

Specifics

  1. In package.json, add a dev dependency on the @mappable-world/mappable-types package.

    We recommend installing the latest version:

    npm i --save-dev @mappable-world/mappable-types@latest

  2. In tsconfig.json, set compilerOptions.typeRoots with a list of paths to type files. Add a path to the @mappable-world/mappable-types package there to make the mappable namespace with types appear in the global scope.

    Note

    The mappable namespace contains all the class types provided by the JS API, but they are not available in the runtime environment until mappable.ready is resolved.

  3. In tsconfig.json, set compilerOptions.paths, informing the TS compiler that the contents of the imported mappable package should be searched for at the specified path. This enables you to import types in project files as if they were located not at @mappable-world/mappable-types, but in the mappable package:

    import type {MMapLocationRequest} from 'mappable';
    

    All types must be imported from the root.

    The internal structure isn't guaranteed and can change over time.

  4. In tsconfig.json, for top-level-await to operate correctly, the compilerOptions.module parameter must be set to one of the following values: es2022, esnext, system, or preserve. The compilerOptions.target parameter must be set to es2017 or higher.

  5. Connect the @mappable-world/mappable-reactify module. In lib/mappable.ts, wait until the JS API and Reactify module are fully loaded and then export the necessary map components so that they can be used in other parts of the project:

    import React from 'react';
    import ReactDom from 'react-dom';
    
    const [mappableReact] = await Promise.all([mappable.import('@mappable-world/mappable-reactify'), mappable.ready]);
    
    export const reactify = mappableReact.reactify.bindTo(React, ReactDom);
    export const {MMap, MMapDefaultSchemeLayer, MMapDefaultFeaturesLayer} = reactify.module(mappable);
    

    Note

    The @mappable-world/mappable-reactify module provides a set of React access methods both for individual objects and modules/packages as a whole. The hierarchy of objects and initialization parameters are the same.

  6. The use of top-level-await in lib/mappable.ts guarantees that mappable.ready and mappable.import('@mappable-world/mappable-reactify') are executed before the map components are imported, allowing any JS API objects to be used as React components synchronously:

    <MMap location={reactify.useDefault(LOCATION)}>
      <MMapDefaultSchemeLayer />
      <MMapDefaultFeaturesLayer />
      ...
    </MMap>
    

reactify.useDefault

MMap and all other components are uncontrolled. Components use the imperative interface of the library (for example, MMapZoomControl calls MMap.update({location})). This may cause desynchronizing with the state and parameters in React (for example, location for MMap or coordinates for MMapMarker with dragging enabled).

Use reactify.useDefault(value) to set a component parameter only once and not update it on re-renders. For example, <MMap location={reactify.useDefault({center, zoom})}/> will behave as <input defaultValue={''}/>.

To control parameter updates, use the second reactify.useDefault(value, deps) parameter: an array of dependencies as in React hooks (for example, useCallback, useMemo, useEffect). The parameter will be updated if the dependency array is different.

For parameters with objects, the value itself can be used as a dependency. Re-rendering occurs only if the values are different. For example, const [location, setLocation] = useState(...), reactify.useDefault(location, [location]), setLocation({...}).

reactify.useDefault works with any parameters of any components from reactify.

Warning

reactify.useDefault returns an object with no public contract and should be used only directly in component parameters.

Custom implementations of mappable.MMapEntity objects for React

Use the overrideKey key to determine custom implementations of mappable.MMapEntity objects for reactify:

type MMapSomeClassProps = {
  id: string;
};
export class MMapSomeClass extends mappable.MMapComplexEntity<MMapSomeClassProps> {
  static [mappableReactify.reactify.overrideKey] = MMapSomeClassReactifyOverride;
  //...
}

and the method for determining a custom implementation:

export const MMapSomeClassReactifyOverride = (
  MMapSomeClassI, // base MMapSomeClass class
  {reactify, React}
) => {
  const MMapSomeClassReactified = reactify.entity(MMapSomeClassI); // Standard reactify method
    const MMapSomeClassR = React.forwardRef((props, ref) => {
      return (<>
        <MMapSomeClassReactified {...props} ref={ref} ... />
      </>);
    })
  return MMapSomeClassR;
}

and add the resulting component to the app:

import {MMapSomeClass} from './some-class';
import React from 'react';
import ReactDOM from 'react-dom';
// ...
const mappableReactify = await mappable.import('@mappable-world/mappable-reactify');
const reactify = mappableReactify.reactify.bindTo(React, ReactDOM);
const MMapSomeClassR = reactify.entity(MMapSomeClass);

function App() {
  return <MMapSomeClassR id="some_id" />;
}
Next