Issue
I am building a dashboard filled with Esri maps that are editable. The structure of the components is something like this:
<Dashboard>
<Visuals>
<EventsMap>
<PointsLayer/>
</EventsMap>
</Visuals>
</Dashboard>
When a user edits something inside of the Dashboards component(i.e. the color of the points on a map) the data does get passed through to PointsLayer which then should re-render and show the updated color, but only updates if I refresh the page. Is it because I don't have a render method? The PointsLayer component:
import {useState, useEffect} from 'react';
import {loadModules} from 'esri-loader';
import {getFormattedDate} from 'Lib/helpers';
import styles from './Summary.module.css';
import point from "@arcgis/core/geometry/Point";
const PointsLayer = (props) => {
const data = props.data;
const color = data?.color;
console.log(color)
const humanize = (str) =>{
let i, frags = str.split('_');
for (i=0; i<frags.length; i++) {
frags[i] = frags[i].charAt(0).toUpperCase() + frags[i].slice(1);
}
return frags.join(' ');
}
const pluralize = (val, word, plural = word + 's') => {
const _pluralize = (num, word, plural = word + 's') =>
[1, -1].includes(Number(num)) ? word : plural;
if (typeof val === 'object') return (num, word) => _pluralize(num, word, val[word]);
return _pluralize(val, word, plural);
};
const [graphic, setGraphic] = useState(null);
useEffect(() => {
loadModules(['esri/Graphic']).then(([Graphic]) => {
// Parse out the Lat-Long from each Event and the doc_count
for (let i = 0; i < data?.events.length; i++) {
const point = {
type: "point", // autocasts as new Point
longitude: data?.events[i]?.location.split(",")[1],
latitude: data?.events[i]?.location.split(",")[0]
};
// Create a symbol for rendering the graphic
const symbol = {
type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
style: "circle", color: color, // Color Selected on popup
size: "12px", outline: {
color: [255, 255, 255], // White
width: 1.5
}
};
// Create attributes for popup
const attributes = {
watcherType: humanize(data?.events[i]?.doc_fields?.watcher_type),
eventCount: data?.events[i]?.doc_count,
plural: pluralize(data?.events[i]?.doc_count, 'Event'),
deviceName: data?.events[i]?.key,
lat: data?.events[i]?.location.split(",")[0],
long: data?.events[i]?.location.split(",")[1],
account: data?.events[i]?.doc_fields?.account_id,
address: data?.events[i]?.doc_fields['@service_address'],
meterId: data?.events[i]?.doc_fields?.meter_id,
lastEvent: getFormattedDate(data?.events[i]?.doc_fields['@time_raised_last'], '')
};
// Create popup template
const popupTemplate = {
title: "{eventCount} {watcherType} {plural}",
content:
"<ul><li><b>Address:</b> {address}</li>" +
"<li><b>Account ID:</b> {account}</li>" +
"<li><b>Meter ID:</b> {meterId}</li>" +
"<li><b>Last Event:</b> {lastEvent}</li>" +
"<li><a href='https://maps.google.com/maps?q=&layer=c&cbll={lat},{long}&cbp='>Google Street View</a></li></ul>"
};
// Add the multiPoints to a new graphic
const graphic = new Graphic({
geometry: point,
symbol: symbol,
attributes: attributes,
popupTemplate: popupTemplate
});
setGraphic(graphic);
props.view.graphics.add(graphic);
}
}).catch((err) => console.error(err));
return function cleanup() {
props.view.graphics.remove(graphic);
};
}, []);
return null;
}
export default PointsLayer
An image to visualize what I am working on:

Solution
Try adding the graphic state to the useEffect dependency array [graphic]
useEffect(function, [graphic]) or useEffect(function, [props]) but props may cause more re-renders than you may want
A more complete example would look like this.
const [graphic, setGraphic] = useState(null);
useEffect(() => {
loadModules(['esri/Graphic']).then(([Graphic]) => {
// Parse out the Lat-Long from each Event and the doc_count
for (let i = 0; i < data?.events.length; i++) {
const point = {
type: "point", // autocasts as new Point
longitude: data?.events[i]?.location.split(",")[1],
latitude: data?.events[i]?.location.split(",")[0]
};
// Create a symbol for rendering the graphic
const symbol = {
type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
style: "circle", color: color, // Color Selected on popup
size: "12px", outline: {
color: [255, 255, 255], // White
width: 1.5
}
};
// Create attributes for popup
const attributes = {
watcherType: humanize(data?.events[i]?.doc_fields?.watcher_type),
eventCount: data?.events[i]?.doc_count,
plural: pluralize(data?.events[i]?.doc_count, 'Event'),
deviceName: data?.events[i]?.key,
lat: data?.events[i]?.location.split(",")[0],
long: data?.events[i]?.location.split(",")[1],
account: data?.events[i]?.doc_fields?.account_id,
address: data?.events[i]?.doc_fields['@service_address'],
meterId: data?.events[i]?.doc_fields?.meter_id,
lastEvent: getFormattedDate(data?.events[i]?.doc_fields['@time_raised_last'], '')
};
// Create popup template
const popupTemplate = {
title: "{eventCount} {watcherType} {plural}",
content:
"<ul><li><b>Address:</b> {address}</li>" +
"<li><b>Account ID:</b> {account}</li>" +
"<li><b>Meter ID:</b> {meterId}</li>" +
"<li><b>Last Event:</b> {lastEvent}</li>" +
"<li><a href='https://maps.google.com/maps?q=&layer=c&cbll={lat},{long}&cbp='>Google Street View</a></li></ul>"
};
// Add the multiPoints to a new graphic
const graphic = new Graphic({
geometry: point,
symbol: symbol,
attributes: attributes,
popupTemplate: popupTemplate
});
setGraphic(graphic);
props.view.graphics.add(graphic);
}
}).catch((err) => console.error(err));
return function cleanup() {
props.view.graphics.remove(graphic);
};
}, [graphic]);
or using props,
const [graphic, setGraphic] = useState(null);
useEffect(() => {
loadModules(['esri/Graphic']).then(([Graphic]) => {
// Parse out the Lat-Long from each Event and the doc_count
for (let i = 0; i < data?.events.length; i++) {
const point = {
type: "point", // autocasts as new Point
longitude: data?.events[i]?.location.split(",")[1],
latitude: data?.events[i]?.location.split(",")[0]
};
// Create a symbol for rendering the graphic
const symbol = {
type: "simple-marker", // autocasts as new SimpleMarkerSymbol()
style: "circle", color: color, // Color Selected on popup
size: "12px", outline: {
color: [255, 255, 255], // White
width: 1.5
}
};
// Create attributes for popup
const attributes = {
watcherType: humanize(data?.events[i]?.doc_fields?.watcher_type),
eventCount: data?.events[i]?.doc_count,
plural: pluralize(data?.events[i]?.doc_count, 'Event'),
deviceName: data?.events[i]?.key,
lat: data?.events[i]?.location.split(",")[0],
long: data?.events[i]?.location.split(",")[1],
account: data?.events[i]?.doc_fields?.account_id,
address: data?.events[i]?.doc_fields['@service_address'],
meterId: data?.events[i]?.doc_fields?.meter_id,
lastEvent: getFormattedDate(data?.events[i]?.doc_fields['@time_raised_last'], '')
};
// Create popup template
const popupTemplate = {
title: "{eventCount} {watcherType} {plural}",
content:
"<ul><li><b>Address:</b> {address}</li>" +
"<li><b>Account ID:</b> {account}</li>" +
"<li><b>Meter ID:</b> {meterId}</li>" +
"<li><b>Last Event:</b> {lastEvent}</li>" +
"<li><a href='https://maps.google.com/maps?q=&layer=c&cbll={lat},{long}&cbp='>Google Street View</a></li></ul>"
};
// Add the multiPoints to a new graphic
const graphic = new Graphic({
geometry: point,
symbol: symbol,
attributes: attributes,
popupTemplate: popupTemplate
});
setGraphic(graphic);
props.view.graphics.add(graphic);
}
}).catch((err) => console.error(err));
return function cleanup() {
props.view.graphics.remove(graphic);
};
}, [props]);
I would think just using the graphic state would be closer to what you are looking for. Leaving the dependency array empty causes the useEffect hook to only fire on the initial component mount.
Adding the graphic state to the array tells the useEffect to watch for changes in the graphic state, and if it changes, refire the useEffect again
This post may be helpful Hooks and Dependency Arrays
Answered By - AndrewBrntt
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.