Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add onClick event for map #52

Closed
wants to merge 1 commit into from

Conversation

jabranr
Copy link

@jabranr jabranr commented Nov 4, 2023

Hi,

This PR adds onClick event support for the map. This addresses the #49.

As part of this PR I have:

  • add an example
  • updated API reference
  • wrote unit test coverage

Unit tests
I have tried to put a basic unit test but it just won't work so I would appreciate the help/feedback with this. Here is the example I tried with:

test('onClick handler is called when map is clicked', () => {
  createMapSpy.mockReset();
  const onClick = jest.fn();
  rerender(
    <GoogleMap id={'mymap'} center={center} zoom={14} onClick={onClick} />
  );

  screen.getByTestId('map').click();
  expect(onClick).toHaveBeenCalledTimes(1);
});

@jabranr jabranr changed the title Add onClick event for map feat: add onClick event for map Nov 4, 2023
Copy link
Collaborator

@usefulthink usefulthink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First of all, thanks so much for taking the time and contributing to this project. Also congratulations, you are the very first outside contributor here 🎉

Adding the map-events was actually something that I had on my todo list for the next week, but I would be more than happy to hand this task over to you or collaborate in this PR to get it done.

I left a number of comments for you to consider with the overall vision for the event-support in mind. If you need any help, feel free to let me know, otherwise I'd try to pick it up from here and add to this PR.

@@ -127,6 +127,7 @@ interface MapProps extends google.maps.MapOptions {
id?: string;
style?: CSSProperties;
className?: string;
onClick?: (event: google.maps.Map) => void;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the events we add here should use the same signatures as the events defined in the google maps API, see here: https://developers.google.com/maps/documentation/javascript/reference/map#Map-Events

At least for now I don't think we need anything on top of providing a props-version for the events coming from the maps-API.

So in this case that would be

Suggested change
onClick?: (event: google.maps.Map) => void;
onClick?: (event: google.maps.MapMouseEvent | google.maps.MapIconEvent) => void;

/**
* Callback function that is called when the map is clicked
*/
onClick?: (event: google.maps.Map) => void;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see my note above:

Suggested change
onClick?: (event: google.maps.Map) => void;
onClick?: (event: google.maps.MapMouseEvent | google.maps.MapIconEvent) => void;

};

/**
* Component to render a Google Maps map
*/
export const Map = (props: PropsWithChildren<MapProps>) => {
export const Map = (props: MapProps & ComponentPropsWithRef<'div'>) => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't quite understand why this is added here. As far as I understand it, ComponentPropsWithRef is supposed to be used when a component wraps a regular HTML-element, but that isn't what is happening here.

If anything, we're wrapping the google.maps.Map instance (using forwardRef to expose the map-object is something I wanted to add).

@@ -160,6 +165,14 @@ function useMapInstanceHandlerEffects(
});
}

if (onClick) {
google.maps.event.addListenerOnce(newMap, 'idle', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the reasoning for waiting for the idle-event before adding the click-event?

@@ -160,6 +165,14 @@ function useMapInstanceHandlerEffects(
});
}

if (onClick) {
google.maps.event.addListenerOnce(newMap, 'idle', () => {
google.maps.event.addListener(newMap, 'click', () => {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is missing some code to remove the event-listener on unmount. For this, we could store the returned google.maps.MapsEventListener object and call it's remove() method.

Since there's going to be a lot of events, I think this should be extracted into a separate internal hook (for example useMapEventHandlers() that takes care of adding and removing event-handlers based on the props. Something similar has been done in src/components/advanced-marker.tsx.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's going to be a lot more events added and I would like to have a single example to demonstrate them. With that in mind, could you rename the example to map-events or similar?

@usefulthink
Copy link
Collaborator

usefulthink commented Nov 4, 2023

As for the unit-tests, that's a bit complicated since we don't actually have a real map-instance to work with (everything google-maps related comes from the @googlemaps/jest-mocks library).

What we need to do here is to mock emitting the events, which can get a bit complicated.

I'd try the following:

  • In the test-environment, google.maps.event.addListener is actually a jest.fn() spy.
  • After the map is initialized, we can find the callbacks passed in by the Map component using something like
    const addListenerMock = google.maps.event.addListener as jest.Mock;
    const [name, callback] = addListenerMock.calls.find(args => args[0] === 'click');`
  • after that, the callback can be called and verified that the function passed to the onClick prop is actually called

Something similar can be done to validate that event-listeners are removed on unmount and re-render.

Unfortunately we need to be very specific here about which parts of the maps-api are used since the @googlemaps/jest-mocks are fully non-functional. So using map.addListener instead of google.maps.event.addListener would break the test even though both are equivalent when it comes to the real thing. But that is something we have to live with for now.

@jabranr
Copy link
Author

jabranr commented Nov 7, 2023

@usefulthink Thank you Martin for the feedback. I will pick it up and will revert.

@usefulthink
Copy link
Collaborator

usefulthink commented Nov 8, 2023

I'm very sorry for this, but I had to move on with this topic since it's one of the biggest bits of functionality currently missing.

The implementation for full event-support is almost ready in this PR: #64
I still have to add some documentation for this and add an example. Do you want to pitch in with that?

@usefulthink
Copy link
Collaborator

Still missing tests and documentation, but the main functionality for events is now merged and available in v0.3.
Again, apologies for rushing this but I had to get this done asap.

@usefulthink usefulthink closed this Nov 9, 2023
@jabranr
Copy link
Author

jabranr commented Nov 9, 2023

No worries! I have been quite busy recently so it was hard to take time out. Appreciate the update as you went along. I will be glad to contribute to any other parts of this repo!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants