6

I'm writing a custom component for react-leaflet. It is an editable popup, with a few other features. Here is a codesandbox for an example.. The source code for the component is here. This example just does an import EditablePopup from 'my-react-leaflet-popup-plugin', which is a .js file in my project. Works well.

I am trying to bundle this using webpack to package it as a node module so that others can use it. It compiles without issue. You can see my webpack.config.js here. I am then using an npm link to link this module into a test project on my local machine. When I do so, I get the error message:

TypeError: Cannot read property 'leafletElement' of null

  460 |   value: function value() {
  461 |     var e = this;
  462 |     this.props.open && setTimeout(function () {
> 463 |       e.thePopup.leafletElement._source.openPopup();
      | ^  464 |     }, .001);
  465 |   }
  466 | }, {

Even if I get rid of that clause, I get this:

TypeError: Cannot read property 'openPopup' of undefined


  9226 | // @method openOn(map: Map): this
  9227 | // Adds the popup to the map and closes the previous one. The same as `map.openPopup(popup)`.
  9228 | openOn: function openOn(map) {
> 9229 |   map.openPopup(this);
       | ^  9230 |   return this;
  9231 | },
  9232 | onAdd: function onAdd(map) {

Its like the popup is trying to add itself to the map, but is not finding the map instance of the parent Map component. My hunch is that for some reason, when building this with webpack and importing it as an npm module, the context of the react-leaflet map is not getting passed properly to my component. Which I don't really understand, as my component is just a dressed up version of react-leaflet's <Popup> component. My <EditablePopup> uses <Popup> as a direct child, which inherently should be receiving the LeafletConsumer context consumer wrapper.

I'm not sure why this component works great when linked within my project, but has this error when built through webpack and imported via npm.

Seth Lutske
  • 9,154
  • 5
  • 29
  • 78

3 Answers3

5

I have a similar issue at the workplace, and I now suspect the (similar) way we are declaring webpack config is the culprit. But for me, just changing the libraryTarget didn't have any effect. My insights below after describing our setup...

We have a collection of visual components we are bundling to a "library" that we are importing into a main project. There is some external data the main project needs to fetch, but needs to be consumed at arbitrary places throughout the library's components. And we didn't want to pass props around everywhere. So context, right? Except as soon as the provider (which we setup and initialize in the "main" project) and the consumer were on different sides of the library boundary, context for the consumer was always undefined (or rather, whatever the default is for 'createContext' ... we didn't use an initializer).

Anyway, fast forward a bunch. If I change from listing each different component in the library as its own independent "entry" (in the webpack file) to instead building one index.js file that imports-and-reexports everything -- and only include that one file as a singular "entry" (in the webpack) -- then things started working.

Because React context API needs that created Context object to be shared, but the notion of sharing is (I guess anyway) at a module level. It must not be truly global. So different entry points, different modules, different context objects. Consumer is left out in the cold.

  • This is a very interesting take. In my case, the `libraryTarget` worked, but I am only trying to export one component. I feel like this same answer is needed in the question [React Context API not working from custom NPM component library](https://stackoverflow.com/questions/57976672/react-context-api-not-working-from-custom-npm-component-library). Please post it there! – Seth Lutske Mar 12 '20 at 16:44
  • @robert-lybarger Do you have any more insight on the contexts being at a module level? I had this exact same issue and solved it following your answer, but it makes importing a lot less clean if I can't separate the modules. Is there any way around this? – ROODAY Aug 05 '20 at 16:59
  • @ROODAY -- I'm afraid it wasn't long after that research and post that our projects were up-ended (covid-19). For what we were working toward, at the time, we were about to bail on 'context' (at least, crossing the module boundary) and revise how we were passing state around. – Robert Lybarger Aug 06 '20 at 17:17
  • I see, thanks for the extra info! In my case I'd really prefer not switch from contexts as code would get super repetitive. However, this does give me some ideas, perhaps all the code relating to the context needs to come from one module, and code that doesn't use it can be in another? Could at least make my imports a bit more manageable. – ROODAY Aug 06 '20 at 19:09
2

I'm posting an answer that was inspired by this anwer in Cannot read property 'Component' of undefined - webpack build of react component plugin. My question here seems at least somewhat related to the question React Context API not working from custom NPM component library , and those folks are still looking for an answer - maybe this will help.

I had to make adjustments in my webpack.config.js as well as the package.json to solve this issue.


In webpack.config.js

Change this:

  libraryTarget: 'commonjs2'

to this:


      library: "EditablePopup",
      libraryTarget: 'umd'

As well as adding mode: development to module.exports.


In package.json

Moving some of my dependencies to peerDependencies:


    // Change from this:
      "dependencies": {
        "html-react-parser": "^0.10.0",
        "leaflet": "^1.6.0",
        "react": "^16.12.0",
        "react-contenteditable": "^3.3.3",
        "react-dom": "^16.12.0",
        "react-leaflet": "^2.6.1",
        "webpack": "^4.41.5"
      },
      "peerDependencies": {
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
        "react-leaflet": "^2.6.1",
        "leaflet": "^1.6.0"
      }

    // to this:
      "dependencies": {
        "html-react-parser": "^0.10.0",
        "react-contenteditable": "^3.3.3",
        "webpack": "^4.41.5"
      },
      "peerDependencies": {
        "react": "^16.12.0",
        "react-dom": "^16.12.0",
        "react-leaflet": "^2.6.1",
        "leaflet": "^1.6.0"
      }


I'm pretty sure its the webpack.config.js changes that were the kicker. For completeness, here are the changes to that file:


    // webpack.config.js

    var path = require('path');
    module.exports = {
        entry: './src/EditablePopup.js',
        output: {
            path: path.resolve(__dirname, 'build'),
            filename: 'EditablePopup.js',
    -       libraryTarget: 'commonjs2',
    +       library: "EditablePopup",
    +       libraryTarget: 'umd'
        },
    +   mode: "development",
        module: {
            rules: [
                {
                    test: /\.js$/,
                    include: path.resolve(__dirname, 'src'),
                    exclude: /(node_modules|bower_components|build)/,
                    use: [
                        {
                            loader: 'babel-loader',
                            options: {
                                presets: ['@babel/preset-env']
                            }
                        }
                    ]
                },
                { 
                    test : /\.css$/, 
                    use: [
                        { loader: 'style-loader' },
                        { loader: 'css-loader' }
                    ]  
                }
            ]
        },
        externals: {
            'react-dom': 'commonjs react-dom',
            leaflet: {
                commonjs: 'leaflet',
                commonjs2: 'leaflet',
                root: 'L'
            },
            'react-leaflet': {
                commonjs: 'react-leaflet',
                commonjs2: 'react-leaflet',
                root: 'ReactLeaflet'
            },
            react: {
                commonjs: 'react',
                commonjs2: 'react',
                root: 'React'
            }
        }
    };

Seth Lutske
  • 9,154
  • 5
  • 29
  • 78
  • Thank you Seth. I have asked a similar question, although I am compiling to TypeScript. Hoping this puts me in the right direction. If you have any hints to `compilerOptions`, I would love your suggestions. – Darren Mar 12 '20 at 06:05
0

Perhaps you need to wrap the custom component in withLeaflet? I found this SO answer to be very clear in the steps to creating custom components with react-leaflet.

Using the HOC will make the map (and the rest of the context) available in your implementation of createLeafletElement after the map has mounted.

nrako
  • 2,952
  • 17
  • 30
  • I initially thought that this might be the case, but it turned out it wasn't. Because the component was working properly *not* as an npm module. My component returns a ``, which already contains the leaflet context. The issue was in how I was compiling the JSX with webpack to turn it into a node module. Thanks for your answer though – Seth Lutske Apr 10 '20 at 15:11
  • I see. That makes sense. Hopefully you got it sorted – nrako Apr 10 '20 at 15:58