Using React
Using Mappedin JS with your own map requires a Pro license. Try a demo map for free or refer to the Pricing page for more information.
Mappedin publishes a version of the JS package which is compatible with React applications. The package can be found here on NPM @mappedin/react-sdk.
The Mappedin React SDK exports convenient JSX components to render and manipulate the 3D map. When using @mappedin/react-sdk, also install and import @mappedin/mappedin-js.
Example:
import { MapView } from "@mappedin/react-sdk"
import { MapView as MapViewJS } from "@mappedin/mappedin-js"
...
<MapView onLoad={(mapView: MapViewJS) => {}} />
Coding with AI
Mappedin JS provides an llms-mappedin-js.txt file that can be used to help with coding when using Large Language Models (LLMs).
Expand for instructions on how to use Mappedin provided llms.txt files with Cursor, Github Copilot, JetBrains AI Assistant and Windsurf.
How to use llms.txt files with LLMs:
- Cursor
- Github Copilot
- JetBrains AI Assistant
- Windsurf
To use the llms.txt file with Cursor:
- Download the
llms.txtfile for this guide. - Create a
.cursordirectory in your project root if it doesn't exist already. - Place the
llms.txtfile in the.cursordirectory. - Start creating a new Cursor rule using Cursor's instructions.
- Set the rule type to:
Agent Requested. - Set the agent description to:
Use this rule whenever a question is asked about Mappedin, Mappedin-JS, Mappedin SDK.. - In the rule body, add the name of the llms.txt file such as:
@llms.txtor@llms-mappedin-js.txt - Optionally, if using Mappedin JS add :
@index.d.ts, referring to theindex.d.tsfile located in thenode_modules/@mappedin/mappedin-js/lib/esm/directory to add all Mappedin types to the rule. - With this configuration, Cursor will automatically use the
llms.txtwhenever a question is asked aboutMappedin,Mappedin-JSorMappedin SDKs. When starting a new chat be sure to mentionMappedinto trigger the rule. - The
llms.txtfile includes:- Detailed explanations of concepts
- Code examples and their context
- Related documentation references
- Source file locations for verification
This helps ensure that Cursor provides assistance that is consistent with Mappedin documentation and best practices, using the same structured information that's available on the Mappedin Developer Portal.
To use the llms.txt file with GitHub Copilot Chat in VS Code:
- Download the
llms.txtfile for this guide. - Create a
.copilotdirectory in your project root if it doesn't exist already. - Place the
llms.txtfile in the.copilotdirectory. - When using Copilot Chat, you can reference the file by saying "Please use the context from the llms.txt file in the .copilot directory".
- The
llms.txtfile includes:- Detailed explanations of concepts
- Code examples and their context
- Related documentation references
- Source file locations for verification
This helps ensure that Copilot provides assistance that is consistent with documentation and best practices, using the same structured information that's available on the Mappedin Developer Portal.
To use the llms.txt file with JetBrains AI Assistant:
- Download the
llms.txtfile for this guide. - Create a
.idea/aidirectory in your project root if it doesn't exist already. - Place the
llms.txtfile in the.idea/aidirectory. - When using the AI Assistant, you can reference the file by saying "Please use the context from the llms.txt file in the .idea/ai directory".
- The
llms.txtfile includes:- Detailed explanations of concepts
- Code examples and their context
- Related documentation references
- Source file locations for verification
This helps ensure that the AI Assistant provides assistance that is consistent with documentation and best practices, using the same structured information that's available on the Mappedin Developer Portal.
To use the llms.txt file with Windsurf in VS Code:
- Download the
llms.txtfile for this guide. - Create a
.windsurfdirectory in your project root if it doesn't exist already. - Place the
llms.txtfile in the.windsurfdirectory. - When using Windsurf Chat, you can reference the file by saying "Please use the context from the llms.txt file in the .windsurf directory".
- The
llms.txtfile includes:- Detailed explanations of concepts
- Code examples and their context
- Related documentation references
- Source file locations for verification
This helps ensure that Windsurf provides assistance that is consistent with documentation and best practices, using the same structured information that's available on the Mappedin Developer Portal.
Local Development
For local development, start a project using Vite. Refer to the Vite Getting Started guide for setup details. Guides are written in TypeScript (JavaScript), as the SDK is written in Typescript and uses comprehensive types.
1. Create a Project
Run these shell commands to set up a new project and install Mappedin JS with React.
Use the same version of Mappedin JS @mappedin/mappedin-js as the Mappedin React SDK @mappedin/react-sdk. They are released with matching versions to ensure compatibility.
With Yarn
yarn create vite mappedin-quickstart --template react-ts
cd mappedin-quickstart
yarn add @mappedin/react-sdk
yarn add @mappedin/mappedin-js
With NPM
npm create vite@latest mappedin-quickstart -- --template react-ts
cd mappedin-quickstart
npm add @mappedin/react-sdk
npm add @mappedin/mappedin-js
2. Update index.html, App.tsx
Modify & update the contents of index.html to match the following.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Mappedin JS v6 Getting Started</title>
<style>
* {
margin: 0;
padding: 0;
}
html,
body {
width: 100%;
height: 100%;
}
#root {
height: 100%;
width: 100%;
position: relative;
}
</style>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Modify & update the contents of App.tsx file under the src directory to match the following.
import React from 'react';
import { MapView, useMapData, useMap, Label } from '@mappedin/react-sdk';
function MyCustomComponent() {
const { mapData } = useMap();
return mapData.getByType('space').map(space => {
return <Label target={space.center} text={space.name} />;
});
}
export default function App() {
// See Demo API key Terms and Conditions
// https://developer.mappedin.com/docs/demo-keys-and-maps
const { isLoading, error, mapData } = useMapData({
key: 'mik_yeBk0Vf0nNJtpesfu560e07e5',
secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022',
mapId: '65c0ff7430b94e3fabd5bb8c',
});
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
return mapData ? (
<MapView mapData={mapData}>
<MyCustomComponent />
</MapView>
) : null;
}
3. Run the Project
To run the demo (with hotloading), use the following command:
With Yarn
yarn run dev
With NPM
npm run dev
This should result in a prompt showing the project being served at http://127.0.0.1:5173 (default port). The 3D rendered map can be zoomed, panned and rotated via mouse or fingers.
MapView
The MapView component contains the DOM element of the 3D map and the context wrapper for control of the map. Components which reference the 3D map must be children of the MapView component to have access to the context. The Mappedin CSS must be imported alongside the MapView.
import React from 'react';
import { MapView, useMapData, useMap, Label } from '@mappedin/react-sdk';
function MyCustomComponent() {
const { mapView, mapData } = useMap();
return mapData.getByType('space').map(space => {
return <Label target={space.center} text={space.name} />;
});
}
export default function App() {
// See Demo API key Terms and Conditions
// https://developer.mappedin.com/docs/demo-keys-and-maps
const { isLoading, error, mapData } = useMapData({
key: 'mik_yeBk0Vf0nNJtpesfu560e07e5',
secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022',
mapId: '65c0ff7430b94e3fabd5bb8c',
});
if (isLoading) {
return <div>Loading...</div>;
}
if (error) {
return <div>{error.message}</div>;
}
return mapData ? (
<MapView mapData={mapData} style={{ width: '650px', height: '650px' }}>
<MyCustomComponent />
</MapView>
) : null;
}
useMapData and useMap
The useMapData hook fetches and hydrates new Map Data from Mappedin servers. It is recommended to only do this once at the beginning of the application to limit the amount of network requests for the user.
It returns an object containing isLoading, error, and mapData. These can be used to monitor the state of the fetch and display feedback for the user.
// See Demo API key Terms and Conditions
// https://developer.mappedin.com/docs/demo-keys-and-maps
const { isLoading, error, mapData } = useMapData({
key: 'mik_yeBk0Vf0nNJtpesfu560e07e5',
secret: 'mis_2g9ST8ZcSFb5R9fPnsvYhrX3RyRwPtDGbMGweCYKEq385431022',
mapId: '65c0ff7430b94e3fabd5bb8c',
});
The useMap hook returns an object containing the mapView and mapData once the 3D map has been rendered. Once the MapData has been initially fetched and passed to the <MapView>, useMap should be used to reference the hydrated data. useMap must be used in a child component of the <MapView>.
function MyCustomComponent() {
const { mapView, mapData } = useMap();
return mapData.getByType('space').map(space => {
return <Label target={space.center} text={space.name} />;
});
}
useMapViewEvent
The useMapViewEvent hook enables subscribing to interactive map events and triggering a callback when they occur.
For example, the following snippet subscribes to the click event and updates state using the click coordinate. Labels are rendered based on the updated state. The click event contains all the interactive elements that the click action passed through.
See full JavaScript guides about Interactivity and the Camera for more information about events.
function MyCustomComponent() {
const { mapView, mapData } = useMap();
const [labels, setLabels] = useState([]);
useMapViewEvent('click', event => {
setLabels(prevLabels => [
...prevLabels,
{ target: event.coordinate, text: 'Hello, World!' },
]);
});
return labels.map((label, idx) => <Label key={idx} {...label} />);
}
Other useful events include hover, camera-change, and floor-change.
The hover event is very similar to the click event and will contain all the interactive elements that are underneath the mouse cursor.
useMapViewEvent('hover', event => {
console.log('hover', event);
});
The camera-change event fires when the camera is adjusted. It contains the new bearing, pitch, zoomLevel and camera center coordinate.
useMapViewEvent('camera-change', event => {
console.log('camera-change', event.bearing, event.pitch, event.zoomLevel, event.center);
});
The floor-change event fires whenever the building floor is changed. It contains the floor and the reason that the change occurred.
useMapViewEvent('floor-change', event => {
console.log('floor-change', event.floor, event.reason);
});
Optimizing User Interface Performance
To optimize performance and avoid unnecessary re-renders, it's important to use React's memoization techniques. Rendering a MapView can be resource-intensive, especially when dealing with complex maps that include many components. Ensure that components only re-render when necessary, and avoid patterns that trigger cascading re-renders in sub-components.
Avoid defining component properties inline—this can lead to new references on every render. Instead, define them as constants, memoized callbacks (useCallback), or memoized values (useMemo) when appropriate.
Labels
Refer to Labels guide for more information and examples
The Label component renders a2D point of interest label on the map. Typically, they are used to show the name of each Space in the building. Labelling each Space can be achieved by iterating through mapData.getByType('space') and returning a Label for each item.
function MyCustomComponent() {
const { mapData } = useMap();
// Get all spaces with names
const spaces = mapData.getByType('space').filter(space => space.name);
const options = { interactive: true };
return (
<>
{spaces.map(space => (
<Label
key={space.externalId}
target={space}
text={space.name} // label text
options={options} // makes the label interactive
/>
))}
</>
);
}
Dynamic Visibility
Labels can be dynamically enabled or disabled using the enabled option. When a label is disabled, it will be hidden from view but remain in the map's memory:
const [isEnabled, setIsEnabled] = useState(false);
return (
<Label
target={space}
text="Dynamic Label"
options={{
enabled: isEnabled, // label will be hidden when false
}}
/>
);
The CodeSandbox examples below demonstrates how to dynamically enable and disable Labels based on user zoom level. Zoom in and out to show secondary labels:
CodeSandbox - Dynamic Label visibility on zoom
Markers
Refer to Markers guide for more information and examples
The Marker component creates a DOM element to a coordinate on the map. React components can be provided as children of a Marker with their own state and interactivity. The interactive property of a Marker can be set to true, false or pointer-events-auto.
false- The Marker is not interactive.true- The Marker is interactive and the click event is captured by theuseMapViewEvent("click", (event) => {});event handler.pointer-events-auto- The Marker is interactive and mouse events are passed to the Marker's HTML content.
function MyCustomComponent() {
const { mapData } = useMap();
// Get all spaces with names
const spaces = mapData.getByType('space').filter(space => space.name);
const options = { interactive: true };
return (
<>
{spaces.map(space => (
<Marker key={space.externalId} target={space} options={options}>
<div
style={{
borderRadius: '10px',
backgroundColor: '#fff',
padding: '5px',
boxShadow: '0px 0px 1px rgba(0, 0, 0, 0.25)',
fontFamily: 'sans-serif',
}}
>
{space.name}
</div>
</Marker>
))}
</>
);
}
Animated Markers
The AnimatedMarker component allows for smooth transitions when updating marker positions:
function AnimatedMarkers() {
const { mapData } = useMap();
const [markerCoordinate, setMarkerCoordinate] = useState<Mappedin.Coordinate | null>(
mapData?.mapCenter || null
);
// Handle map clicks to update the marker's coordinate
useMapViewEvent('click', event => {
setMarkerCoordinate(event.coordinate);
});
const options = { interactive: true, anchor: 'right' };
return (
<>
{markerCoordinate && (
<AnimatedMarker target={markerCoordinate} duration={1000} options={options}>
<div>Animated Marker</div>
</AnimatedMarker>
)}
</>
);
}
The CodeSandbox examples below demonstrates how to display and animate a custom marker. Click around the map to animate the marker to the clicked location using default animation options:
CodeSandbox - Animate Markers
Dynamic Visibility
Like labels, markers can be dynamically enabled or disabled using the enabled option:
const [isEnabled, setIsEnabled] = useState(false);
return (
<Marker
target={space}
options={{
enabled: isEnabled, // marker will be hidden when false
}}
>
<div>Dynamic Marker</div>
</Marker>
);
Paths
Paths can be drawn from A to B using the Path component. The Path component requires directions which must be created between two mapData objects.
The following example creates a Path from the first space in the data to the second.
See full JavaScript guide on Wayfinding for more information.
import { Path, useMap } from "@mappedin/react-sdk";
import { useEffect, useState } from "react";
function MyCustomComponent() {
const { mapData, mapView } = useMap();
const [directions, setDirections] = useState(null);
const space1 = mapData
.getByType("space")
.find((space) => space.name.includes("Focus"));
const space2 = mapData
.getByType("space")
.find((space) => space.name.includes("Elm"));
useEffect(() => {
if (space1 == null || space2 == null) {
return;
}
const getDirections = async () => {
try {
const result = await mapView.getDirections(space1, space2);
setDirections(result);
} catch (error) {
console.error("Error getting directions:", error);
setDirections(null);
}
};
getDirections();
}, [mapView, space1, space2]);
if (space1 == null || space2 == null) {
return null;
}
return directions ? (
<Path
coordinate={directions.coordinates}
options={{ color: "goldenrod" }}
/>
) : null;
}
Navigation
The Navigation component appears similar to the Path but with extra functionality to handle floor changes, start and end Markers, and styling.
See full JavaScript guide on Wayfinding for more information.
import { Navigation, useMap } from "@mappedin/react-sdk";
import { useEffect, useState } from "react";
function MyCustomComponent() {
const { mapData, mapView } = useMap();
const [directions, setDirections] = useState(null);
const space1 = mapData
.getByType("space")
.filter((space) => space.floor.id === mapView.currentFloor.id)[0];
const space2 = mapData
.getByType("space")
.filter((space) => space.floor.id === mapView.currentFloor.id)[10];
useEffect(() => {
if (!space1 || !space2) {
return;
}
const getDirections = async () => {
try {
const result = await mapView.getDirections(space1, space2);
setDirections(result);
} catch (error) {
console.error("Error getting directions:", error);
setDirections(null);
}
};
getDirections();
}, [mapView, space1, space2]);
return directions ? <Navigation directions={directions} /> : null;
}
Shapes
The Shapes component takes a GeoJSON feature collection and renders it to the map in 3D.
function MyCustomComponent() {
return (
<Shape
geometry={featureCollection}
style={{
color: "red",
altitude: 0.2,
height: 2,
opacity: 0.7,
}}
/>
);
}
const featureCollection = {
type: "FeatureCollection",
features: [
{
type: "Feature",
properties: {},
geometry: {
type: "Polygon",
coordinates: [
..., // Some valid GeoJSON coordinates
],
},
},
],
};
Models
The Model component adds an instanced GLB/GLTF model to the map. In the example below, a model is added to each space that contains the word "Multipurpose" in its name.
See full JavaScript guide on 3D Models for more information.
function MyCustomComponent() {
const { mapData } = useMap();
const multiPurposeSpaces = mapData
.getByType('space')
.filter(space => space.name.includes('Multipurpose'));
const modelUrl = 'https://myDomain.com/myModel.glb';
const options = {
scale: [1, 1, 1],
rotation: [90, 0, 0],
opacity: 0.5,
};
return multiPurposeSpaces.map((space: Mappedin.Space) => {
return (
<Model
key={space.id}
target={space.center}
url={modelUrl}
options={options}
/>
);
});
}
Full Featured Example
The following CodeSandbox implements all the features mentioned above.