alex3165 / react-leaflet-draw

React component for leaflet-draw on top of react-leaflet
228 stars 152 forks source link

React-Leaflet v3 #87

Closed abewartech closed 3 years ago

abewartech commented 4 years ago

WARNING in ./node_modules/react-leaflet-draw/dist/esm/EditControl.js 131:2-12 "export 'MapControl' was not found in 'react-leaflet'

WARNING in ./node_modules/react-leaflet-draw/dist/esm/EditControl.js 184:15-26 "export 'withLeaflet' was not found in 'react-leaflet'

ehsan-sotoodeh commented 4 years ago

I have the same problem! any solution?

L4brax commented 3 years ago

Hello, are you sure you have installed react-leaflet and leaflet-draw ? npm install leaflet-draw react-leaflet

abewartech commented 3 years ago

image Yes, of course

tadassuksteris commented 3 years ago

I was only able to use it by downgrading "react-leaflet" to 2.7.0 Here is my used packages. Hope it helps.

"react-leaflet": "^2.7.0", "react-leaflet-draw": "^0.19.0", "leaflet": "^1.7.1", "leaflet-draw": "^1.0.4"

r2dliu commented 3 years ago

I took a stab at refactoring this; I only dumped about a half hour into it/copied the most basic pattern from the docs, so it's ugly and doesn't use their cleaner, custom hooks but it works for my use case. Didn't rigorously test all the eventHandlers either. Please reply with improvements if you iterate on it

import 'leaflet-draw/dist/leaflet.draw.css' // wherever you need it

// Refactored v3 EditControl.js file
import { useEffect, useRef } from 'react';
import { PropTypes } from 'prop-types';
import Draw from 'leaflet-draw'; // eslint-disable-line
import isEqual from 'lodash.isequal';

import { useLeafletContext } from '@react-leaflet/core';
import leaflet, { Map, Control } from 'leaflet';

const eventHandlers = {
  onEdited: 'draw:edited',
  onDrawStart: 'draw:drawstart',
  onDrawStop: 'draw:drawstop',
  onDrawVertex: 'draw:drawvertex',
  onEditStart: 'draw:editstart',
  onEditMove: 'draw:editmove',
  onEditResize: 'draw:editresize',
  onEditVertex: 'draw:editvertex',
  onEditStop: 'draw:editstop',
  onDeleted: 'draw:deleted',
  onDeleteStart: 'draw:deletestart',
  onDeleteStop: 'draw:deletestop',
};

function EditControl(props) {
    const context = useLeafletContext()
    const controlRef = useRef()
    const propsRef = useRef(props)

    const onDrawCreate = (e) => {    
        context.layerContainer.addLayer(e.layer);
        props.onCreated && props.onCreated(e);
    };

    useEffect(() => {          
        for (const key in eventHandlers) {
            context.map.on(eventHandlers[key], (evt) => {
                let handlers = Object.keys(eventHandlers).filter(handler => eventHandlers[handler] === evt.type);
                if (handlers.length === 1) {
                    let handler = handlers[0];
                    props[handler] && props[handler](evt);
                }
            })
        }

        context.map.on(leaflet.Draw.Event.CREATED, onDrawCreate);
        const options = {
            edit: {
                ...props.edit,
                featureGroup: context.layerContainer
            }
        }
        if (props.draw) {
            options.draw = { ...props.draw };
        }
        if (props.position) {
            options.position = props.position;
        }

        controlRef.current = new Control.Draw(options);
        controlRef.current.addTo(context.map);
        props.onMounted && props.onMounted(controlRef.current);

        return () => {
            context.map.off(leaflet.Draw.Event.CREATED, onDrawCreate);

            for (const key in eventHandlers) {
              if (props[key]) {
                context.map.off(eventHandlers[key], props[key]);
              }
            }
        }
    }, [])

    useEffect(() => {
        // If the props haven't changed, don't update
        if (
            isEqual(props.draw, propsRef.current.draw)
            && isEqual(props.edit, propsRef.current.edit)
            && props.position === propsRef.current.position
        ) {
            return false;
        }

        const options = {
            edit: {
                ...props.edit,
                featureGroup: context.layerContainer
            }
        }
        if (props.draw) {
            options.draw = { ...props.draw };
        }
        if (props.position) {
            options.position = props.position;
        }

        controlRef.current.remove(context.map);
        controlRef.current = new Control.Draw(options);
        controlRef.current.addTo(context.map);

        // Remount the new draw control
        props.onMounted && props.onMounted(controlRef.current);
        propsRef.current = props
    }, [props.draw, props.edit, props.position])

    return null;
}

EditControl.propTypes = {
    ...Object.keys(eventHandlers).reduce((acc, val) => {
        acc[val] = PropTypes.func;
        return acc;
    }, {}),
    onCreated: PropTypes.func,
    onMounted: PropTypes.func,
    draw: PropTypes.shape({
        polyline: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        polygon: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        rectangle: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        circle: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        marker: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
    }),
    edit: PropTypes.shape({
        edit: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        remove: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        poly: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        allowIntersection: PropTypes.bool,
    }),
    position: PropTypes.oneOf([
        'topright',
        'topleft',
        'bottomright',
        'bottomleft'
    ]),
};

export default EditControl;
ghost commented 3 years ago

I got this error

Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:

  1. You might have mismatching versions of React and the renderer (such as React DOM)
  2. You might be breaking the Rules of Hooks
  3. You might have more than one copy of React in the same app See https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem.
chaosJS commented 3 years ago

I took a stab at refactoring this; I only dumped about a half hour into it/copied the most basic pattern from the docs, so it's ugly and doesn't use their cleaner, custom hooks but it works for my use case. Didn't rigorously test all the eventHandlers either. Please reply with improvements if you iterate on it

import 'leaflet-draw/dist/leaflet.draw.css' // wherever you need it

// Refactored v3 EditControl.js file
import { useEffect, useRef } from 'react';
import { PropTypes } from 'prop-types';
import Draw from 'leaflet-draw'; // eslint-disable-line
import isEqual from 'lodash.isequal';

import { useLeafletContext } from '@react-leaflet/core';
import leaflet, { Map, Control } from 'leaflet';

const eventHandlers = {
  onEdited: 'draw:edited',
  onDrawStart: 'draw:drawstart',
  onDrawStop: 'draw:drawstop',
  onDrawVertex: 'draw:drawvertex',
  onEditStart: 'draw:editstart',
  onEditMove: 'draw:editmove',
  onEditResize: 'draw:editresize',
  onEditVertex: 'draw:editvertex',
  onEditStop: 'draw:editstop',
  onDeleted: 'draw:deleted',
  onDeleteStart: 'draw:deletestart',
  onDeleteStop: 'draw:deletestop',
};

function EditControl(props) {
    const context = useLeafletContext()
    const controlRef = useRef()
    const propsRef = useRef(props)

    const onDrawCreate = (e) => {    
        context.layerContainer.addLayer(e.layer);
        props.onCreated && props.onCreated(e);
    };

    useEffect(() => {          
        for (const key in eventHandlers) {
            context.map.on(eventHandlers[key], (evt) => {
                let handlers = Object.keys(eventHandlers).filter(handler => eventHandlers[handler] === evt.type);
                if (handlers.length === 1) {
                    let handler = handlers[0];
                    props[handler] && props[handler](evt);
                }
            })
        }

        context.map.on(leaflet.Draw.Event.CREATED, onDrawCreate);
        const options = {
            edit: {
                ...props.edit,
                featureGroup: context.layerContainer
            }
        }
        if (props.draw) {
            options.draw = { ...props.draw };
        }
        if (props.position) {
            options.position = props.position;
        }

        controlRef.current = new Control.Draw(options);
        controlRef.current.addTo(context.map);
        props.onMounted && props.onMounted(controlRef.current);

        return () => {
            context.map.off(leaflet.Draw.Event.CREATED, onDrawCreate);

            for (const key in eventHandlers) {
              if (props[key]) {
                context.map.off(eventHandlers[key], props[key]);
              }
            }
        }
    }, [])

    useEffect(() => {
        // If the props haven't changed, don't update
        if (
            isEqual(props.draw, propsRef.current.draw)
            && isEqual(props.edit, propsRef.current.edit)
            && props.position === propsRef.current.position
        ) {
            return false;
        }

        const options = {
            edit: {
                ...props.edit,
                featureGroup: context.layerContainer
            }
        }
        if (props.draw) {
            options.draw = { ...props.draw };
        }
        if (props.position) {
            options.position = props.position;
        }

        controlRef.current.remove(context.map);
        controlRef.current = new Control.Draw(options);
        controlRef.current.addTo(context.map);

        // Remount the new draw control
        props.onMounted && props.onMounted(controlRef.current);
        propsRef.current = props
    }, [props.draw, props.edit, props.position])

    return null;
}

EditControl.propTypes = {
    ...Object.keys(eventHandlers).reduce((acc, val) => {
        acc[val] = PropTypes.func;
        return acc;
    }, {}),
    onCreated: PropTypes.func,
    onMounted: PropTypes.func,
    draw: PropTypes.shape({
        polyline: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        polygon: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        rectangle: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        circle: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        marker: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
    }),
    edit: PropTypes.shape({
        edit: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        remove: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        poly: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        allowIntersection: PropTypes.bool,
    }),
    position: PropTypes.oneOf([
        'topright',
        'topleft',
        'bottomright',
        'bottomleft'
    ]),
};

export default EditControl;

when i link to another route , i'll get an error like this image this error is occured in EditControl like this image @r2dliu i don't how to fix this error

chaosJS commented 3 years ago

WARNING in ./node_modules/react-leaflet-draw/dist/esm/EditControl.js 131:2-12 "export 'MapControl' was not found in 'react-leaflet'

WARNING in ./node_modules/react-leaflet-draw/dist/esm/EditControl.js 184:15-26 "export 'withLeaflet' was not found in 'react-leaflet'

@srghma react-leaflet upgrade to v3, MapControl and withLeaflet is not fond in old version

srghma commented 3 years ago

@chaosJS I'm not working directly on react-leaflet-draw, but I can accept pr

MarcoGiorgi commented 3 years ago

I confirm that react-leaflet-draw 0.19.0 is incompatible with react-leaflet 3.0.0 and higher. I've got the same error: 'MapControl' is not exported from 'react-leaflet' which goes away only if I downgrade react-leaflet to version 2.7.0

dartmoordunbar commented 3 years ago

I took a stab at refactoring this; I only dumped about a half hour into it/copied the most basic pattern from the docs, so it's ugly and doesn't use their cleaner, custom hooks but it works for my use case. Didn't rigorously test all the eventHandlers either. Please reply with improvements if you iterate on it import 'leaflet-draw/dist/leaflet.draw.css' // wherever you need it

// Refactored v3 EditControl.js file
import { useEffect, useRef } from 'react';
import { PropTypes } from 'prop-types';
import Draw from 'leaflet-draw'; // eslint-disable-line
import isEqual from 'lodash.isequal';

import { useLeafletContext } from '@react-leaflet/core';
import leaflet, { Map, Control } from 'leaflet';

const eventHandlers = {
  onEdited: 'draw:edited',
  onDrawStart: 'draw:drawstart',
  onDrawStop: 'draw:drawstop',
  onDrawVertex: 'draw:drawvertex',
  onEditStart: 'draw:editstart',
  onEditMove: 'draw:editmove',
  onEditResize: 'draw:editresize',
  onEditVertex: 'draw:editvertex',
  onEditStop: 'draw:editstop',
  onDeleted: 'draw:deleted',
  onDeleteStart: 'draw:deletestart',
  onDeleteStop: 'draw:deletestop',
};

function EditControl(props) {
    const context = useLeafletContext()
    const controlRef = useRef()
    const propsRef = useRef(props)

    const onDrawCreate = (e) => {    
        context.layerContainer.addLayer(e.layer);
        props.onCreated && props.onCreated(e);
    };

    useEffect(() => {          
        for (const key in eventHandlers) {
            context.map.on(eventHandlers[key], (evt) => {
                let handlers = Object.keys(eventHandlers).filter(handler => eventHandlers[handler] === evt.type);
                if (handlers.length === 1) {
                    let handler = handlers[0];
                    props[handler] && props[handler](evt);
                }
            })
        }

        context.map.on(leaflet.Draw.Event.CREATED, onDrawCreate);
        const options = {
            edit: {
                ...props.edit,
                featureGroup: context.layerContainer
            }
        }
        if (props.draw) {
            options.draw = { ...props.draw };
        }
        if (props.position) {
            options.position = props.position;
        }

        controlRef.current = new Control.Draw(options);
        controlRef.current.addTo(context.map);
        props.onMounted && props.onMounted(controlRef.current);

        return () => {
            context.map.off(leaflet.Draw.Event.CREATED, onDrawCreate);

            for (const key in eventHandlers) {
              if (props[key]) {
                context.map.off(eventHandlers[key], props[key]);
              }
            }
        }
    }, [])

    useEffect(() => {
        // If the props haven't changed, don't update
        if (
            isEqual(props.draw, propsRef.current.draw)
            && isEqual(props.edit, propsRef.current.edit)
            && props.position === propsRef.current.position
        ) {
            return false;
        }

        const options = {
            edit: {
                ...props.edit,
                featureGroup: context.layerContainer
            }
        }
        if (props.draw) {
            options.draw = { ...props.draw };
        }
        if (props.position) {
            options.position = props.position;
        }

        controlRef.current.remove(context.map);
        controlRef.current = new Control.Draw(options);
        controlRef.current.addTo(context.map);

        // Remount the new draw control
        props.onMounted && props.onMounted(controlRef.current);
        propsRef.current = props
    }, [props.draw, props.edit, props.position])

    return null;
}

EditControl.propTypes = {
    ...Object.keys(eventHandlers).reduce((acc, val) => {
        acc[val] = PropTypes.func;
        return acc;
    }, {}),
    onCreated: PropTypes.func,
    onMounted: PropTypes.func,
    draw: PropTypes.shape({
        polyline: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        polygon: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        rectangle: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        circle: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        marker: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
    }),
    edit: PropTypes.shape({
        edit: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        remove: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        poly: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
        allowIntersection: PropTypes.bool,
    }),
    position: PropTypes.oneOf([
        'topright',
        'topleft',
        'bottomright',
        'bottomleft'
    ]),
};

export default EditControl;

when i link to another route , i'll get an error like this image this error is occured in EditControl like this image @r2dliu i don't how to fix this error

Does this fix the issue? If so, would you please make a PR?

DVGY commented 3 years ago

@chaosJS I'm not working directly on react-leaflet-draw, but I can accept pr

I can try this, but I don't know how to get started. I am new. So I have forked you repo, clone and everything. How can I run your code after making changes to src/EditControl.js

DVGY commented 3 years ago

@srghma Please check my pr and merge.

@abewartech @ehsan-sotoodeh @MarcoGiorgi @bokurev @r2dliu @L4brax @ Guys tried to update the library, waiting for the comments from the collaborator.

https://github.com/alex3165/react-leaflet-draw/pull/90

cargaralo commented 3 years ago

Hello guys, thanks for the PR @DVGY , are you going to release it as a new version?

srghma commented 3 years ago

@cargaralo I don't have rights to release to non, only @alex3165 have

DVGY commented 3 years ago

Hello guys, thanks for the PR @DVGY , are you going to release it as a new version?

@cargaralo Thanks! It Can only released by the owner. For the time being you can define your own cutom file EditControl.js and copy the code from #93 and use it normally.

pollomarzo commented 3 years ago

Just to give a different possibility, we solved this the way react-leaflet probably intended. Since we had to extract the featureGroup from the context, we modified createControlComponent slightly; apart from that, it should be exactly the same in the original code. I mention this because while it looks daunting it was literally 2 function calls away. Anyway, here's our code:

import { useCallback, useEffect, useRef } from 'react';
import leaflet, { Control } from 'leaflet';
import Draw from 'leaflet-draw';
import {
  useLeafletContext,
  createElementHook,
  createLeafComponent,
} from '@react-leaflet/core';

const createControlComponent = (createInstance) => {
  function createElement(props, context) {
    const { layerContainer } = context;
    const { position } = props;
    const options = {
      position,
      edit: {
        featureGroup: layerContainer,
      },
    };

    return { instance: createInstance(options), context };
  }
  const useElement = createElementHook(createElement);
  const useControl = createControlHook(useElement);
  return createLeafComponent(useControl);
};

const createControlHook = (useElement) => {
  return function useLeafletControl(props) {
    const context = useLeafletContext();
    const elementRef = useElement(props, context);
    const { instance } = elementRef.current;
    const positionRef = useRef(props.position);
    const { position, onCreated, onEdit, onDeleted } = props;

    const onDrawCreate = useCallback(
      (e) => {
        context.layerContainer.addLayer(e.layer);
        onCreated(e);
      },
      [context.layerContainer, onCreated]
    );

    useEffect(
      function addControl() {
        instance.addTo(context.map);
        context.map.on(leaflet.Draw.Event.CREATED, onDrawCreate);

        if (onDeleted) {
          context.map.on(leaflet.Draw.Event.DELETED, onDeleted);
        }

        if (onEdit) {
          context.map.on(leaflet.Draw.Event.EDITRESIZE, onEdit);
          context.map.on(leaflet.Draw.Event.EDITMOVE, onEdit);
        }

        return function removeControl() {
          context.map.off(leaflet.Draw.Event.CREATED, onDrawCreate);

          if (onDeleted) {
            context.map.off(leaflet.Draw.Event.DELETED, onDeleted);
          }

          if (onEdit) {
            context.map.off(leaflet.Draw.Event.EDITRESIZE, onEdit);
            context.map.off(leaflet.Draw.Event.EDITMOVE, onEdit);
          }

          instance.remove();
        };
      },
      [context.map, instance, onDrawCreate, onDeleted, onEdit]
    );

    useEffect(
      function updateControl() {
        if (position != null && position !== positionRef.current) {
          instance.setPosition(position);
          positionRef.current = position;
        }
      },
      [instance, position]
    );

    return elementRef;
  };
};

Then, to add a custom control:

export const EditOnlyControl = createControlComponent(
  (options) =>
    new Control.Draw({
      ...options,
      draw: {
        polyline: false,
        polygon: false,
        rectangle: false,
        circle: false,
        marker: false,
        circlemarker: false,
      },
    })
);

Of course, it still needs to be used inside a <FeatureGroup>

davis3tnpolitics commented 3 years ago

This issue was solved by #90, right? Any ideas on when the new version will be released? Anyone know? @alex3165

Really great work with this package. So much appreciation from all of us.

srghma commented 3 years ago

@davis3tnpolitics, Alex is not working on project anymore, I have rights to github repo, but not for npm, do the new version won't be released, but it is merged, yes, I would suggest to install from github master itself if this is possible

gmarshall56 commented 3 years ago

Thank you!!

paulschreiber commented 2 years ago

@srghma peerDependencies still has "react-leaflet": "^2.0.0", which fails to install:

npm ERR! Could not resolve dependency:
npm ERR! peer react-leaflet@"^2.0.0" from react-leaflet-draw@0.19.8
npm ERR! node_modules/react-leaflet-draw
npm ERR!   react-leaflet-draw@"https://github.com/alex3165/react-leaflet-draw" from the root project

Can you fix this? I can work around it with --force, but that is not ideal.