Building a Reactive Vue Map Component
This challenge focuses on creating a reusable Vue component that displays a map and allows for dynamic updates to its markers. You will implement a component that leverages Vue's reactivity system to ensure that changes to the map's data are reflected instantly on the displayed map. This is crucial for applications that require real-time location tracking, user-generated content with map pins, or any scenario where map data needs to be updated dynamically.
Problem Description
Your task is to create a Vue component, written in TypeScript, that renders a map and allows for the addition, removal, and updating of markers on that map. The component should be designed to be reactive, meaning that any changes made to the data source for the markers should automatically update the map.
Key Requirements:
- Component Structure: Create a Vue 3 component (using the Composition API) named
ReactiveMap. - Map Integration: Integrate with a popular JavaScript mapping library (e.g., Leaflet, Mapbox GL JS, Google Maps JavaScript API). For simplicity, Leaflet is recommended for this challenge.
- Marker Data: The component should accept an array of marker objects as a prop. Each marker object should have at least
id,lat(latitude),lng(longitude), andtooltip(text to display on hover/click) properties. - Reactivity: The map and its markers must be reactive. When the prop array of markers changes (e.g., items added, removed, or updated), the map should reflect these changes without manual re-rendering or explicit function calls.
- Initialization: The map should initialize to a default view (e.g., a specific zoom level and center) when the component is first mounted.
- Marker Display: Each marker in the provided data should be rendered on the map.
- Tooltip Functionality: Clicking on a marker should display its associated tooltip.
Expected Behavior:
- When the
ReactiveMapcomponent is mounted, it should initialize a map instance and display it in a designated container. - Initial markers provided via props should be rendered on the map.
- If markers are added to the prop array, new markers should appear on the map.
- If markers are removed from the prop array, corresponding markers should disappear from the map.
- If a marker's
lat,lng, ortooltipproperties are updated in the prop array, the corresponding marker on the map should update its position or tooltip accordingly.
Edge Cases to Consider:
- What happens if the initial marker data is empty?
- What happens if marker data contains invalid latitude or longitude values? (For this challenge, assume valid numbers are provided).
- How to handle potentially large numbers of markers efficiently? (While not the primary focus, consider basic performance implications).
Examples
Example 1: Initial Map Setup and Marker Addition
// Parent Component (Vue SFC)
<template>
<div>
<button @click="addMarker">Add Marker</button>
<ReactiveMap :markers="mapMarkers" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ReactiveMap from './components/ReactiveMap.vue'; // Assuming ReactiveMap is in components folder
interface Marker {
id: number;
lat: number;
lng: number;
tooltip: string;
}
const mapMarkers = ref<Marker[]>([
{ id: 1, lat: 51.505, lng: -0.09, tooltip: 'London' },
{ id: 2, lat: 40.7128, lng: -74.0060, tooltip: 'New York' },
]);
let nextId = 3;
const addMarker = () => {
mapMarkers.value.push({
id: nextId++,
lat: Math.random() * 85, // Random latitude
lng: Math.random() * 180 - 90, // Random longitude
tooltip: `New Marker ${nextId - 1}`,
});
};
</script>
Output:
A map centered around a default location, displaying two markers: one for "London" and one for "New York". Clicking the "Add Marker" button adds a new random marker to the map, and its tooltip is visible on hover/click.
Explanation:
The parent component initializes mapMarkers with two predefined markers. These markers are passed as a prop to ReactiveMap. The addMarker function dynamically adds a new marker to the mapMarkers ref. Because mapMarkers is a reactive ref and passed as a prop, the ReactiveMap component will automatically detect this change and update the map to include the new marker.
Example 2: Marker Update and Removal
// Parent Component (Vue SFC) - continuing from Example 1
<template>
<div>
<button @click="addMarker">Add Marker</button>
<button @click="updateFirstMarker">Update First Marker</button>
<button @click="removeLastMarker">Remove Last Marker</button>
<ReactiveMap :markers="mapMarkers" />
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ReactiveMap from './components/ReactiveMap.vue';
interface Marker {
id: number;
lat: number;
lng: number;
tooltip: string;
}
const mapMarkers = ref<Marker[]>([
{ id: 1, lat: 51.505, lng: -0.09, tooltip: 'London' },
{ id: 2, lat: 40.7128, lng: -74.0060, tooltip: 'New York' },
]);
let nextId = 3;
const addMarker = () => {
mapMarkers.value.push({
id: nextId++,
lat: Math.random() * 85,
lng: Math.random() * 180 - 90,
tooltip: `New Marker ${nextId - 1}`,
});
};
const updateFirstMarker = () => {
if (mapMarkers.value.length > 0) {
mapMarkers.value[0].lat = 48.8566; // Paris Latitude
mapMarkers.value[0].lng = 2.3522; // Paris Longitude
mapMarkers.value[0].tooltip = 'Paris (Updated)';
}
};
const removeLastMarker = () => {
if (mapMarkers.value.length > 0) {
mapMarkers.value.pop();
}
};
</script>
Output:
Initially, the map shows "London" and "New York". Clicking "Update First Marker" changes the first marker's position to Paris and updates its tooltip. Clicking "Remove Last Marker" removes the "New York" marker from the map.
Explanation:
The updateFirstMarker function directly modifies the properties of the first object in the mapMarkers array. The removeLastMarker function uses pop() to remove the last element. Vue's reactivity system will detect these changes to the array and its objects, triggering the ReactiveMap component to update the corresponding markers on the map.
Constraints
- The
ReactiveMapcomponent must be implemented using Vue 3 and TypeScript. - You must use the Composition API (
setupfunction or<script setup>). - You should use Leaflet.js for map rendering. You will need to install it (
npm install leaflet @types/leaflet). - The
markersprop should be typed asArray<Marker>, whereMarkeris an interface definingid(number),lat(number),lng(number), andtooltip(string). - The map container element should have a minimum height for visibility.
- The initial map center and zoom level can be hardcoded within the component for simplicity.
Notes
- Map Initialization: You'll need to initialize the Leaflet map instance within the
onMountedlifecycle hook of your Vue component. Make sure to clean up the map instance inonUnmountedto prevent memory leaks. - Marker Management: For reactivity, you'll likely need to use Vue's
watchorwatchEffectto observe changes in themarkersprop. When changes are detected, you'll need to add new markers, remove old ones, and update existing ones on the Leaflet map instance. Consider using a mapping between marker IDs and their Leaflet layer representations for efficient updates. - Styling: Basic CSS will be required to give the map container a defined size.
- Library Choice: While Leaflet is recommended, if you have strong familiarity with another mapping library and can meet all requirements, that may be acceptable. However, be prepared to justify your choice and ensure it integrates well with Vue's reactivity.
- Tooltips: Leaflet provides a
Tooltipclass that can be bound to markers.