Support server-side rendering
dmitry opened this issue ยท 17 comments
https://github.com/PaulLeCam/react-leaflet#technical-considerations have the following point:
Leaflet makes direct calls to the DOM when it is loaded, therefore this library is not compatible with server-side rendering.
I would like to discuss of the possibilities how it's possible to add server-side support for this library.
My current solution just skip rendering the leaflet on server-side, and render just on client-side:
render() {
const position = [51.505, -0.09];
if (process.env.BROWSER) {
var {Map, Marker, Popup, TileLayer} = require('react-leaflet');
return (
<div className="search-map">
<Map center={position} zoom={13}>
<TileLayer
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
/>
<Marker position={position}>
<Popup>
<span>A pretty CSS3 popup.<br/>Easily customizable.</span>
</Popup>
</Marker>
</Map>
</div>
);
}
else {
return null;
}
}
I don't believe it's a best practice, but still it's something, than nothing, and I think it's better than using the headless-leaflet
fork.
The issue is with Leaflet itself, as it doesn't check if the DOM is available when it's loaded, not directly this library.
I do not plan to support server-side rendering in this lib until it is supported by Leaflet itself, unless it's a trivial implementation with no side-effect.
The solution you presented may work for you, but it's completely dependant on your environment and your build, it would probably not work in other cases.
@PaulLeCam We could provide a React Component as fallback for server side rendering, check if window
exists render the map or render the fallback? Something like that:
<Map .... fallback={ReactComponent}>
...
</Map>
@iam4x Feel free to implement any workaround in your app... As I previously explained, this issue is not caused by this lib.
Thanks for the workarounds gents, I ended up just using a client side bower package and not the react package, but will switch back now I know the render could be defined conditionally based on the environment
@dougajmcdonald similar idea I'm using too. It's more flexible and maintainable.
@dougajmcdonald I was wondering what was the solution for this? You guys are loading leaflet in with bower, and then proceeding to require react-leaflet
the same way? Or are you guys also using bower to load react-leaflet
?
May someone give the example of this workaround
It's ugly, but it works.
let Map, MapComponents
class LeafletMap extends Component {
componentDidMount(){
//Only runs on Client, not on server render
Map = require('react-leaflet').Map
MapComponents = require('./mapComponents').default
this.forceUpdate()
}
render () {
return (
(Map)
? (
<Map
zoom={10}
maxZoom={18}
minZoom={9}
>
<MapComponent />
</Map>
)
: (null)
}
)
}
}
For future googlers, there is also https://github.com/masotime/react-leaflet-universal
And some attempts to render leaflet on the server:
it works, too.
In index.html:
Include ..rel="stylesheet" href="https://unpkg.com/leaflet@1.2.0/dist/leaflet.css"..
In React Component
`import React, { Component } from 'react'
class Mapa extends Component {
componentWillMount() {
console.log('componentWillMount')
Map = require('react-leaflet').Map
TileLayer = require('react-leaflet').TileLayer
TileLayer = require('react-leaflet').TileLayer
Marker = require('react-leaflet').Marker
Popup = require('react-leaflet').Popup
}
render() {
const position = [-12.76767, -76.343434]
return (
<Map center={position} zoom={10} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
url='http://{s}.tile.osm.org/{z}/{x}/{y}.png'
/>
<Marker position={position}>
<Popup>
<span>A pretty CSS3 popup. <br /> Easily customizable.</span>
</Popup>
</Marker>
</Map>
)
}
}
export default Mapa`
The cleanest solution i found is here : https://github.com/etalab/adresse.data.gouv.fr/blob/0bc6dd7/pages/map.js#L5-L12
rktel how to extends MapControl with componentWillMount().
I want add search box by https://github.com/smeijer/leaflet-geosearch.
For anyone wondering I managed to get this working using react-loadable for now
In your parent component do something like so:
const LoadableSearchResultMap = Loadable({ loader: () => import('./SearchResultMap') as Promise<any>, loading() { return <div>Loading...</div> } });
Then I made a component called SearchResultMap.tsx and inside is:
`import * as React from 'react';
import { Map, Marker, Popup, TileLayer, LayersControl } from 'react-leaflet';
import ReactLeafletGoogleLayer from 'react-leaflet-google-layer';
import { LatLngExpression } from 'leaflet';
export default class SearchResultMap extends React.Component {
mapSettings = {
zoom: 15,
center: [48.067539, 12.862530] as LatLngExpression
}
render() {
return <><Map {...this.mapSettings}><ReactLeafletGoogleLayer googleMapsLoaderConf={{ KEY: 'Key Goes Here' }} type={'terrain'} /></Map></>
}
}`
The cleanest solution i found is here : https://github.com/etalab/adresse.data.gouv.fr/blob/0bc6dd7/pages/map.js#L5-L12
Sorry, but how i pass props with this dynamic
Hopefully this will help someone...If you're using gatsby then for some reason using the react-leaflet gatsby plugin works rather than using the library straight...weird but it saved me a lot of headache.
This might be helpful for those who use Webpack & es6 dynamic import feature:
import React, { PureComponent } from 'react';
class MapLoader extends PureComponent {
constructor(props) {
super(props);
this.state = { loading: true };
this.Map= null;
}
componentDidMount() {
import(/* webpackMode: "eager" */ './Map').then((module) => {
this.Map = module.default;
this.setState({ loading: false });
});
}
render() {
const { Map, props, state: { loading } } = this;
if (loading) {
return null;// or render a loading
}
return <Map {...props} />;
}
}
export default MapLoader;
This worked for me in React 18. MyMap is the component that has the leaflet imports. Then you can use Suspense if you want to render something else while it is importing.
const MyMap = React.lazy(() => import('./MyMap'));