Integration with React

Note

The JS API supports integration with React JS only.

At the moment, there is no support for React Native.

Quick start

Warning

Supported React version: at least 16

Connecting via top-level-await

<!DOCTYPE html>
<html>
  <head>
    <!-- Substitute the value of the real key instead of YOUR_API_KEY -->
    <script src="https://api-maps.yandex.ru/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 {YMap, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer, YMapMarker, reactify} from './lib/ymaps';
import type {YMapLocationRequest} from 'ymaps3';

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

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

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

const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]);

export const reactify = ymaps3React.reactify.bindTo(React, ReactDom);
export const {YMap, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer, YMapMarker} = reactify.module(ymaps3);
{
  "compilerOptions": {
    "target": "es2017",
    "lib": ["dom", "dom.iterable", "esnext"],
    "esModuleInterop": true,
    "module": "esnext",
    "moduleResolution": "node",
    "jsx": "react-jsx",
    "typeRoots": ["./node_modules/@types", "./node_modules/@yandex/ymaps3-types"],
    "paths": {
      "ymaps3": ["./node_modules/@yandex/ymaps3-types"]
    }
  }
}
{
  "devDependencies": {
    "@types/react": "^18.3.3",
    "@types/react-dom": "^18.3.0",
    "@yandex/ymaps3-types": "^0.0.17",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-scripts": "5.0.1",
    "typescript": "^4.9.5"
  },
  "scripts": {
    "start": "react-scripts start"
  }
}

Install the dependencies and start the local server:

npm install
npm start

Open application

Specificities

  1. In package.json adding a dev-dependency on the package @yandex/ymaps3-types.

    It is recommended to install the latest version:

    npm i --save-dev @yandex/ymaps3-types@latest

  2. In tsconfig.json we set compilerOptions.typeRoots with a list of paths to file types. Adding the path to the package @yandex/ymaps3-types there, so that the namespace ymaps3 with types appears in the global scope.

    Note

    The namespace ymaps3 contains all the class types that the JS API provides, but they are not available in the runtime until the resolving ymaps3.ready.

  3. In tsconfig.json we set compilerOptions.paths, in which we inform the ts compiler that when importing the package ymaps3, its content should be searched for in the specified path. Thanks to this, you can import types in project files as if they are not in the @yandex/ymaps3-types, but in the ymaps3 package:

    import type {YMapLocationRequest} from 'ymaps3';
    

    All types must be imported from the root.

    The internal structure is not guaranteed and may change over time.

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

  5. For each object in the JS API, there is a React analog. To use the React API version, connect the @yandex/ymaps3-reactify module. In the lib/ymaps.ts file, we wait for the JS API and reactify module to be fully loaded, after which we export the necessary map components for use in other parts of the project:

    import React from 'react';
    import ReactDom from 'react-dom';
    
    const [ymaps3React] = await Promise.all([ymaps3.import('@yandex/ymaps3-reactify'), ymaps3.ready]);
    
    export const reactify = ymaps3React.reactify.bindTo(React, ReactDom);
    export const {YMap, YMapDefaultSchemeLayer, YMapDefaultFeaturesLayer} = reactify.module(ymaps3);
    

    Note

    The @yandex/ymaps3-reactify module provides a set of methods for accessing React to both individual objects and modules/packages in general. The hierarchy of objects and initialization parameters is the same.

  6. Using top-level-await in lib/ymaps.ts guarantees the execution of ymaps3.ready and ymaps3.import('@yandex/ymaps3-reactify') before importing map components, which allows you to synchronously use any JS API objects as React components:

    <YMap location={reactify.useDefault(LOCATION)}>
      <YMapDefaultSchemeLayer />
      <YMapDefaultFeaturesLayer />
      ...
    </YMap>
    

reactify.useDefault

YMap and all other components are uncontrollable by design. Components use imperative API (e.g. YMapZoomControl calls YMap.update({location})). This may cause desynchronization with props and state in React (e.g. location in YMap or coordinates on YMapMarker with enabled drag).

Use reactify.useDefault(value) to set prop once and don't update it in rerenders.
E.g. <YMap location={reactify.useDefault({center, zoom})}/> will behave like <input defaultValue={''}/>.

To precisely control prop update pass array of dependencies as a second argument to reactify.useDefault(value, deps) like in React hooks (e.g. useCallback, useMemo, useEffect).
Prop will be updated if dependencies array differs from the last one.

For object parameters value itself can be used as a dependency, if value references differ. E.g. const [location, setLocation] = useState(...), reactify.useDefault(location, [location]), setLocation({...}).

reactify.useDefault works with any prop of any component returned from reactify.

Warning

reactify.useDefault returns opaque object with no public API and MUST be used only directly in props.

Custom implementations of objects ymaps3.YMapEntity for React

Use the overrideKey key to define a custom implementation of ymaps3.YMapEntity objects for reactify:

type YMapSomeClassProps = {
  id: string;
};
export class YMapSomeClass extends ymaps3.YMapComplexEntity<YMapSomeClassProps> {
  static [ymaps3Reactify.reactify.overrideKey] = YMapSomeClassReactifyOverride;
  //...
}

and define a method for determining user implementation:

export const YMapSomeClassReactifyOverride = (
  YMapSomeClassI, // YMapSomeClass base class
  {reactify, React}
) => {
  const YMapSomeClassReactified = reactify.entity(YMapSomeClassI); // Standard reactify method
    const YMapSomeClassR = React.forwardRef((props, ref) => {
      return (<>
        <YMapSomeClassReactified {...props} ref={ref} ... />
      </>);
    })
  return YMapSomeClassR;
}

and add the resulting component to the application:

import {YMapSomeClass} from './some-class';
import React from 'react';
import ReactDOM from 'react-dom';
// ...
const ymaps3Reactify = await ymaps3.import('@yandex/ymaps3-reactify');
const reactify = ymaps3Reactify.reactify.bindTo(React, ReactDOM);
const YMapSomeClassR = reactify.entity(YMapSomeClass);

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