Esri / react-arcgis

A few components to help you get started using the ArcGIS API for JavaScript and esri-loader with React
Apache License 2.0
319 stars 79 forks source link

removeChild error #194

Closed CaptainCed closed 2 years ago

CaptainCed commented 4 years ago

Hello,

My application owns a menu that allows to add and remove expand widgets on the map view. The list of widgets is stored in Redux and my React main class reads this list before injecting it into the react-argis Map class. The problem is when I try to remove a widget from the map I get the following error:

Uncaught DOMException: Failed to execute 'removeChild' on 'Node': The node to be removed is not a child of this node.

Here is a sample of the class i try to remove:

import { Button } from '@material-ui/core';
import { loadModules } from 'esri-loader';
import * as React from 'react';
import { IWidgetDisplay } from '../../../utils/CommonInterfaces';

interface TestWidgetProps extends IWidgetDisplay {
  view: __esri.MapView | __esri.SceneView;
  deleteWidget(pSelectedWidget: string): void;
}

export default function TestWidget(props: TestWidgetProps) {
  const ref = React.useRef<HTMLDivElement>(null);
  const [_expandWidget, setExpandWidget] = React.useState<__esri.Expand>();

  const deleteWidget = () => {
    props.deleteWidget('Test widget'); // Call Redux to delete the widget from the widgets lits
  };

  React.useEffect(() => {
    loadModules(['esri/widgets/Expand']).then(([Expand]) => {
      const expandWidget = new Expand({
        view: props.view,
        content: ref.current,
        expandIconClass: 'esri-icon-edit',
        collapseIconClass: '',
        expanded: true,
        group: props.group,
      });

      props.view.ui.add(expandWidget, {
        position: props.position,
        index: props.index,
      });

      setExpandWidget(expandWidget);
    });

    return function cleanup() {
      props.view.ui.remove(_expandWidget!);
    };
  }, []);

  return (
    <div ref={ref}>
      <Button onClick={deleteWidget}>Delete</Button>
    </div>
  );
}

And the render method of my React class:

public render(): JSX.Element {
    // Build the React components from the 'enabled' or 'default' widgets list
    const enabledOrDefaultWidgetComponents: JSX.Element[] = enabledOrDefaultWidgets.map(
      (enabledWidget: IConfigWidget) => {
        const Component = enabledWidget.component;
        const { name, index, position, group } = enabledWidget;
        return (
          <Component
            key={name}
            id={name}
            position={position}
            index={index}
            group={group}
            {...this.props}
          />
        );
      }
    );

    return (
      <Map
              style={{ height: '93vh' }}
              mapProperties={{
                basemap: this.state.defaultBasemap,
                layers: this.state.defaultLayers,
              }}
              viewProperties={{
                extent: this.state.defaultExtent,
                spatialReference: { wkid: 2154 },
                constraints: {
                  rotationEnabled: false,
                  minScale: 7559040.0,
                },
              }}
              onLoad={this.handleMapLoad}
            >
              {enabledOrDefaultWidgetComponents}
            </Map>
    );
}
tomwayson commented 3 years ago

I think you need to guard the view.ui.remove() call like:

    return function cleanup() {
      if (_expandWidget) {
        props.view.ui.remove(_expandWidget);
      }
    };

If that doesn't work, you could try not putting the expand widget in state. What if you just did:

  React.useEffect(() => {
    let expandWidget: __esri.Expand;
    loadModules(['esri/widgets/Expand']).then(([Expand]) => {
      expandWidget = new Expand({
        view: props.view,
        content: ref.current,
        expandIconClass: 'esri-icon-edit',
        collapseIconClass: '',
        expanded: true,
        group: props.group,
      });

      props.view.ui.add(expandWidget, {
        position: props.position,
        index: props.index,
      });

      setExpandWidget(expandWidget);
    });

    return function cleanup() {
      if (expandWidget) {
        props.view.ui.remove(expandWidget);
     }
    };
  }, []);