JustFly1984 / react-google-maps-api

React Google Maps API
MIT License
1.81k stars 437 forks source link

On drag end the OverLayView tag flickering #3080

Closed RayPaez83 closed 2 years ago

RayPaez83 commented 2 years ago

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

njho commented 2 years ago

+1 on this behavior with Overlay Views

ararTP commented 2 years ago

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

loukamb commented 2 years ago

+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.

luisoliva1 commented 2 years ago

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

JustFly1984 commented 2 years ago

@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.

p0thi commented 2 years ago

@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.

ararTP commented 2 years ago

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

JustFly1984 commented 2 years ago

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

andrewdoro commented 2 years ago

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)
}
tylerbecks commented 1 year ago

Using OverlayViewF worked for me!