SamSamskies / react-map-gl-geocoder

React wrapper for mapbox-gl-geocoder for use with react-map-gl
MIT License
122 stars 29 forks source link

Blank screen after adding geocoder to deck.gl #88

Closed andrewshrout closed 3 years ago

andrewshrout commented 3 years ago

Hey I was hoping I could get some help - I'm a new dev working on a mapping app. I tried to add the geocoder to a map I've been working on, and eventually figured out that it could work with static maps. But I have spent days and I still don't understand what I have done wrong with the mapref. Upon loading the page, my map will flicker with the datapoints on it and then the entire page goes white.

I was hoping to add a geocoder to the map so that I could eventually pass requests to my geoserver and only get points nearby, but I'm at a standstill. Below is my code, and I'd appreciate anything to help me understand better.

import React, { Component, useRef } from 'react';
import { StaticMap } from 'react-map-gl';
import DeckGL, { MapView } from 'deck.gl';
import '../map/mapbox-gl.css';
import { renderLayers } from './map_components/deckgl-layers';
import {
  LayerControls,
  MapStylePicker,
  HEXAGON_CONTROLS,
  //  SCATTERPLOT_CONTROLS
} from './map_components/controls';

import dynamic from 'next/dynamic';

const Geocoder = dynamic(() => import('react-map-gl-geocoder'), {
  loading: () => <p>Loading...</p>,
  ssr: false,
});

const MAPBOX_TOKEN =
  'proces.env.MAPBOX_TOKEN';
const LAT = 28.5383;
const LON = -81.3792;
const INITIAL_VIEW_STATE = {
  latitude: LAT,
  longitude: LON,
  zoom: 9,
  maxZoom: 16,
  pitch: 50,
  bearing: 0,
};
}
class TestMapTwo extends Component {
  constructor(props) {
    super(props);
    this.state = {
      viewState: INITIAL_VIEW_STATE,
      GeojsonPoint: [],
      hover: {
        x: 0,
        y: 0,
        hoveredObject: null,
      },
      settings: Object.keys(HEXAGON_CONTROLS).reduce(
        (accu, key) => ({
          ...accu,
          [key]: HEXAGON_CONTROLS[key].value,
        }),
        {},
      ),
      style: 'https://api.maptiler.com/maps/bright/style.json?key=KO5UrPKClQethpf7vtZh',
      completed_fetch: false,
      explore_data: new Array(19).fill(0).reduce(
        (prev, curr) => [
          ...prev,
          {
            x: prev.slice(-1)[0].x + 1,
            y: prev.slice(-1)[0].y * (0.9 + Math.random() * 0.2),
          },
        ],
        [{ x: 0, y: 10 }],
      ),
      selectedTime: null,
    };
  }

  componentDidMount() {
    this._fetchData();
  }

  async _fetchData() {
    this.setState({ ...this.state });
    await fetch(
      //process.env.TEMP_DATA_FETCH
      `process.env.GEOSERVER_URL`,
    )
      .then((res) => res.json())
      .then(
        (result) => {
          this.setState({
            GeojsonPoint: result.features,
            chart_data: result.features.map(function(elem) {
              return {
                x: elem.properties.date_scraped,
                y: elem.properties.price,
              };
            }),
            completed_fetch: true,
          });
          console.log('result', result);
        },

        (error) => {
          console.log(error);
          this.setState({ ...this.state });
        },
      );
  }

  onStyleChange = (style) => {
    this.setState({ style });
  };
  _updateLayerSettings(settings) {
    this.setState({ settings });
  }

  //chart functionality
  _onHighlight(highlightedTime) {
    this.setState({ highlightedTime });
  }

  _onSelect(selectedTime) {
    this.setState({
      selectedTime: selectedTime === this.state.selectedTime ? null : selectedTime,
    });
  }

  render() {
    const data = this.state.GeojsonPoint;
    if (!data.length) {
      return null;
    }
    const { hover, settings } = this.state;

    const {
      onViewStateChange = ({ viewState }) => this.setState({ viewState }),
      viewState = this.state.viewState,
    } = this.props;

    const mapRef = React.createRef();

    return (
      <div>
        <DeckGL
          layers={renderLayers({
            data: this.state.GeojsonPoint,
            onHover: (hover) => this._onHover(hover),
            settings: this.state.settings,
          })}
          views={new MapView({ id: 'map' })}
          viewState={viewState}
          onViewStateChange={onViewStateChange}
          controller={true}
          getTooltip={({ object }) =>
            object &&
            `Rent: ${object.properties.price}\nBeds: ${object.properties.beds}\nBaths: ${object.properties.baths}\nSq Ft: ${object.properties.size}`
          }
        >
          <StaticMap
            ref={mapRef}
            viewId="map"
            {...viewState}
            reuseMaps
            mapStyle={this.state.style}
            preventStyleDiffing={true}
            mapboxApiAccessToken={MAPBOX_TOKEN}
          ></StaticMap>
          <Geocoder
            mapRef={mapRef}
            onViewportChange={onViewStateChange}
            mapboxApiAccessToken={MAPBOX_TOKEN}
            position="top-left"
            countries="us"
          />
        </DeckGL>
      </div>
    );
  }
}

The map and page render fine without the geocoder component, but when it loads my console lights up with a lot of information.

Uncaught Error: WebGL context: Could not create a WebGL context, VENDOR = 0x15ad, context.js:29
DEVICE = 0x0405, GL_VENDOR = Google Inc., GL_RENDERER = ANGLE (VMware, Inc., llvmpipe (LLVM 10.0.0, 128 bits), OpenGL 3.3 Core), GL_VERSION = 2.10,ea8043b73f93, Sandboxed = no, Optimus = no, AMD switchable = no, Reset notification strategy = 0x8261, ErrorMessage = bindToCurrentThread failed: .

Uncaught Error: WebGL context: Failed to create a WebGL2 context.

react_devtools_backend.js:2450 This page appears to be missing CSS declarations for Mapbox GL JS, which may cause the map to display incorrectly. Please ensure your page includes mapbox-gl.css, as described in https://www.mapbox.com/mapbox-gl-js/api/. 

Uncaught TypeError: Cannot read property 'addControl' of null

Uncaught Type Error: Cannot read property 'remove' of undefined

The above errror occured in the <a> component:

Uncaught TypeError: Cannot read property 'addControl' of null

The above error occured in the <MyApp> component:
React will try to recreate this component tree from scratch using the error boundary you provided, Container.
SamSamskies commented 3 years ago

HI @andrewshrout, have you tried putting the Geocoder inside the StaticMap component as a child? If that doesn't work, it would help if you could provide the simplest code possible that demonstrates your issue on Code Sandbox.

andrewshrout commented 3 years ago

https://codesandbox.io/s/testmap-v21w4?file=/src/App.js

Geocoder inside the staticmap as a child didn't work in my nextjs project. It created the map with the geocoder, but the second it moves, it breaks again. It's also not clickable. I'm guessing something is wrong with it being beneath Deck.gl's layer, and the onViewStateChange function.

I've done my best in the link above to create a super simplified version of what I'm trying to do, but I can't recreate the issues of my project. I do know that adding the geocoder in either of them breaks.

I've been reading over every single issue on this repository with a fine toothed comb, with #44 and #56 being the most useful to me. I'm a new dev in general but I guess I'm just incredibly confused and frustrated about adding this geocoder to a class component using Deck.gl.

andrewshrout commented 3 years ago

https://codesandbox.io/s/react-map-gl-geocoder-v2-example-forked-2zbz0?file=/src/index.js

Got a somewhat working version by forking your functional component example, passing deck.gl as a child. Now there's working deck.gl, and geocoding. However I feel like I've just sidestepped my lack of understanding for why it won't work with a static map or in a class component.

And now I have to read up on how to fetch data in a functional component because I can't get my geoservers data onto the map.

SamSamskies commented 3 years ago

Oh I see what the problem is with your class component. It's creating a new ref on every render. The ref should only be created once. Here's the old class component example that used to be in the README.

import 'mapbox-gl/dist/mapbox-gl.css'
import 'react-map-gl-geocoder/dist/mapbox-gl-geocoder.css'
import React, { Component } from 'react'
import MapGL from 'react-map-gl'
import Geocoder from 'react-map-gl-geocoder'

function getAccessToken() {
  var accessToken = null;

  if (typeof window !== 'undefined' && window.location) {
    var match = window.location.search.match(/access_token=([^&\/]*)/);
    accessToken = match && match[1];
  }

  if (!accessToken && typeof process !== 'undefined') {
    // Note: This depends on bundler plugins (e.g. webpack) inmporting environment correctly
    accessToken = accessToken || process.env.MapboxAccessToken; // eslint-disable-line
  }

  return accessToken || null;
}

// Ways to set Mapbox token: https://uber.github.io/react-map-gl/#/Documentation/getting-started/about-mapbox-tokens
const MAPBOX_TOKEN = getAccessToken()

class Example extends Component {
  state = {
    viewport: {
      width: 400,
      height: 400,
      latitude: 37.7577,
      longitude: -122.4376,
      zoom: 8
    }
  }

  mapRef = React.createRef()

  componentDidMount() {
    window.addEventListener('resize', this.resize)
    this.resize()
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.resize)
  }

  resize = () => {
    this.handleViewportChange({
      width: window.innerWidth,
      height: window.innerHeight
    })
  }

  handleViewportChange = (viewport) => {
    this.setState({
      viewport: { ...this.state.viewport, ...viewport }
    })
  }

  // if you are happy with Geocoder default settings, you can just use handleViewportChange directly
  handleGeocoderViewportChange = (viewport) => {
    const geocoderDefaultOverrides = { transitionDuration: 1000 }

    return this.handleViewportChange({
      ...viewport,
      ...geocoderDefaultOverrides
    })
  }

  render() {
    return (
      <MapGL
        ref={this.mapRef}
        {...this.state.viewport}
        onViewportChange={this.handleViewportChange}
        mapboxApiAccessToken={MAPBOX_TOKEN}>
        <Geocoder
          mapRef={this.mapRef}
          onViewportChange={this.handleGeocoderViewportChange}
          mapboxApiAccessToken={MAPBOX_TOKEN}
        />
      </MapGL>
    )
  }
}

export default Example