I’m working on a React component that toggles visibility of map layers in an Esri map. My widget works perfectly when I hardcode the layers configuration, but fails to add layers when I load the configuration from a config.json file.
Working Code Example:
<code>import { React, AllWidgetProps } from 'jimu-core';
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis';
import FeatureLayer from 'esri/layers/FeatureLayer';
import { IMConfig } from '../config';
const { useState, useEffect } = React;
url: "https://example.com/arcgis/rest/services/Layer1/MapServer/0",
url: "https://example.com/arcgis/rest/services/Layer2/MapServer/0",
const Widget = (props: AllWidgetProps<IMConfig>) => {
const [jimuMapView, setJimuMapView] = useState<JimuMapView>(null);
const [layers, setLayers] = useState([]); // State initialization moved after handler definition for clarity
console.log('Config on init:', props.config.layers[0].url); // Check initial config
console.log('Config on effect:', props.config); // Check config inside effect
if (props.config && props.config.layers) {
const initialLayers = props.config.layers.map(layerConfig => ({
layer: new FeatureLayer({
console.log('Initial layers:', initialLayers); // Debug the mapped layers
setLayers([...initialLayers]);
const activeViewChangeHandler = (jmv: JimuMapView) => {
console.log('layers:', layers);
layers.forEach(({ layer }) => jmv.view.map.add(layer));
const toggleLayerVisibility = (index) => {
console.log(`Toggling visibility for layer at index: ${index}`);
const newLayers = layers.map((layer, idx) => {
console.log(`Layer : ${layer.layer}`);
console.log(`Layer before toggle: ${layer.title}, Visible: ${layer.visible}`);
layer.layer.visible = !layer.layer.visible;
console.log(`Layer after toggle: ${layer.title}, Visible: ${layer.visible}`);
<div className="widget-starter jimu-widget">
{props.useMapWidgetIds && props.useMapWidgetIds.length === 1 && (
<JimuMapViewComponent useMapWidgetId={props.useMapWidgetIds[0]} onActiveViewChange={activeViewChangeHandler} />
{layers.length > 0 ? layers.map((layer, index) => (
checked={layer.layer.visible}
onChange={() => toggleLayerVisibility(index)}
aria-labelledby={`layer-name-${index}`}
<span id={`layer-name-${index}`}>{layer.name}</span>
)) : <p>Loading layers...</p>}
<code>import { React, AllWidgetProps } from 'jimu-core';
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis';
import FeatureLayer from 'esri/layers/FeatureLayer';
import { IMConfig } from '../config';
const { useState, useEffect } = React;
const layersConfig = [
{
name: "Layer 1",
url: "https://example.com/arcgis/rest/services/Layer1/MapServer/0",
},
{
name: "Layer 2",
url: "https://example.com/arcgis/rest/services/Layer2/MapServer/0",
}
];
const Widget = (props: AllWidgetProps<IMConfig>) => {
const [jimuMapView, setJimuMapView] = useState<JimuMapView>(null);
const [layers, setLayers] = useState([]); // State initialization moved after handler definition for clarity
console.log('Config on init:', props.config.layers[0].url); // Check initial config
useEffect(() => {
console.log('Config on effect:', props.config); // Check config inside effect
if (props.config && props.config.layers) {
const initialLayers = props.config.layers.map(layerConfig => ({
...layerConfig,
layer: new FeatureLayer({
url: layerConfig.url,
title: layerConfig.name,
visible: false
})
}));
console.log('Initial layers:', initialLayers); // Debug the mapped layers
setLayers([...initialLayers]);
}
}, [props.config]);
const activeViewChangeHandler = (jmv: JimuMapView) => {
if (jmv) {
setJimuMapView(jmv);
console.log('layers:', layers);
layers.forEach(({ layer }) => jmv.view.map.add(layer));
}
};
const toggleLayerVisibility = (index) => {
console.log(`Toggling visibility for layer at index: ${index}`);
const newLayers = layers.map((layer, idx) => {
if (idx === index) {
console.log(`Layer : ${layer.layer}`);
console.log(`Layer before toggle: ${layer.title}, Visible: ${layer.visible}`);
layer.layer.visible = !layer.layer.visible;
console.log(`Layer after toggle: ${layer.title}, Visible: ${layer.visible}`);
}
return layer;
});
setLayers(newLayers);
};
return (
<div className="widget-starter jimu-widget">
{props.useMapWidgetIds && props.useMapWidgetIds.length === 1 && (
<JimuMapViewComponent useMapWidgetId={props.useMapWidgetIds[0]} onActiveViewChange={activeViewChangeHandler} />
)}
<div>
{layers.length > 0 ? layers.map((layer, index) => (
<div key={index}>
<label>
<input
type="checkbox"
checked={layer.layer.visible}
onChange={() => toggleLayerVisibility(index)}
aria-labelledby={`layer-name-${index}`}
/>
<span id={`layer-name-${index}`}>{layer.name}</span>
</label>
</div>
)) : <p>Loading layers...</p>}
</div>
</div>
);
};
export default Widget;
</code>
import { React, AllWidgetProps } from 'jimu-core';
import { JimuMapViewComponent, JimuMapView } from 'jimu-arcgis';
import FeatureLayer from 'esri/layers/FeatureLayer';
import { IMConfig } from '../config';
const { useState, useEffect } = React;
const layersConfig = [
{
name: "Layer 1",
url: "https://example.com/arcgis/rest/services/Layer1/MapServer/0",
},
{
name: "Layer 2",
url: "https://example.com/arcgis/rest/services/Layer2/MapServer/0",
}
];
const Widget = (props: AllWidgetProps<IMConfig>) => {
const [jimuMapView, setJimuMapView] = useState<JimuMapView>(null);
const [layers, setLayers] = useState([]); // State initialization moved after handler definition for clarity
console.log('Config on init:', props.config.layers[0].url); // Check initial config
useEffect(() => {
console.log('Config on effect:', props.config); // Check config inside effect
if (props.config && props.config.layers) {
const initialLayers = props.config.layers.map(layerConfig => ({
...layerConfig,
layer: new FeatureLayer({
url: layerConfig.url,
title: layerConfig.name,
visible: false
})
}));
console.log('Initial layers:', initialLayers); // Debug the mapped layers
setLayers([...initialLayers]);
}
}, [props.config]);
const activeViewChangeHandler = (jmv: JimuMapView) => {
if (jmv) {
setJimuMapView(jmv);
console.log('layers:', layers);
layers.forEach(({ layer }) => jmv.view.map.add(layer));
}
};
const toggleLayerVisibility = (index) => {
console.log(`Toggling visibility for layer at index: ${index}`);
const newLayers = layers.map((layer, idx) => {
if (idx === index) {
console.log(`Layer : ${layer.layer}`);
console.log(`Layer before toggle: ${layer.title}, Visible: ${layer.visible}`);
layer.layer.visible = !layer.layer.visible;
console.log(`Layer after toggle: ${layer.title}, Visible: ${layer.visible}`);
}
return layer;
});
setLayers(newLayers);
};
return (
<div className="widget-starter jimu-widget">
{props.useMapWidgetIds && props.useMapWidgetIds.length === 1 && (
<JimuMapViewComponent useMapWidgetId={props.useMapWidgetIds[0]} onActiveViewChange={activeViewChangeHandler} />
)}
<div>
{layers.length > 0 ? layers.map((layer, index) => (
<div key={index}>
<label>
<input
type="checkbox"
checked={layer.layer.visible}
onChange={() => toggleLayerVisibility(index)}
aria-labelledby={`layer-name-${index}`}
/>
<span id={`layer-name-${index}`}>{layer.name}</span>
</label>
</div>
)) : <p>Loading layers...</p>}
</div>
</div>
);
};
export default Widget;
Non-Working Code (using config.json):
<code>// Similar to the above, but `layersConfig` is replaced with `props.config.layers`
<code>// Similar to the above, but `layersConfig` is replaced with `props.config.layers`
</code>
// Similar to the above, but `layersConfig` is replaced with `props.config.layers`
console.log('Config on effect:', props.config); // Check config inside effect
if (props.config && props.config.layers) {
const initialLayers = props.config.layers.map(layerConfig => ({
layer: new FeatureLayer({
console.log('Initial layers:', initialLayers); // Debug the mapped layers
setLayers([...initialLayers]);
<code>useEffect(() => {
console.log('Config on effect:', props.config); // Check config inside effect
if (props.config && props.config.layers) {
const initialLayers = props.config.layers.map(layerConfig => ({
...layerConfig,
layer: new FeatureLayer({
url: layerConfig.url,
title: layerConfig.name,
visible: false
})
}));
console.log('Initial layers:', initialLayers); // Debug the mapped layers
setLayers([...initialLayers]);
}
}, [props.config]);
</code>
useEffect(() => {
console.log('Config on effect:', props.config); // Check config inside effect
if (props.config && props.config.layers) {
const initialLayers = props.config.layers.map(layerConfig => ({
...layerConfig,
layer: new FeatureLayer({
url: layerConfig.url,
title: layerConfig.name,
visible: false
})
}));
console.log('Initial layers:', initialLayers); // Debug the mapped layers
setLayers([...initialLayers]);
}
}, [props.config]);
config.json:
<code>{
"layers": [
{
"name": "Layer 1",
"url": "hidden",
},
{
"name": "Layer 2",
"url": "hidden"
}
]
}
</code>
{
"layers": [
{
"name": "Layer 1",
"url": "hidden",
},
{
"name": "Layer 2",
"url": "hidden"
}
]
}
config.ts:
<code>import { ImmutableObject } from 'seamless-immutable';
export interface LayerConfig {
export interface Config {
export type IMConfig = ImmutableObject<Config>;
<code>import { ImmutableObject } from 'seamless-immutable';
export interface LayerConfig {
name: string;
url: string;
}
export interface Config {
layers: LayerConfig[];
}
export type IMConfig = ImmutableObject<Config>;
</code>
import { ImmutableObject } from 'seamless-immutable';
export interface LayerConfig {
name: string;
url: string;
}
export interface Config {
layers: LayerConfig[];
}
export type IMConfig = ImmutableObject<Config>;
Error Message:
<code>TypeError: Cannot read properties of undefined (reading 'add')
<code>TypeError: Cannot read properties of undefined (reading 'add')
</code>
TypeError: Cannot read properties of undefined (reading 'add')
This error occurs at the line where I try to add layers to the map:
<code>layers.forEach(({ layer }) => jmv.view.map.add(layer));
<code>layers.forEach(({ layer }) => jmv.view.map.add(layer));
</code>
layers.forEach(({ layer }) => jmv.view.map.add(layer));
Extra:
Here is initial layers for the working sample:
enter image description here
And for the non-working:
enter image description here
- Checked the URLs in the config file to ensure they are correct and accessible.
- Added logs to various points in the component to ensure the data is being loaded and state changes are occurring as expected.
- Ensured the component is correctly re-rendering on state changes.
Please help I’ve been trying to figure it out by different means for the past 10 hours…