react-google-maps-api icon indicating copy to clipboard operation
react-google-maps-api copied to clipboard

On drag end the OverLayView tag flickering

Open RayPaez83 opened this issue 1 year ago • 7 comments

Issue template

The new react version 18 having some issues with the react google maps api on drag end.

https://codesandbox.io/s/googlemapreact-demo-forked-5gdsib

Please provide an explanation of the issue

We were working in this issue the last days and we found that it is an issue between React 18 and the maps library because as soon you downgrade the react version to the 17 the flickering issue disappears

Your Environment

os: mac/

node --version 14.7.1

react version 18

webpack version

@babel version

@react-google-maps/api version 2.12.1

How does it behave?

When you drag en finish this the overlayview content flickering.

How should it behave correctly?

The overlayview content should stay in the same spots where the developer place it

Basic implementation of incorrect behavior in codesandbox.com

This is not my code but is some one code who has the same issue

https://codesandbox.io/s/googlemapreact-demo-forked-5gdsib

RayPaez83 avatar Aug 18 '22 16:08 RayPaez83

+1 on this behavior with Overlay Views

njho avatar Aug 22 '22 03:08 njho

Same here, with the official @react-google-maps/api library and the provided overlay code snippet from https://react-google-maps-api-docs.netlify.app/#overlayview. sandbox: https://codesandbox.io/s/simple-overlay-r3fky0

ararTP avatar Aug 24 '22 20:08 ararTP

+1. I am currently working on a commercial project with this library and the issue occurs on React 18 when dragging ends. It seems to impact all OverlayViews. Downgrading to React 17 fixes the issue, but this is not an acceptable solution.

loukamb avatar Sep 05 '22 14:09 loukamb

Good day everyone, here is a sandbox with an example using OverlayView and having the flashing issue: https://codesandbox.io/s/usememo-example-forked-vwfwrw

P.S.: Since the issue only happens with React v18, is maybe somehow related to the new "concurent renderer"? https://reactjs.org/blog/2022/03/29/react-v18.html#new-client-and-server-rendering-apis

luisoliva1 avatar Sep 06 '22 14:09 luisoliva1

@luisoliva1 There is multiple issues with your example. please do not store const in the component - move it out. Add performance optimizations with hooks and memo. Use eslint with plugins. eslint-plugin-react-perf is important to reduce re-renders. memo important to reduce re-renders too.

JustFly1984 avatar Sep 15 '22 05:09 JustFly1984

@luisoliva1 There is multiple issues with your example. please do not store const in the component - move it out. Add performance optimizations with hooks and memo. Use eslint with plugins. eslint-plugin-react-perf is important to reduce re-renders. memo important to reduce re-renders too.

How would you improve this example? you can't move the const isLoaded out of the scope, because useLoadScript is a react hook, or am I mistaken? And the other const values are not inside the component. The only thing one can store in a memo is the isLoaded ? <MapGoogle /> : <h1>Loading...</h1> part. But that does not fix the flickering.

So I definitely think there there is a problem between react 18 and the @react-google-maps.

p0thi avatar Sep 15 '22 08:09 p0thi

@JustFly1984 adding an OverlayView the traditional way (extending google.maps.OverlayView) and applying setMap(mapRef) to it works fine. Seems like a react problem.

ararTP avatar Sep 18 '22 19:09 ararTP

The problem is class component itself. it require rewrite to functional component with hooks.

JustFly1984 avatar Sep 19 '22 14:09 JustFly1984

Working example adapted from DawChihLiou blog https://hackernoon.com/building-an-airbnb-like-map-in-nextjs It seems that the difference between the two is that the one from Daw has another nested div. In the image the Overlays are the components that have left and right styling. image

Class logic

export function createOverlay(
  container: HTMLElement,
  pane: keyof google.maps.MapPanes,
  position: google.maps.LatLng | google.maps.LatLngLiteral
) {
  class Overlay extends google.maps.OverlayView {
    container: HTMLElement
    pane: keyof google.maps.MapPanes
    position: google.maps.LatLng | google.maps.LatLngLiteral

    constructor(
      container: HTMLElement,
      pane: keyof google.maps.MapPanes,
      position: google.maps.LatLng | google.maps.LatLngLiteral
    ) {
      super()
      this.container = container
      this.pane = pane
      this.position = position
    }

    onAdd(): void {
      const pane = this.getPanes()?.[this.pane]
      pane?.appendChild(this.container)
    }

    draw(): void {
      const projection = this.getProjection()
      const point = projection.fromLatLngToDivPixel(this.position)

      if (point === null) {
        return
      }

      this.container.style.transform = `translate(${point.x}px, ${point.y}px)`
    }

    onRemove(): void {
      if (this.container.parentNode !== null) {
        this.container.parentNode.removeChild(this.container)
      }
    }
  }
  return new Overlay(container, pane, position)
}

Overlay Component

import { PropsWithChildren, useContext, useEffect, useMemo } from 'react'
import { createPortal } from 'react-dom'
import MapContext from '../../map-context'
import { createOverlay } from './Overlay'

type OverlayProps = PropsWithChildren<{
  position: google.maps.LatLng | google.maps.LatLngLiteral
  pane?: keyof google.maps.MapPanes
  map: google.maps.Map
  zIndex?: number
}>

export default function OverlayView({
  position,
  pane = 'floatPane',
  zIndex,
  children,
}: OverlayProps) {
  const map = useContext(MapContext)
  const container = useMemo(() => {
    const div = document.createElement('div')
    div.style.position = 'absolute'
    return div
  }, [])

  const overlay = useMemo(() => {
    return createOverlay(container, pane, position)
  }, [container, pane, position])

  useEffect(() => {
    overlay?.setMap(map)
    return () => overlay?.setMap(null)
  }, [map, overlay])

  // to move the container to the foreground and background
  useEffect(() => {
    container.style.zIndex = `${zIndex}`
  }, [zIndex, container])

  return createPortal(children, container)
}

andrewdoro avatar Sep 22 '22 09:09 andrewdoro