├── .npmignore ├── .gitignore ├── .travis.yml ├── .DS_Store ├── .editorconfig ├── .eslintrc ├── .babelrc ├── src ├── index.js └── decorator.js ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | ß -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | dist/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "6" 4 | - "5" 5 | - "4" -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/masotime/react-leaflet-universal/HEAD/.DS_Store -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_style = tab 8 | indent_size = 2 9 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "plugins": [ "import", "react" ], 4 | "extends": [ "eslint:recommended", "plugin:react/recommended", "plugin:import/recommended" ], 5 | "settings": { 6 | "import/resolver": { 7 | "babel-module": {} 8 | }, 9 | "react": { 10 | "version": "detect" 11 | } 12 | }, 13 | "rules": { 14 | "no-undef": 2, 15 | "no-unused-vars": 2, 16 | "no-console": 0 17 | }, 18 | "env": { 19 | "node": true, 20 | "es6": true 21 | }, 22 | "parserOptions": { 23 | "ecmaVersion": 2015, 24 | "sourceType": "module", 25 | "ecmaFeatures": { 26 | "jsx": true, 27 | "experimentalObjectRestSpread": true, 28 | "forOf": true 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "@babel/preset-env", 4 | "@babel/preset-react" 5 | ], 6 | "plugins": [ 7 | [ 8 | "module-resolver", 9 | { 10 | "root": [ 11 | "src" 12 | ] 13 | } 14 | ], 15 | "@babel/plugin-syntax-dynamic-import", 16 | "@babel/plugin-syntax-import-meta", 17 | "@babel/plugin-proposal-class-properties", 18 | "@babel/plugin-proposal-json-strings", 19 | [ 20 | "@babel/plugin-proposal-decorators", 21 | { 22 | "legacy": true 23 | } 24 | ], 25 | "@babel/plugin-proposal-function-sent", 26 | "@babel/plugin-proposal-export-namespace-from", 27 | "@babel/plugin-proposal-numeric-separator", 28 | "@babel/plugin-proposal-throw-expressions", 29 | "@babel/plugin-proposal-export-default-from", 30 | "@babel/plugin-proposal-logical-assignment-operators", 31 | "@babel/plugin-proposal-optional-chaining", 32 | [ 33 | "@babel/plugin-proposal-pipeline-operator", 34 | { 35 | "proposal": "minimal" 36 | } 37 | ], 38 | "@babel/plugin-proposal-nullish-coalescing-operator", 39 | "@babel/plugin-proposal-do-expressions", 40 | "@babel/plugin-proposal-function-bind" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import decorate from './decorator'; 2 | 3 | export const AttributionControl = decorate('AttributionControl') 4 | export const Circle = decorate('Circle') 5 | export const CircleMarker = decorate('CircleMarker') 6 | export const ControlledLayer = decorate('ControlledLayer') 7 | export const DivOverlay = decorate('DivOverlay') 8 | export const FeatureGroup = decorate('FeatureGroup') 9 | export const GeoJSON = decorate('GeoJSON') 10 | export const GridLayer = decorate('GridLayer') 11 | export const ImageOverlay = decorate('ImageOverlay') 12 | export const LayerGroup = decorate('LayerGroup') 13 | export const LayersControl = decorate('LayersControl') 14 | export const Map = decorate('Map') 15 | export const MapComponent = decorate('MapComponent') 16 | export const MapControl = decorate('MapControl') 17 | export const MapEvented = decorate('MapEvented') 18 | export const MapLayer = decorate('MapLayer') 19 | export const Marker = decorate('Marker') 20 | export const Pane = decorate('Pane') 21 | export const Path = decorate('Path') 22 | export const Polygon = decorate('Polygon') 23 | export const Polyline = decorate('Polyline') 24 | export const Popup = decorate('Popup') 25 | export const Rectangle = decorate('Rectangle') 26 | export const ScaleControl = decorate('ScaleControl') 27 | export const TileLayer = decorate('TileLayer') 28 | export const Tooltip = decorate('Tooltip') 29 | export const VideoOverlay = decorate('VideoOverlay') 30 | export const WMSTileLayer = decorate('WMSTileLayer') 31 | export const ZoomControl = decorate('ZoomControl') 32 | -------------------------------------------------------------------------------- /src/decorator.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { PropTypes } from 'prop-types'; 3 | 4 | const { node, oneOfType, func, object } = PropTypes; 5 | 6 | export default function decorate(componentName) { 7 | const displayName = `LeafletUniv${componentName}`; 8 | 9 | class Decorated extends Component { 10 | constructor(props) { 11 | super(props); 12 | this.state = { loaded: false }; 13 | this.constructor.displayName = displayName; 14 | this._leafletRef = React.createRef(); 15 | this._getRef = () => this.props.leafletRef || this._leafletRef; 16 | } 17 | 18 | componentDidMount() { 19 | this.setState(() => ({ loaded: true })); 20 | this.ClientComponent = require('react-leaflet')[componentName]; 21 | } 22 | 23 | componentDidUpdate() { 24 | const ref = this._getRef(); 25 | if (ref && ref.current && this.leafletElement !== ref.current.leafletElement) { 26 | this.leafletElement = ref.current.leafletElement; 27 | } 28 | } 29 | 30 | render() { 31 | if (!this.state.loaded) return null; 32 | 33 | const { ClientComponent } = this; 34 | const { children, ...rest } = this.props; 35 | const childComponents = typeof children === 'function' ? children() : children; 36 | 37 | return ( 38 | 39 | { childComponents } 40 | 41 | ); 42 | } 43 | } 44 | 45 | Decorated.displayName = displayName; 46 | Decorated.propTypes = { 47 | children: oneOfType([node, func]), 48 | leafletRef: oneOfType([func, object]) 49 | }; 50 | 51 | return Decorated; 52 | } 53 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-leaflet-universal", 3 | "version": "2.2.1", 4 | "description": "Simple wrapper around react-leaflet for painless universal integration", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "lint": "eslint src/*", 8 | "compile": "rm -rf dist/; babel --plugins @babel/transform-runtime,add-module-exports -d dist/ src/", 9 | "prepublish": "npm test && npm run compile", 10 | "test": "npm run lint" 11 | }, 12 | "author": "Benjamin Goh ", 13 | "license": "ISC", 14 | "peerDependencies": { 15 | "prop-types": "^15.5.7", 16 | "react": "^15.x || ^16.x", 17 | "react-dom": "^15.x || ^16.x", 18 | "react-leaflet": "^2.0.1" 19 | }, 20 | "repository": "https://github.com/masotime/react-leaflet-universal", 21 | "devDependencies": { 22 | "@babel/cli": "^7.0.0", 23 | "@babel/core": "^7.0.0", 24 | "@babel/plugin-proposal-class-properties": "^7.0.0", 25 | "@babel/plugin-proposal-decorators": "^7.0.0", 26 | "@babel/plugin-proposal-do-expressions": "^7.0.0", 27 | "@babel/plugin-proposal-export-default-from": "^7.0.0", 28 | "@babel/plugin-proposal-export-namespace-from": "^7.0.0", 29 | "@babel/plugin-proposal-function-bind": "^7.0.0", 30 | "@babel/plugin-proposal-function-sent": "^7.0.0", 31 | "@babel/plugin-proposal-json-strings": "^7.0.0", 32 | "@babel/plugin-proposal-logical-assignment-operators": "^7.0.0", 33 | "@babel/plugin-proposal-nullish-coalescing-operator": "^7.0.0", 34 | "@babel/plugin-proposal-numeric-separator": "^7.0.0", 35 | "@babel/plugin-proposal-optional-chaining": "^7.0.0", 36 | "@babel/plugin-proposal-pipeline-operator": "^7.0.0", 37 | "@babel/plugin-proposal-throw-expressions": "^7.0.0", 38 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 39 | "@babel/plugin-syntax-import-meta": "^7.0.0", 40 | "@babel/plugin-transform-runtime": "^7.0.0", 41 | "@babel/preset-env": "^7.0.0", 42 | "@babel/preset-react": "^7.0.0", 43 | "@babel/runtime-corejs2": "^7.0.0", 44 | "babel-eslint": "^9.0.0", 45 | "babel-plugin-add-module-exports": "^1.0.2", 46 | "babel-plugin-module-resolver": "^4.0.0", 47 | "eslint": "^5.16.0", 48 | "eslint-import-resolver-babel-module": "^5.1.0", 49 | "eslint-plugin-import": "^2.17.1", 50 | "eslint-plugin-react": "^7.17.0", 51 | "leaflet": "^1.3.4", 52 | "react": "^15.x || ^16.x", 53 | "react-dom": "^15.x || ^16.x", 54 | "react-leaflet": "^2.0.1" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-leaflet-universal 2 | 3 | Thin wrapper around [react-leaflet][react-leaflet-url] that is designed to make it easier to implement the module in universal applications. Leaflet was not designed with the server in mind, making it very difficult to work with for server-side rendering. 4 | 5 | To sidestep this issue, we simply don't render server side. This module wraps all of `react-leaflet`'s components in a Wrapper class that only renders when the component is mounted - which only happens client-side. 6 | 7 | ## usage 8 | 9 | Just use it as you normally would use `react-leaflet`. e.g. Instead of 10 | 11 | ``` 12 | import { Map } from 'react-leaflet'; 13 | ``` 14 | 15 | write 16 | ``` 17 | import { Map } from 'react-leaflet-universal'; 18 | ``` 19 | 20 | To forward reference, pass to `leafletRef`: 21 | 22 | ``` 23 | 24 | 25 | 26 | ``` 27 | 28 | If you do not provide `leafletRef`, wrappers will instead create their own ref and set the property `leafletElement` on the instance when it becomes available, so setting a `ref` prop will still work, however note that since this only occurs late in the render cycle, `leafletElement` may still be undefined when attempting to access it from the `ref`, so it is recommended to check that `ref.leafletElement` exists before attempting to invoke properties or methods on it. 29 | 30 | ## Troubleshooting custom `react-leaflet` components / render prop support 31 | 32 | Some components, such as [react-leaflet-markercluster][markercluster-url], make use of `componentWillMount` and so cannot be used directly. 33 | 34 | To mitigate this, you can now use a function [render prop][render-prop-url] instead of normal children for a component. Thus, instead of e.g. 35 | 36 | ``` 37 | 38 | 39 | 40 | ``` 41 | 42 | this will also work 43 | 44 | ``` 45 | 46 | () => { 47 | return 48 | } 49 | 50 | ``` 51 | 52 | So in the case of `react-leaflet-markercluster`, you can write something similar to: 53 | 54 | ``` 55 | 56 | () => { 57 | const MarkerClusterGroup = require('react-leaflet-markercluster').default; 58 | return ( 59 |
60 | 64 | 71 |
72 | ); 73 | } 74 |
75 | ``` 76 | 77 | (contrast with the [example on the react-leaflet-markercluster website][markercluster-example-url]) 78 | 79 | Note: If you use React 16.2+, [you can also make use of `` or simply `<>`][react-16-fragment-example] instead of the wrapping `
` in the example above. 80 | 81 | [react-leaflet-url]: https://www.npmjs.com/package/react-leaflet 82 | [markercluster-url]: https://www.npmjs.com/package/react-leaflet-markercluster 83 | [markercluster-example-url]: https://github.com/YUzhva/react-leaflet-markercluster#getting-started 84 | [render-prop-url]: https://cdb.reacttraining.com/use-a-render-prop-50de598f11ce 85 | [react-16-fragment-example]: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html 86 | --------------------------------------------------------------------------------