5

The sample below is a simplified excerpt where a child component emits events based on mouse behaviours. React then should update the DOM according to the emitted events.

function SimpleSample() {
  const [addons, setAddons] = React.useState<any>({
    google: new GoogleMapsTile('google'),
  })
  const [tooltip, setTooltip] = React.useState<null | { text: string[]; x; y }>(null)
  React.useEffect(() => {
    // ...
  }, [])
  const mapEventHandle = React.useCallback(
    (event: MapEvents) => {
      console.log('event', event.type, tooltip) // LOG 1
      if (event.type === MapEventType.mouseoverPopup_show) {
        setTooltip({ text: event.text, x: event.coordinates[0], y: event.coordinates[1] })
      } else if (event.type === MapEventType.mouseoverPopup_move) {
        if (tooltip) setTooltip({ ...tooltip, x: event.coordinates[0], y: event.coordinates[1] })
      } else if (event.type === MapEventType.mouseoverPopup_hide) {
        setTooltip(null)
      }
    },
    [tooltip]
  )
  console.log('render', tooltip) // LOG 2

  return <MapComponent addons={addons} onEvent={mapEventHandle} />
}

The following order of events is expected:

  1. mouseoverPopup_show is emitted, then tooltip changed from null to a value, a rerender occurs
  2. mouseoverPopup_move is emitted, then tooltip is updated, triggering a rerender

What actually is happening:

  • Logpoint LOG 2 logs the updated value of tooltip (correct)
  • When mapEventHandle is called again, the value of tooltip inside that closure (logpoint LOG 1) is never updated, being always null.

Am I missing somethinig? Using the wrong hook?

Here's a codesandbox for it

https://codesandbox.io/s/blissful-torvalds-wm27f

EDIT: On de codesandbox sample setTooltip is not even triggering a rerender

wkrueger
  • 1,281
  • 11
  • 21
  • 1
    This is working: https://codesandbox.io/s/inspiring-gauss-bnvdc. I just removed `event.coordinates` from the code because it will not be defined in your example (these events are no actual mouse events). – Hinrich Aug 22 '19 at 19:07
  • Oh was tricked by just looking at codesandbox's console and not at the browser one, yeah it is working... Well, that error got introduced while creating the example. Im gonna have a closer look at the source... – wkrueger Aug 22 '19 at 19:15

3 Answers3

2

Thanks for the help folks, the issue seems to be down inside a dependency of <MapComponent/>. It ended up saving a reference to the old callback on construction. Still a caveat to watch for, and which i probably wouldnt face with class components...

//something like this
class MapComponent {
   emitter = this.props.onChange //BAD
   emitter = (...i) => this.props.onChange(...i) //mmkay
}
wkrueger
  • 1,281
  • 11
  • 21
1

I think event.coordinates is undefined so event.coordinates[0] causes an error.

If you do: setTooltip({working:'fine'}); you'll get type errors but it does set the toolTip state and re renders.

HMR
  • 37,593
  • 24
  • 91
  • 160
  • yeah I failed while adapting the code to the question... taking a closer look at the source, might edit it later, ty – wkrueger Aug 22 '19 at 19:16
0

Thanks to your answer it helped me debug mine which was a bit different. Mine was not working because the callback reference was kept in a state value of the child component.

const onElementAdd = useCallBack(...)..
<Dropzone onElementAdded={props.onElementAdded} />

export const Dropzone = (props: DropzoneProps): JSX.Element => {
    const [{ isOver }, drop] = useDrop(
        () => ({
        accept: props.acceptedTypes,
        drop: (item: BuilderWidget, monitor): void => {
            if (monitor.didDrop()) return;
            props.onElementAdded(item);
        },
    }),
    // missed props.onElementAdded here
    [props.onElementAdded, props.acceptedTypes, props.disabled],
);
Quentin
  • 1,361
  • 1
  • 9
  • 8