Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React-leaflet custom component - context not being passed?

Tags:

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.

like image 854
Seth Lutske Avatar asked Jan 08 '20 07:01

Seth Lutske


2 Answers

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.

like image 69
Robert Lybarger Avatar answered Sep 22 '22 11:09

Robert Lybarger


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'
            }
        }
    };

like image 22
Seth Lutske Avatar answered Sep 19 '22 11:09

Seth Lutske