React 18+TypeScript
Tích hợp React
Hướng dẫn đầy đủ cách sử dụng component Bản đồ Việt Nam trong ứng dụng React với đầy đủ type safety và hỗ trợ hooks.
01Cài đặt
npm install @xdev-asia/vietnam-map-34-provinces highcharts highcharts-react-official
Thư viện yêu cầu highcharts và highcharts-react-official là peer dependencies. Đảm bảo bạn đã cài đặt React 18 trở lên.
02Sử dụng Cơ bản
Import component VietnamMap và đặt vào ứng dụng của bạn. Bản đồ tự động xử lý topology 34 tỉnh thành và trạng thái loading.
- Responsive chiều cao/chiều rộng
- Tự động fill container
SimpleExample.tsx
import { VietnamMap } from '@xdev-asia/vietnam-map-34-provinces/react';export default function App() {return (<div style={{ height: '600px' }}><VietnamMap /></div>);}
03Tương tác & Props
import { VietnamMap } from '@xdev-asia/vietnam-map-34-provinces/react';function InteractiveMap() {return (<VietnamMapheight={600}data={[{ 'hc-key': 'vn-new-ha-noi', value: 5000 },{ 'hc-key': 'vn-new-ho-chi-minh', value: 8000 }]}onProvinceClick={(province) => {console.log('Đã chọn:', province.name);}}colorAxis={{minColor: '#e0f2fe',maxColor: '#0284c7'}}/>);}
04Custom Data & Tooltip
Hiển thị dữ liệu riêng cho từng tỉnh
Truyền data với bất kỳ fields nào bạn muốn. Tất cả custom fields sẽ được pass vào tooltipFormatter và onProvinceClick.
function CustomDataMap() {const provinceData = [{name: 'Hà Nội',value: 8500000,population: 8500000,area: 3344,gdp: 150000,hospitals: 120,universities: 85},{name: 'Hồ Chí Minh',value: 9000000,population: 9000000,area: 9650,gdp: 280000,hospitals: 200,universities: 95}// ... các tỉnh khác];return (<VietnamMapdata={provinceData}tooltipFormatter={(point) => `<div style="padding: 12px; min-width: 220px;"><div style="font-weight: bold; font-size: 16px; margin-bottom: 8px;">📍 ${point.name}</div><table style="width: 100%; font-size: 12px;"><tr><td>Dân số:</td><td style="text-align: right;"><b>${point.population?.toLocaleString()}</b></td></tr><tr><td>Diện tích:</td><td style="text-align: right;"><b>${point.area} km²</b></td></tr><tr><td>GDP:</td><td style="text-align: right;"><b>${point.gdp?.toLocaleString()} tỷ</b></td></tr><tr><td>Bệnh viện:</td><td style="text-align: right;"><b>${point.hospitals}</b></td></tr></table></div>`}onProvinceClick={(province) => {console.log('Province data:', province);// province chứa tất cả custom fields}}/>);}
05Load Data từ API
Fetch và transform data
import { useState, useEffect } from 'react';function APIDataMap() {const [data, setData] = useState([]);const [loading, setLoading] = useState(true);useEffect(() => {async function fetchData() {try {const res = await fetch('/api/provinces/statistics');const apiData = await res.json();// Transform dataconst transformed = apiData.map(item => ({name: item.province_name,value: item.total_cases,activeCases: item.active,recovered: item.recovered,vaccinationRate: item.vaccination_rate}));setData(transformed);} catch (error) {console.error('Error:', error);} finally {setLoading(false);}}fetchData();}, []);if (loading) return <div>Đang tải...</div>;return (<VietnamMapdata={data}tooltipFormatter={(point) => `<div><b>${point.name}</b><br/>Tổng ca: ${point.value}<br/>Đang điều trị: ${point.activeCases}<br/>Đã khỏi: ${point.recovered}</div>`}/>);}
06State Management
Kết hợp với Redux/Zustand
// store/useMapStore.tsimport { create } from 'zustand';export const useMapStore = create((set) => ({selectedProvince: null,data: [],setSelectedProvince: (province) => set({ selectedProvince: province }),setData: (data) => set({ data })}));// Componentfunction MapWithStore() {const { data, selectedProvince, setSelectedProvince } = useMapStore();return (<div className="grid grid-cols-2 gap-4"><VietnamMapdata={data}onProvinceClick={setSelectedProvince}/>{selectedProvince && (<div className="p-4 bg-white rounded shadow"><h2 className="text-xl font-bold">{selectedProvince.name}</h2><p>Dân số: {selectedProvince.population?.toLocaleString()}</p><p>GDP: {selectedProvince.gdp?.toLocaleString()} tỷ</p></div>)}</div>);}
07Core Utilities
Truy xuất Dữ liệu trực tiếp
Bạn có thể truy xuất dữ liệu thô của tỉnh và xã thông qua các tiện ích core. Hữu ích để xây dựng giao diện tìm kiếm hoặc danh sách tùy chỉnh.
import {getProvinceStats,getProvinceCommunes,searchCommunes,getNewProvinceName,NEW_34_PROVINCES} from '@xdev-asia/vietnam-map-34-provinces/core';// 1. Lấy thống kê tổng quanconst stats = getProvinceStats();console.log(`Tổng số xã: ${stats.totalCommunes}`);console.log(`Tỉnh lớn nhất: ${stats.largestProvince.name}`);// 2. Lấy danh sách xã/phường của tỉnhconst communes = getProvinceCommunes('Hà Nội');console.log(`Hà Nội có ${communes.length} xã/phường`);// 3. Tìm kiếm xã phườngconst results = searchCommunes('Ba Đình');// [{ province: 'Hà Nội', commune: { code: 10101003, name: 'Phường Ba Đình' } }]// 4. Convert tên tỉnh cũ sang mớiconst newName = getNewProvinceName('Hà Giang'); // → "Tuyên Quang"// 5. Danh sách tất cả tỉnh thànhconst hcm = NEW_34_PROVINCES.find(p => p.code === '29');
08TypeScript Support
Type-safe với custom data
import type { ProvinceData } from '@xdev-asia/vietnam-map-34-provinces/react';// Define custom interfaceinterface HealthcareData extends ProvinceData {name: string;value: number;hospitals: number;doctors: number;beds: number;}function TypedMap() {const [data, setData] = useState<HealthcareData[]>([]);const handleClick = (province: HealthcareData) => {// TypeScript biết province có field hospitals, doctors, bedsconsole.log(`${province.name} có ${province.hospitals} bệnh viện`);};return (<VietnamMapdata={data}onProvinceClick={handleClick}tooltipFormatter={(point: HealthcareData) => `<div><b>${point.name}</b><br/>Bệnh viện: ${point.hospitals}<br/>Bác sĩ: ${point.doctors}<br/>Giường bệnh: ${point.beds}</div>`}/>);}
API Reference
| Prop | Type | Default | Mô tả |
|---|---|---|---|
| data | any[] | - | Dữ liệu cho từng tỉnh với name, value và custom fields |
| height | number | string | 600 | Chiều cao của container bản đồ |
| showLabels | boolean | true | Hiển thị tên tỉnh trên bản đồ |
| showZoomControls | boolean | true | Hiển thị nút zoom +/- |
| enableDrilldown | boolean | true | Cho phép click để xem cấp xã/phường |
| tooltipFormatter | (point) => string | - | Custom tooltip, nhận point data return HTML |
| onProvinceClick | (province) => void | - | Callback khi click vào tỉnh |
| hoverColor | string | #fbbf24 | Màu sắc khi hover |
| borderColor | string | #ffffff | Màu viền giữa các tỉnh |
| colorAxis | ColorAxisOptions | - | Cấu hình gradient màu (minColor, maxColor) |
| className | string | - | CSS class cho container wrapper |
| options | Highcharts.Options | - | Override toàn bộ config Highcharts |
💡 Tip: Tất cả custom fields trong
data sẽ được pass vào tooltipFormatter và onProvinceClick09Best Practices
Performance Optimization
import { useMemo, useCallback } from 'react';function OptimizedMap({ rawData }) {// Memoize transformed dataconst mapData = useMemo(() => {return rawData.map(item => ({name: item.province_name,value: item.total,...item}));}, [rawData]);// Memoize callbacksconst handleClick = useCallback((province) => {console.log('Clicked:', province.name);}, []);const tooltipFormatter = useMemo(() => {return (point) => `<div><b>${point.name}</b>: ${point.value}</div>`;}, []);return (<VietnamMapdata={mapData}onProvinceClick={handleClick}tooltipFormatter={tooltipFormatter}/>);}
Error Handling
function SafeMap({ data }) {const [error, setError] = useState(null);// Validate dataconst validatedData = useMemo(() => {try {if (!Array.isArray(data)) {throw new Error('Data must be an array');}return data.filter(item => {if (!item.name || typeof item.value !== 'number') {console.warn('Invalid item:', item);return false;}return true;});} catch (err) {setError(err.message);return [];}}, [data]);if (error) {return <div className="text-red-500">Error: {error}</div>;}return <VietnamMap data={validatedData} />;}
Responsive Design
function ResponsiveMap() {const [height, setHeight] = useState(600);useEffect(() => {const updateHeight = () => {if (window.innerWidth < 768) {setHeight(400);} else if (window.innerWidth < 1024) {setHeight(500);} else {setHeight(600);}};updateHeight();window.addEventListener('resize', updateHeight);return () => window.removeEventListener('resize', updateHeight);}, []);return (<div className="w-full"><VietnamMapheight={height}showLabels={window.innerWidth >= 768}/></div>);}
10Next.js Integration
Dynamic Import (SSR disabled)
Highcharts cần chạy client-side. Sử dụng dynamic import với ssr: false
// app/map/page.tsx'use client';import dynamic from 'next/dynamic';const VietnamMap = dynamic(() => import('@xdev-asia/vietnam-map-34-provinces/react').then(m => m.VietnamMap),{ssr: false,loading: () => (<div className="h-[600px] flex items-center justify-center">Đang tải bản đồ...</div>)});export default function MapPage() {return (<main><h1>Bản đồ Việt Nam</h1><VietnamMap height={600} /></main>);}
Server-side Data Fetching
// app/map/page.tsximport dynamic from 'next/dynamic';const VietnamMap = dynamic(() => import('@xdev-asia/vietnam-map-34-provinces/react').then(m => m.VietnamMap),{ ssr: false });export default async function MapPage() {// Fetch data server-sideconst data = await fetch('https://api.example.com/provinces', {next: { revalidate: 3600 } // Cache 1 hour}).then(r => r.json());return <VietnamMap data={data} height={600} />;}