@silicon.js/map
v1.0.4
Published
A React Native map management package built on top of `react-native-maps` for Expo with a powerful provider-based architecture for managing regions, markers, and selections.
Maintainers
Readme
Map Package
A React Native map management package built on top of react-native-maps for Expo with a powerful provider-based architecture for managing regions, markers, and selections.
Features
- 🗺️ Easy map region management
- 📍 Full marker CRUD operations (Create, Read, Update, Delete)
- 🎯 Marker selection with visual feedback
- 📱 User location tracking
- 🎨 Custom marker icons support
- 🔄 Automatic state synchronization
- 💾 Centralized map state management
- 🎭 TypeScript support
- ⚡ Optimized with memoized callbacks
- 📦 Expo-compatible
Installation
npx expo install react-native-mapsSetup
1. Configure app.json/app.config.js
Add the maps plugin to your Expo config:
{
"expo": {
"plugins": [
[
"expo-location",
{
"locationAlwaysAndWhenInUsePermission": "Allow $(PRODUCT_NAME) to use your location."
}
]
],
"ios": {
"bundleIdentifier": "com.yourcompany.yourapp",
"config": {
"googleMapsApiKey": "YOUR_IOS_API_KEY"
}
},
"android": {
"package": "com.yourcompany.yourapp",
"config": {
"googleMaps": {
"apiKey": "YOUR_ANDROID_API_KEY"
}
}
}
}
}2. Install location permissions (if using user location)
npx expo install expo-location3. Wrap your app with the provider
import { MapProvider } from './map';
function App() {
return <MapProvider>{/* Your app components */}</MapProvider>;
}4. Use the Map component or hook
import { Map, useMapContext } from './map';
function MapScreen() {
const { addMarker } = useMapContext();
const handleAddMarker = () => {
addMarker({
id: '1',
latitude: 37.78825,
longitude: -122.4324,
title: 'San Francisco',
description: 'City by the Bay',
});
};
return (
<View style={{ flex: 1 }}>
<Map
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
showsUserLocation
style={{ flex: 1 }}
/>
<Button title="Add Marker" onPress={handleAddMarker} />
</View>
);
}API Reference
MapProvider
Wrap your app to provide map context to all child components.
<MapProvider>{children}</MapProvider>Map Component Props
| Prop | Type | Required | Description |
| --------------------- | ---------------------------- | -------- | ----------------------------------------------- |
| initialRegion | MapRegion | No | Initial map region (lat, lng, deltas) |
| showsUserLocation | boolean | No | Show user's current location (default: false) |
| followsUserLocation | boolean | No | Center map on user location (default: false) |
| style | ViewStyle | No | Custom styles for the map container |
| onMarkerPress | (markerId: string) => void | No | Callback when marker is pressed |
useMapContext Hook
Access map state and methods throughout your app.
State
interface MapState {
region: MapRegion | null;
markers: MapMarker[];
selectedMarkerId: string | null;
}Methods
Region Management
setRegion(region: MapRegion): void- Update map viewport
Marker Management
setMarkers(markers: MapMarker[]): void- Replace all markersaddMarker(marker: MapMarker): void- Add a single markerremoveMarker(markerId: string): void- Remove marker by IDupdateMarker(markerId: string, updates: Partial<MapMarker>): void- Update marker propertiesclearMarkers(): void- Remove all markers
Selection Management
selectMarker(markerId: string | null): void- Select/deselect marker
Types
MapRegion
interface MapRegion {
latitude: number;
longitude: number;
latitudeDelta?: number;
longitudeDelta?: number;
}MapMarker
interface MapMarker {
id: string;
latitude: number;
longitude: number;
title?: string;
description?: string;
draggable?: boolean;
icon?: ReactNode;
}Usage Examples
Basic Map Display
import { Map } from './map';
function SimpleMap() {
return (
<Map
initialRegion={{
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
}}
showsUserLocation
style={{ flex: 1 }}
/>
);
}Adding Markers Dynamically
import { useMapContext } from './map';
function LocationPicker() {
const { addMarker, mapState } = useMapContext();
const handleMapPress = (coordinate) => {
const newMarker = {
id: Date.now().toString(),
latitude: coordinate.latitude,
longitude: coordinate.longitude,
title: 'New Location',
};
addMarker(newMarker);
};
return (
<View>
<Text>Total Markers: {mapState.markers.length}</Text>
{/* Map component */}
</View>
);
}Managing Multiple Markers
import { useMapContext } from './map';
function LocationManager() {
const { setMarkers, clearMarkers, mapState } = useMapContext();
const loadLocations = async () => {
const locations = await fetchLocations();
const markers = locations.map((loc) => ({
id: loc.id,
latitude: loc.lat,
longitude: loc.lng,
title: loc.name,
description: loc.address,
}));
setMarkers(markers);
};
return (
<View>
<Button title="Load Locations" onPress={loadLocations} />
<Button title="Clear All" onPress={clearMarkers} />
<Text>{mapState.markers.length} locations</Text>
</View>
);
}Marker Selection with Details
import { useMapContext, Map } from './map';
function InteractiveMap() {
const { mapState, selectMarker } = useMapContext();
const selectedMarker = mapState.markers.find((m) => m.id === mapState.selectedMarkerId);
return (
<View style={{ flex: 1 }}>
<Map
showsUserLocation
style={{ flex: 1 }}
onMarkerPress={(markerId) => {
selectMarker(markerId);
}}
/>
{selectedMarker && (
<View style={styles.detailsPanel}>
<Text style={styles.title}>{selectedMarker.title}</Text>
<Text>{selectedMarker.description}</Text>
<Button title="Close" onPress={() => selectMarker(null)} />
</View>
)}
</View>
);
}Updating Marker Properties
import { useMapContext } from './map';
function EditableMarkers() {
const { updateMarker, mapState } = useMapContext();
const handleEditMarker = (markerId: string) => {
updateMarker(markerId, {
title: 'Updated Title',
description: 'New description',
draggable: true,
});
};
return (
<View>
{mapState.markers.map((marker) => (
<TouchableOpacity key={marker.id} onPress={() => handleEditMarker(marker.id)}>
<Text>{marker.title}</Text>
</TouchableOpacity>
))}
</View>
);
}Custom Marker Icons
import { useMapContext } from './map';
import { Image } from 'react-native';
function CustomMarkerMap() {
const { addMarker } = useMapContext();
const addCustomMarker = () => {
addMarker({
id: 'custom-1',
latitude: 37.78825,
longitude: -122.4324,
title: 'Custom Marker',
icon: <Image source={require('./pin.png')} style={{ width: 30, height: 30 }} />,
});
};
return <Button title="Add Custom Marker" onPress={addCustomMarker} />;
}Centering Map on Region
import { useMapContext } from './map';
function RegionControls() {
const { setRegion } = useMapContext();
const centerOnSF = () => {
setRegion({
latitude: 37.78825,
longitude: -122.4324,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
});
};
const centerOnNY = () => {
setRegion({
latitude: 40.7128,
longitude: -74.006,
latitudeDelta: 0.0922,
longitudeDelta: 0.0421,
});
};
return (
<View>
<Button title="San Francisco" onPress={centerOnSF} />
<Button title="New York" onPress={centerOnNY} />
</View>
);
}Using with Expo Location
import { useMapContext } from './map';
import * as Location from 'expo-location';
function UserLocationMap() {
const { setRegion, addMarker } = useMapContext();
useEffect(() => {
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
alert('Permission to access location was denied');
return;
}
const location = await Location.getCurrentPositionAsync({});
const { latitude, longitude } = location.coords;
// Center map on user
setRegion({
latitude,
longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
});
// Add user marker
addMarker({
id: 'user',
latitude,
longitude,
title: 'You are here',
});
})();
}, []);
return <Map showsUserLocation style={{ flex: 1 }} />;
}Filtering Markers
import { useMapContext } from './map';
function FilteredMap() {
const { mapState, setMarkers } = useMapContext();
const [category, setCategory] = useState('all');
const filterByCategory = (cat: string) => {
setCategory(cat);
const filtered = allMarkers.filter((m) => cat === 'all' || m.category === cat);
setMarkers(filtered);
};
return (
<View>
<Button title="All" onPress={() => filterByCategory('all')} />
<Button title="Restaurants" onPress={() => filterByCategory('restaurant')} />
<Button title="Hotels" onPress={() => filterByCategory('hotel')} />
</View>
);
}Advanced Usage
Route Planning with Multiple Markers
import { useMapContext } from './map';
function RouteMap() {
const { setMarkers, mapState } = useMapContext();
const planRoute = (waypoints: Coordinate[]) => {
const markers = waypoints.map((point, index) => ({
id: `waypoint-${index}`,
latitude: point.latitude,
longitude: point.longitude,
title: `Stop ${index + 1}`,
draggable: true,
}));
setMarkers(markers);
};
return <Map style={{ flex: 1 }} />;
}Real-time Location Tracking with Expo
import { useMapContext } from './map';
import * as Location from 'expo-location';
function LocationTracker() {
const { updateMarker, setRegion } = useMapContext();
useEffect(() => {
let subscription;
(async () => {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') return;
subscription = await Location.watchPositionAsync(
{
accuracy: Location.Accuracy.High,
timeInterval: 1000,
distanceInterval: 10,
},
(location) => {
const { latitude, longitude } = location.coords;
updateMarker('user-location', { latitude, longitude });
setRegion({
latitude,
longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
});
},
);
})();
return () => subscription?.remove();
}, []);
return <Map followsUserLocation style={{ flex: 1 }} />;
}Common Use Cases
1. Store Locator
Display multiple store locations with search and filtering
2. Delivery Tracking
Show real-time delivery driver location and route
3. Property Listings
Display real estate properties on a map with details
4. Event Finder
Show nearby events with custom markers
5. Route Planning
Create multi-stop routes with draggable waypoints
6. Geo-tagging
Allow users to select locations by tapping the map
Visual Features
Marker Selection Feedback
Selected markers are highlighted (opacity: 1.0) while unselected markers are dimmed (opacity: 0.5), providing clear visual feedback.
Draggable Markers
addMarker({
id: 'draggable-1',
latitude: 37.78825,
longitude: -122.4324,
draggable: true, // Enable dragging
});Performance Optimization
- Memoized callbacks - All context methods use
useCallbackto prevent unnecessary re-renders - Efficient state updates - State updates are batched and optimized
- Selective rendering - Only affected markers re-render on updates
Expo-Specific Features
Using with Expo Location
npx expo install expo-locationimport * as Location from 'expo-location';
const { status } = await Location.requestForegroundPermissionsAsync();
const location = await Location.getCurrentPositionAsync({});Development Builds
For optimal performance and full feature support, use EAS Build or development builds:
npx expo run:ios
npx expo run:androidExpo Go Limitations
When using Expo Go:
- Limited map styling options
- Some native features may not work
- Consider using development builds for production apps
Best Practices
- Always provide unique IDs - Use UUIDs or timestamps for marker IDs
- Handle permissions - Request location permissions before enabling user location
- Optimize marker count - Consider clustering for 100+ markers
- Clean up on unmount - Clear markers when navigating away
- Test on real devices - Maps behave differently on simulators
- Handle loading states - Show indicators while fetching location data
- Provide fallbacks - Handle cases where location services are disabled
- Use development builds - Expo Go has limitations; use dev builds for full features
Platform Differences
iOS
- Requires location permissions in
app.json - Native Apple Maps integration
- Better performance with many markers
Android
- Requires Google Maps API key in
app.json - Google Maps integration
- May require Google Play Services
Troubleshooting
Map not showing
- Check API key configuration in
app.json - Verify permissions are granted
- Ensure region has valid coordinates
- Try using a development build instead of Expo Go
Markers not appearing
- Verify marker coordinates are within visible region
- Check that markers array is not empty
- Ensure marker IDs are unique
Performance issues
- Reduce number of visible markers
- Implement marker clustering
- Optimize custom marker icons
- Use development builds for better performance
Location permissions not working
import * as Location from 'expo-location';
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
Alert.alert('Permission denied', 'Location permission is required');
}Getting API Keys
Google Maps API Key
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Maps SDK for Android and iOS
- Create credentials (API Key)
- Add to
app.json
TypeScript Support
Fully typed with TypeScript for excellent IDE support and type safety:
const { mapState, addMarker }: MapContextType = useMapContext();