Search code examples
reactjsreduxreact-hooksdeck.glreact-map-gl

React useEffect doesn't change data displayed on map


I'm playing with uber's react-map-gl and deck-gl libraries to try to display data dynamically.I've got 2 components in my react little app.A navigation bar and a Mapbox map.So here is my pipeline.When the page first loads,only the map is displayed without any marker or visual data.But when i click on one of navigation bar link,an action creator gets called,when i make an ajax call to fetch some data,and from the action that is dispatched a pass my response data as payload and the reducer is reached so that i have a new version of the store.the data that i would like to visualize in they stor key : mainProjects that contains an array of geojson.My click on a navbar link succefully updates that value,and in the actual Map component,i can load the new values but useEffect does not update my localstate.Here is my map code:

import React, { useState, useEffect, useContext } from "react";
import { StaticMap } from "react-map-gl";
import { MapContext } from "./contexts/MapProvider";
import DeckGL, { GeoJsonLayer } from "deck.gl";
import chroma from "chroma-js";
import { connect } from "react-redux";

const MAPBOX_TOKEN =
  "pk.mykey";
const mapStyle = "mapbox://mymapstyle";

function getEnergyFillColor(regime) {
  const ENERGY_COLORS = {
    naturalGaz: "#FF8500",
    coal: "#99979A",
    nuclear: "#E0399E",
    hydroelectric: "#0082CB",
    Wind: "#00B53B",
    solar: "#DBCA00",
    oil: "#FF0009",
    other: "#FFEFD3"
  };

  let color;
  switch (regime) {
    case "Hydraulique":
      color = ENERGY_COLORS.hydroelectric;
      break;
    case "Thermique":
      color = ENERGY_COLORS.nuclear;
      break;
    default:
      color = ENERGY_COLORS.other;
      break;
  }
  color = chroma(color)
    .alpha(0.667)
    .rgba();
  color[3] *= 255;
  return color;
}

function _onClick(info) {
  if (info.object) {
    // eslint-disable-next-line
    alert(
      `${info.object.properties.NOM} (${info.object.properties.PROVINCE}) - ${
        info.object.properties.PUISSANCE
      }MW`
    );
  }
}

function Map({ mainProjects }) {
  const { viewport, setViewport, onLoad } = useContext(MapContext);
  const [airports, setAireports] = useState();
  const [electricalEnergy, setElectricalEnergy] = useState();
  const [hospitals, setHospitals] = useState();
  const [roads, setRoads] = useState();
  useEffect(() => {
    if (mainProjects.length) {
      setHospitals(mainProjects[0].hospitals);
      setAireports(mainProjects[1].aeroports);
      setElectricalEnergy(mainProjects[2].electricite);
      setRoads(mainProjects[2].routes);
    }
  }, [airports, electricalEnergy, hospitals, roads]);
  const layers = [
    //ENERGIE ELECTRIQUE
    new GeoJsonLayer({
      id: "energy",
      data: electricalEnergy,
      // Styles
      filled: true,
      pointRadiusMinPixels: 20,
      opacity: 1,
      pointRadiusScale: 2000,
      getRadius: energyItem => energyItem.properties.puissance * 3.14,
      getFillColor: energyItem =>
        getEnergyFillColor(energyItem.properties.regime),
      // Interactive props
      pickable: true,
      autoHighlight: true,
      onClick: _onClick
    }),

    //AEROPORTS
    new GeoJsonLayer({
      id: "airports",
      data: airports,
      // Styles
      filled: true,
      pointRadiusMinPixels: 20,
      opacity: 1,
      pointRadiusScale: 2000,
      getRadius: energyItem => energyItem.properties.PUISSANCE * 3.14,
      getFillColor: energyItem =>
        getEnergyFillColor(energyItem.properties.REGIME),
      // Interactive props
      pickable: true,
      autoHighlight: true,
      onClick: _onClick
    }),

    //HOSPITALS
    new GeoJsonLayer({
      id: "hospitals",
      data: hospitals,
      // Styles
      filled: true,
      pointRadiusMinPixels: 20,
      opacity: 1,
      pointRadiusScale: 2000,
      getRadius: energyItem => energyItem.properties.PUISSANCE * 3.14,
      getFillColor: energyItem =>
        getEnergyFillColor(energyItem.properties.REGIME),
      // Interactive props
      pickable: true,
      autoHighlight: true,
      onClick: _onClick
    }),

    //ROUTES
    new GeoJsonLayer({
      id: "roads",
      data: roads,
      pickable: true,
      stroked: false,
      filled: true,
      extruded: true,
      lineWidthScale: 20,
      lineWidthMinPixels: 2,
      getFillColor: [160, 160, 180, 200],
      getLineColor: d => [255, 160, 20, 200],
      // getLineColor: d => colorToRGBArray(d.properties.color),
      getRadius: 100,
      getLineWidth: 1,
      getElevation: 30,
      onHover: ({ object, x, y }) => {
        // const tooltip = object.properties.name || object.properties.station;
        /* Update tooltip
           http://deck.gl/#/documentation/developer-guide/adding-interactivity?section=example-display-a-tooltip-for-hovered-object
        */
      },
      onClick: _onClick
    })
  ];

  return (
    <>
      <link
        href="https://api.tiles.mapbox.com/mapbox-gl-js/v0.53.0/mapbox-gl.css"
        rel="stylesheet"
      />
      <DeckGL
        initialViewState={viewport}
        viewState={viewport}
        controller={true}
        layers={layers}
        onLoad={onLoad}
        onViewportChange={nextViewport => setViewport(nextViewport)}
      >
        <StaticMap mapboxApiAccessToken={MAPBOX_TOKEN} mapStyle={mapStyle} />
      </DeckGL>
    </>
  );
}

const mapStateToProps = ({
  selectedLinks: { sectorSelected, provinceSelected, subSectorSelected },
  mainProjects
}) => {
  if (sectorSelected || provinceSelected || subSectorSelected) {
    return {
      mainProjects
    };
  } else {
    return {
      mainProjects: []
    };
  }
};
export default connect(mapStateToProps)(Map);

In the above code,i try to update my local state values by is setters,but useEffect doesn't seem to work.And it looks like it's only called once,at when the component renders for the first time.How can i solve this problem?

Thank you!!


Solution

  • Your useEffect has a set of dependencies that donˋt match those, which are actually used. You are setting your local state with elements of mainProjects, so useEffect will only do something when mainProjects changes.

    You donˋt seem to be doing anything with your useState-Variables, so you donˋt change state, so react doesnˋt rerender.

    Update: it is really important to check, that the dependency-array (2nd argument to useEffect) and the used variables inside the function (1st argument) correspond, else bad things will happen ;-)

    There is an eslint-rule for that: https://www.npmjs.com/package/eslint-plugin-react-hooks