Changing the react-leaflet layer url by mutating react state throws error -> TypeError: Cannot read property 'call' of undefined
Changing the react-leaflet layer url by mutating react state throws error -> TypeError: Cannot read property 'call' of undefined
我正在尝试使用以下代码构建具有可选动态图层的地图 URL。
当我尝试通过状态更改图层 URL 时,它在所附的屏幕截图中显示了错误。
我已将所有图块存储在图块数组变量中,并且我正在尝试通过更改图块的索引来更改 URL。
我第一次 运行 项目工作正常,但是当我单击按钮并尝试更改图块的索引以呈现另一个地图图块时,它在屏幕截图中显示了错误。
我还应该提到,开发人员工具 chrome 的控制台选项卡中没有显示任何错误。
有没有办法解决这个问题?任何想法将不胜感激:)
错误截图:
const LeafletContainer = () => {
const [baseViewCoords, setBaseViewCoords] = useState([37.715, 44.8611]);
const [map, setMap] = useState();
const merged_20181223 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20180924 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20190323 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20190626 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20190919 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20191218 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20200625 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20200918 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const tiles = [
merged_20190323,
merged_20191218,
merged_20181223,
merged_20180924,
merged_20190626,
merged_20190919,
merged_20200625,
merged_20200918,
];
const [leftTileIndex, setLeftTileIndex] = useState(0);
const [rightTileIndex, setTRightTileIndex] = useState(7);
const changeTileHandler = () => {
setLeftTileIndex((prev) => {
if (leftTileIndex === tiles.length - 1) {
return 0;
}
return prev + 1;
});
};
useEffect(() => {
if (map) {
L.control
.splitMap(
L.tileLayer(tiles[leftTileIndex]._url, {
tms: true,
opacity: 1,
attribution: "",
minZoom: 1,
maxZoom: 16,
}).addTo(map),
L.tileLayer(tiles[rightTileIndex]._url, {
tms: true,
opacity: 1,
attribution: "",
minZoom: 1,
maxZoom: 16,
})
.addTo(map)
.addTo(map)
)
.addTo(map);
}
}, [map, leftTileIndex, rightTileIndex]);
useEffect(() => {
if (map) {
map.setView(baseViewCoords);
}
}, [map, baseViewCoords]);
return (
<div style={{ position: "relative" }}>
<SearchArea
{...{
changeTileHandler,
}}
/>
<Options />
<Coordinate {...{ baseViewCoords }} />
<div id="map">
<MapContainer
style={{ height: "100%" }}
center={baseViewCoords}
zoom={9}
scrollWheelZoom={true}
whenCreated={(map) => {
setMap(map);
}}
>
<TileLayer
tms={true}
minZoom={1}
maxZoom={16}
opacity={1}
attribution=""
url={tiles[leftTileIndex]._url}
/>
<TileLayer
minZoom={1}
maxZoom={16}
opacity={1}
tms={true}
attribution=""
url={tiles[rightTileIndex]._url}
/>
</MapContainer>
</div>
</div>
);
};
export default LeafletContainer;
我不确定为什么这个错误会变得像 leafet 的 Events
class 一样深,但我确实看到了一个主要问题。在您的 useEffect 中,每次用户触发 setLeftTileIndex
或 setRightTileIndex
的状态更改时,都会将 L.Control.splitmap
的 新实例添加到地图中。虽然 react-leaflet <TileLayer />
组件被编写为处理 url 更改并自动更新底层 L.TileLayer
,但您的 L.control.splitmap
版本不是 - 它只是添加一个新的分割图。同时,旧的指的是不再存在的 L.TileLayer。您应该使用 leaflet-splitmap
的 setLeftLayers
和 setRightLayers
方法:
// initialize this outside the useEffect
const [splitMapAdded, setSplitMapAdded] = useState(false)
const splitMap = L.control.splitMap(
L.tileLayer("left_side_initial_url", { options }),
L.tileLayer("right_side_initial_url", { options })
)
useEffect(() => {
if (map && !splitMapAdded) {
splitmap.addTo(map);
setSplitMapAdded()true
}
}, [map]);
// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
if (map && splitMapAdded) {
splitMap.setLeftLayers(
[L.tileLayer(tiles[leftTileIndex]._url],
{ options }
)
}
}, [map, leftTileIndex])
// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
if (map && splitMapAdded) {
splitMap.setRightLayers(
[L.tileLayer(tiles[rightTileIndex]._url],
{ options }
)
}
}, [map, rightTileIndex])
所以现在 splitmap 控件被添加到具有一些初始值的组件安装上。当这些状态变量发生变化时,您无需重新创建控件,而是使用其内部方法来更改 url。希望这会让您开始挖掘其中的一些错误。
另一种方式:
我还可以补充一点,这可以通过创建一个 react-leaflet v3 自定义组件来巧妙地管理。您可以使用创建函数创建组件:
const createSplitMap = (props, context) => {
const instance = L.control.splitmap(
L.tileLayer(props.leftTileLayerUrl, leftTileLayerOptions),
L.tileLayer(props.rightTileLayerUrl, rightTileLayerOptions)
)
return { instance, context }
}
那么您的更新函数可能如下所示:
const updateSplitMap = (instance, props, prevProps) => {
if (prevProps.leftTileLayerUrl !== props.leftTileLayerUrl){
instance.setLeftLayers(L.tileLayer(props.leftTileLayerUrl))
}
if (prevProps.rightTileLayerUrl !== props.rightTileLayerUrl){
instance.setLeftLayers(L.tileLayer(props.rightTileLayerUrl))
}
}
要将它们放在一起,您可以使用 createLayerComponent 工厂函数:
const SplitMap = createLayerComponent(createSplitMap, updateSplitMap);
export SplitMap
现在在你的地图中,你可以使用这个组件:
<MapContainer {...mapContainerProps}>
<SplitMap
rightTileLayerUrl={tiles[rightTileIndex]._url}
leftTileLayerUrl={tiles[leftTileIndex]._url}
/>
<TileLayer {...tileLayer1Props} />
<TileLayer {...tileLayer2Props} />
</MapContainer>
我还没有对此进行测试,但这是您用来为 splitmap 创建 react-leaflet 自定义组件的一般模式。
我正在尝试使用以下代码构建具有可选动态图层的地图 URL。
当我尝试通过状态更改图层 URL 时,它在所附的屏幕截图中显示了错误。
我已将所有图块存储在图块数组变量中,并且我正在尝试通过更改图块的索引来更改 URL。 我第一次 运行 项目工作正常,但是当我单击按钮并尝试更改图块的索引以呈现另一个地图图块时,它在屏幕截图中显示了错误。 我还应该提到,开发人员工具 chrome 的控制台选项卡中没有显示任何错误。 有没有办法解决这个问题?任何想法将不胜感激:)
错误截图:
const LeafletContainer = () => {
const [baseViewCoords, setBaseViewCoords] = useState([37.715, 44.8611]);
const [map, setMap] = useState();
const merged_20181223 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20180924 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20190323 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20190626 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20190919 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20191218 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20200625 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const merged_20200918 = L.tileLayer(
"dymmyurl",
{ tms: true, opacity: 1, attribution: "", minZoom: 1, maxZoom: 16 }
);
const tiles = [
merged_20190323,
merged_20191218,
merged_20181223,
merged_20180924,
merged_20190626,
merged_20190919,
merged_20200625,
merged_20200918,
];
const [leftTileIndex, setLeftTileIndex] = useState(0);
const [rightTileIndex, setTRightTileIndex] = useState(7);
const changeTileHandler = () => {
setLeftTileIndex((prev) => {
if (leftTileIndex === tiles.length - 1) {
return 0;
}
return prev + 1;
});
};
useEffect(() => {
if (map) {
L.control
.splitMap(
L.tileLayer(tiles[leftTileIndex]._url, {
tms: true,
opacity: 1,
attribution: "",
minZoom: 1,
maxZoom: 16,
}).addTo(map),
L.tileLayer(tiles[rightTileIndex]._url, {
tms: true,
opacity: 1,
attribution: "",
minZoom: 1,
maxZoom: 16,
})
.addTo(map)
.addTo(map)
)
.addTo(map);
}
}, [map, leftTileIndex, rightTileIndex]);
useEffect(() => {
if (map) {
map.setView(baseViewCoords);
}
}, [map, baseViewCoords]);
return (
<div style={{ position: "relative" }}>
<SearchArea
{...{
changeTileHandler,
}}
/>
<Options />
<Coordinate {...{ baseViewCoords }} />
<div id="map">
<MapContainer
style={{ height: "100%" }}
center={baseViewCoords}
zoom={9}
scrollWheelZoom={true}
whenCreated={(map) => {
setMap(map);
}}
>
<TileLayer
tms={true}
minZoom={1}
maxZoom={16}
opacity={1}
attribution=""
url={tiles[leftTileIndex]._url}
/>
<TileLayer
minZoom={1}
maxZoom={16}
opacity={1}
tms={true}
attribution=""
url={tiles[rightTileIndex]._url}
/>
</MapContainer>
</div>
</div>
);
};
export default LeafletContainer;
我不确定为什么这个错误会变得像 leafet 的 Events
class 一样深,但我确实看到了一个主要问题。在您的 useEffect 中,每次用户触发 setLeftTileIndex
或 setRightTileIndex
的状态更改时,都会将 L.Control.splitmap
的 新实例添加到地图中。虽然 react-leaflet <TileLayer />
组件被编写为处理 url 更改并自动更新底层 L.TileLayer
,但您的 L.control.splitmap
版本不是 - 它只是添加一个新的分割图。同时,旧的指的是不再存在的 L.TileLayer。您应该使用 leaflet-splitmap
的 setLeftLayers
和 setRightLayers
方法:
// initialize this outside the useEffect
const [splitMapAdded, setSplitMapAdded] = useState(false)
const splitMap = L.control.splitMap(
L.tileLayer("left_side_initial_url", { options }),
L.tileLayer("right_side_initial_url", { options })
)
useEffect(() => {
if (map && !splitMapAdded) {
splitmap.addTo(map);
setSplitMapAdded()true
}
}, [map]);
// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
if (map && splitMapAdded) {
splitMap.setLeftLayers(
[L.tileLayer(tiles[leftTileIndex]._url],
{ options }
)
}
}, [map, leftTileIndex])
// Set the splitmap left side when leftTileIndex changes
useEffect(() => {
if (map && splitMapAdded) {
splitMap.setRightLayers(
[L.tileLayer(tiles[rightTileIndex]._url],
{ options }
)
}
}, [map, rightTileIndex])
所以现在 splitmap 控件被添加到具有一些初始值的组件安装上。当这些状态变量发生变化时,您无需重新创建控件,而是使用其内部方法来更改 url。希望这会让您开始挖掘其中的一些错误。
另一种方式:
我还可以补充一点,这可以通过创建一个 react-leaflet v3 自定义组件来巧妙地管理。您可以使用创建函数创建组件:
const createSplitMap = (props, context) => {
const instance = L.control.splitmap(
L.tileLayer(props.leftTileLayerUrl, leftTileLayerOptions),
L.tileLayer(props.rightTileLayerUrl, rightTileLayerOptions)
)
return { instance, context }
}
那么您的更新函数可能如下所示:
const updateSplitMap = (instance, props, prevProps) => {
if (prevProps.leftTileLayerUrl !== props.leftTileLayerUrl){
instance.setLeftLayers(L.tileLayer(props.leftTileLayerUrl))
}
if (prevProps.rightTileLayerUrl !== props.rightTileLayerUrl){
instance.setLeftLayers(L.tileLayer(props.rightTileLayerUrl))
}
}
要将它们放在一起,您可以使用 createLayerComponent 工厂函数:
const SplitMap = createLayerComponent(createSplitMap, updateSplitMap);
export SplitMap
现在在你的地图中,你可以使用这个组件:
<MapContainer {...mapContainerProps}>
<SplitMap
rightTileLayerUrl={tiles[rightTileIndex]._url}
leftTileLayerUrl={tiles[leftTileIndex]._url}
/>
<TileLayer {...tileLayer1Props} />
<TileLayer {...tileLayer2Props} />
</MapContainer>
我还没有对此进行测试,但这是您用来为 splitmap 创建 react-leaflet 自定义组件的一般模式。