<h1 align="center">Google Maps React Markers</h1>

<p align="center">
<a href="https://www.npmjs.com/package/google-maps-react-markers" target="_blank">
<img src="https://img.shields.io/npm/v/google-maps-react-markers.svg?style=for-the-badge&labelColor=000000" alt="NPM" />
</a>
<a href="https://npm-stat.com/charts.html?package=google-maps-react-markers" target="_blank">
<img src="https://img.shields.io/npm/dt/google-maps-react-markers.svg?style=for-the-badge&labelColor=000000" alt="NPM total downloads" title="last year">
</a>
<img src="https://img.shields.io/badge/Maintained%3F-yes-green.svg?style=for-the-badge&labelColor=000000" alt="Maintained" />
<a href="https://github.com/giorgiabosello/google-maps-react-markers/blob/master/LICENCE.md" target="_blank">
<img src="https://img.shields.io/github/license/giorgiabosello/google-maps-react-markers?style=for-the-badge&labelColor=000000" alt="GitHub license: MIT" />
</a>
<img src="https://img.shields.io/github/stars/giorgiabosello/google-maps-react-markers?style=for-the-badge&labelColor=000000" alt="GitHub stars" />
<a href="https://github.com/giorgiabosello/google-maps-react-markers/issues" target="_blank">
<img src="https://img.shields.io/github/issues/giorgiabosello/google-maps-react-markers?style=for-the-badge&labelColor=000000" alt="GitHub open issues" />
</a>
<a href="https://makeapullrequest.com/">
<img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&labelColor=000000" alt="PRs welcome" />
</a>
</p>

Google Maps library that accepts markers as react components and works with React 18+.

It supports a small set of the props of [Google Map React](https://github.com/google-map-react/google-map-react). Clustering also is possible.
The library implements [Google Maps Custom Overlays](https://developers.google.com/maps/documentation/javascript/customoverlays) official library.

**If you like this library, please consider supporting me ❤️**

[![Buy me a Coffee](https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/giorgiabosello)
[![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.me/giorgiabosello)

## 💬 Discussion

> **Released TypeScript version of the library!**

Feedbacks are welcome in [this discussion](https://github.com/giorgiabosello/google-maps-react-markers/discussions/109).

## 🚀 Demo

<a href="https://giorgiabosello.github.io/google-maps-react-markers/" target="blank">
<img src="https://img.shields.io/website?url=https%3A%2F%2Fgiorgiabosello.github.io%2Fgoogle-maps-react-markers&logo=github&style=for-the-badge&labelColor=000000" />
</a>

See it in action [here](https://giorgiabosello.github.io/google-maps-react-markers/) _(API KEY not provided)_.

Demo source code is available [here](https://github.com/giorgiabosello/google-maps-react-markers/tree/master/docs/src).

## 🛠 Install

```bash
pnpm add google-maps-react-markers
```

or

```bash
yarn add google-maps-react-markers
```

or

```bash
npm install --save google-maps-react-markers
```

## 💻 Usage

```jsx
import GoogleMap from 'google-maps-react-markers'

const App = () => {
  const mapRef = useRef(null)
  const [mapReady, setMapReady] = useState(false)

  /**
   * @description This function is called when the map is ready
   * @param {Object} map - reference to the map instance
   * @param {Object} maps - reference to the maps library
   */
  const onGoogleApiLoaded = ({ map, maps }) => {
    mapRef.current = map
    setMapReady(true)
  }

  const onMarkerClick = (e, { markerId, lat, lng }) => {
    console.log('This is ->', markerId)

    // inside the map instance you can call any google maps method
    mapRef.current.setCenter({ lat, lng })
    // ref. https://developers.google.com/maps/documentation/javascript/reference?hl=it
  }

  return (
    <>
      {mapReady && <div>Map is ready. See for logs in developer console.</div>}
      <GoogleMap
        apiKey=""
        defaultCenter={{ lat: 45.4046987, lng: 12.2472504 }}
        defaultZoom={5}
        options={mapOptions}
        mapMinHeight="100vh"
        onGoogleApiLoaded={onGoogleApiLoaded}
        onChange={(map) => console.log('Map moved', map)}
      >
        {coordinates.map(({ lat, lng, name }, index) => (
          <Marker
            key={index}
            lat={lat}
            lng={lng}
            markerId={name}
            onClick={onMarkerClick} // you need to manage this prop on your Marker component!
            // draggable={true}
            // onDragStart={(e, { latLng }) => {}}
            // onDrag={(e, { latLng }) => {}}
            // onDragEnd={(e, { latLng }) => {}}
          />
        ))}
      </GoogleMap>
    </>
  )
}

export default App
```

## 🧐 Props

### GoogleMap component

| Prop                 | Type     | Required | Default                     | Description                                                                                                                                                                           |
| -------------------- | -------- | -------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **apiKey**           | string   | **yes**  | `''`                        | API Key to load Google Maps                                                                                                                                                           |
| **defaultCenter**    | object   | **yes**  | `{ lat: 0, lng: 0 }`        | Default center of the map                                                                                                                                                             |
| **defaultZoom**      | number   | **yes**  | `1-20`                      | Default zoom of the map                                                                                                                                                               |
| libraries            | array    | no       | `['places', 'geometry']`    | Libraries to load                                                                                                                                                                     |
| options              | object   | no       | `{}`                        | Options for the map                                                                                                                                                                   |
| onGoogleApiLoaded    | function | no       | `() => {}`                  | Callback when the map is loaded                                                                                                                                                       |
| onChange             | function | no       | `() => {}`                  | Callback when the map has changed                                                                                                                                                     |
| events               | array    | no       | `[]`                        | Array of objects name/handler of [DOM events](https://en.wikipedia.org/wiki/DOM_event) to pass down to the `div` overlay. Example: `events: [{ name: 'onClick', handler: () => {} }]` |
| children             | node     | no       | `null`                      | Markers of the map                                                                                                                                                                    |
| loadScriptExternally | bool     | no       | `false`                     | Whether to load the Google Maps script externally.<br>If `true`, the `status` prop is required and it will be used to control the loading of the script                               |
| status               | string   | no       | `idle`                      | The forced status of the Google Maps script. Depends on `loadScriptExternally`.<br>It can be one of `idle`, `loading`, `ready`, `error`                                               |
| loadingContent       | node     | no       | `'Google Maps is loading'`  | Content to show while the map is loading                                                                                                                                              |
| idleContent          | node     | no       | `'Google Maps is on idle'`  | Content to show when the map is idle                                                                                                                                                  |
| errorContent         | node     | no       | `'Google Maps is on error'` | Content to show when the map has an error                                                                                                                                             |
| mapMinHeight         | string   | no       | `'unset'`                   | Min height of the map                                                                                                                                                                 |
| containerProps       | object   | no       | `{}`                        | Props for the div container of the map                                                                                                                                                |
| scriptCallback       | function | no       | `() => {}`                  | window global callback passed to the Google Script                                                                                                                                    |
| externalApiParams    | object   | no       | `undefined`                 | Optional params to pass to the Google API script. Eg. `{region: 'IT', language: 'it'}`                                                                                                |

### Markers

| Prop        | Type   | Required | Default     | Description                                                                                              |
| ----------- | ------ | -------- | ----------- | -------------------------------------------------------------------------------------------------------- |
| **lat**     | number | **yes**  | `undefined` | Latitude of the marker                                                                                   |
| **lng**     | number | **yes**  | `undefined` | Longitude of the marker                                                                                  |
| draggable   | bool   | no       | `false`     | If true, the marker can be dragged                                                                       |
| onDragStart | func   | no       | `() => {}`  | This event is fired when the user starts dragging the marker                                             |
| onDrag      | func   | no       | `() => {}`  | This event is repeatedly fired while the user drags the marker                                           |
| onDragEnd   | func   | no       | `() => {}`  | This event is fired when the user stops dragging the marker                                              |
| zIndex      | number | no       | 0           | The z-index of the marker. To bring the marker to the front, e.g. when it is active, set a higher value. |

## 📍 Clustering

For clustering, follow this [guide](https://www.leighhalliday.com/google-maps-clustering) using [useSupercluster Hook](https://github.com/leighhalliday/use-supercluster), but use bounds in this way:

```jsx
const [mapBounds, setMapBounds] = useState({
  bounds: [0, 0, 0, 0],
  zoom: 0,
})
const onMapChange = ({ bounds, zoom }) => {
  const ne = bounds.getNorthEast()
  const sw = bounds.getSouthWest()
  /**
   * useSupercluster accepts bounds in the form of [westLng, southLat, eastLng, northLat]
   * const { clusters, supercluster } = useSupercluster({
   *	points: points,
   *	bounds: mapBounds.bounds,
   *	zoom: mapBounds.zoom,
   * })
   */
  setMapBounds({ ...mapBounds, bounds: [sw.lng(), sw.lat(), ne.lng(), ne.lat()], zoom })
}
```

## 👥 Contributing

To run the project locally, clone the repo and run:

```bash
# in the root directory
yarn --frozen-install
yarn dev
```

```bash
# in another tab (with NextJS and SSR)
cd docs
yarn install
yarn dev
```

Do your changes to `src/` or `docs/src` directory, commits all files in the root directory (`yarn.lock` and ones in `dist` too) and open a PR.

## 💻 Built with

- [React](https://reactjs.org/)
- [TypeScript](https://www.typescriptlang.org/)
- [Google Maps Custom Overlays](https://developers.google.com/maps/documentation/javascript/customoverlays)
- [tsup](https://github.com/egoist/tsup): for bundling
- [ESLint](https://eslint.org/): for linting
- [Prettier](https://prettier.io/): for code formatting

## 🗒 License

MIT © [giorgiabosello](https://github.com/giorgiabosello)

## 🙏 Support

[![Buy me a Coffee](https://img.shields.io/badge/Buy_Me_A_Coffee-FFDD00?style=for-the-badge&logo=buy-me-a-coffee&logoColor=black)](https://www.buymeacoffee.com/giorgiabosello)
[![PayPal](https://img.shields.io/badge/PayPal-00457C?style=for-the-badge&logo=paypal&logoColor=white)](https://www.paypal.me/giorgiabosello)

<hr>
<p align="center">
Developed with ❤️ in Italy 🇮🇹 
</p>
