React-leaflet: Support server-side rendering

Created on 26 Jun 2015  路  16Comments  路  Source: PaulLeCam/react-leaflet

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='&copy; <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.

Most helpful comment

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)
      }
    )
  }
}

All 16 comments

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)
      }
    )
  }
}

it works, too.

In index.html:

Include ..rel="stylesheet" href="https://unpkg.com/[email protected]/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='&copy; <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`

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;
Was this page helpful?
0 / 5 - 0 ratings

Related issues

samankhademi picture samankhademi  路  3Comments

acpower7 picture acpower7  路  4Comments

fborghi picture fborghi  路  3Comments

diligiant picture diligiant  路  3Comments

lambdakris picture lambdakris  路  3Comments