I have an application with a class as defined below:
class Point {
id: string;
position: Coordinate;
elevation?: number;
}
In my application I store an array of points.
const [points, setPoints] = useState<Point[]>([]);
In the code below, I have a useEffect hook that depends on the points
array; any time points
changes, the effect scans for points with undefined
elevation and queries asynchronously for the elevation. When individual queries return, a callback handleUpdatePoint
is called to update that particular Point with a new elevation value.
// Any time the Points array changes, checks each Point for undefined elevation
// and queries for it
// ISSUE: If more than one elevation query has yet to resolve, there will be an unintended
// state if one query resolves while one or more queries are pending
useEffect(() => {
points.forEach((p) => {
if (p.elevation === undefined) {
ElevationQuery(p.position.lat, p.position.lng, (elevation) => {
handleUpdatePoint(p.id, { elevation: elevation });
});
}
});
}, [handleUpdateSteerpoint, points]);`
The issue I'm facing is that if multiple queries are pending at the same time, the state of points
is overwritten as multiple results return, thus losing elevation results for some of my queries. How can I fix this?
EDIT #1: handleUpdatePoint
added for reference
const handleUpdatePoint = useCallback(
(id: string, newPointData: Partial<Point>) => {
// shallow copy the points array
const pointsClone = [...points];
// the point we are updating
const targetPoint = getPointById(id);
// preserve all other points as they are, but update the target point
// with the new data
const updatedPoints: Point[] = pointsClone.map((p: Point) => {
if (p.id !== id) {
return p;
} else {
// if the target point has a position, change its elevation to undefined
if (newPointData.position) {
newPointData.elevation = undefined;
}
const updatedPoint: Point = update(targetPoint, { $merge: newPointData })!;
return updatedPoint;
}
});
setPoints(updatedPoints);
},
[getPointById, points]
);
just using a callback-style of updating your state would solve the issue:
setPoints((previousPoints) =>
previousPoints.map((p: Point) => {
if (p.id !== id) {
return p;
} else {
// if the target point has a position, change its elevation to undefined
if (newPointData.position) {
newPointData.elevation = undefined;
}
const updatedPoint: Point = update(targetPoint, {
$merge: newPointData,
})!;
return updatedPoint;
}
})
);