Reputation: 1988
I am trying to avoid extra work by adding a property to an object in an array of objects onMouseEnter
when I mouse over the ApartmentCard
displaying info about a given apartment.
There is also a map beside the list of ApartmentCards
. The map has a marker for each apartment. My goal is to make React retrigger useEffect
so that all the old markers are removed, and new ones are placed, including one that will be highlighted to indicate it is the associated marker.
(As an aside, it's possible selecting the appropriate marker and updating its color would be more efficient, but that's for later)
So currently in my ApartmentCard.tsx
I have:
interface ApartmentCardProps {
apartment: IHousing;
addr: string;
gyms: IAssociation[];
apUrl: string;
}
const ApartmentCard: React.FC<ApartmentCardProps> = ({ apartment, addr, gyms, apUrl }) => {
return (
<div
onMouseEnter={() => {
console.log("mouse enter", "23rm");
apartment.isHighlighted = true;
}}
onMouseLeave={() => {
apartment.isHighlighted = false;
}}
>Apartment address, other data</div>
};
export default ApartmentCard;
On the IHousing
interface there is some predictable stuff that I left out, but also:
export interface IHousing {
type: "apartment" | "house";
// snip
isHighlighted?: boolean;
}
ApartmentCard
is a child component of MapPage
. Map
is also a child of MapPage
. Both receive the same data about apartments, so when isHighlighted
is updated in ApartmentCard
, the objects used to populate Map
immediately update their property and values with isHighlighted = true
or false
.
Here's some code from Map.tsx
:
interface MapboxProps {
center: [number, number];
qualifiedFromCurrentPage: IHousing[];
// zoom: number;
}
const Map: React.FC<MapboxProps> = ({ center, qualifiedFromCurrentPage }) => {
const [markers, setMarkers] = useState<mapboxgl.Marker[]>([]);
const { isOpen, toggleIsOpen } = useContext(SidebarStateContext) as ISidebarContext;
// initialization of map has been removed to save space in the post
// plot qualified gyms and apartments
useEffect(() => {
if (map === null) return;
let allMarkers: mapboxgl.Marker[] = [];
if (qualifiedFromCurrentPage.length !== 0 && map.current) {
const { apartmentMarkers, gymMarkers } = unpackMarkers(qualifiedFromCurrentPage);
allMarkers = [apartmentMarkers, gymMarkers].flat();
addNewMarkers(allMarkers, markers, setMarkers, map.current);
}
return () => {
// remove all old markers
for (const marker of allMarkers) {
marker.remove();
}
};
}, [map, qualifiedFromCurrentPage]);
function unpackMarkers(pageMarkers: IHousing[]): { apartmentMarkers: mapboxgl.Marker[]; gymMarkers: mapboxgl.Marker[] } {
// snip
}
function addNewMarkers(newMarkers: mapboxgl.Marker[], oldMarkers: mapboxgl.Marker[], markerUpdater: Function, map: mapboxgl.Map) {
// snip
}
return (
<div id="mapContainerOuter" className={`${decideWidth(isOpen, isOnMobile)} w-full mapHeight mr-2`}>
<div id="mapContainer" ref={mapContainer}></div>
<button
onClick={() => {
console.log(qualifiedFromCurrentPage, "142");
}}
>
Test
</button>
</div>
);
};
export default Map;
What I was really hoping for was that the useEffect
hook with qualifiedFromCurrentPage
in its dependency array would run again when the property changes on one of its objects. But it ignores it.
One solution that for sure would work is if (1) I assigned a number to each apartment in the list for a given page then (2) let the ApartmentCard
set the active value, probably stored in the parent MapPage
, which would then (3) be passed down into the Map
component and used to highlight the associated marker.
It could and would work. But, given that I'm supposed to be a professional React user, and I'm not getting an expected behavior, I would really like to know what's going on. Shouldn't adding a property to an object in an array cause React to re-run that useEffect hook? That's what I expected.
Thanks all
edit: I am feeding in a shallow copy to both apartmentCard
and Map
from this function in MapPage
, but I thought it would be fine since the values of a shallow copy point to the same objects as the input array.
function getCurrentPageResults(qualified: IHousing[], page: number): IHousing[] {
if (qualified.length < 10) {
return qualified;
}
const startOfPage = page * 10 - 10;
const endOfPage = page * 10 - 1;
return qualified.slice(startOfPage, endOfPage);
}
Upvotes: 0
Views: 782
Reputation: 321
I assume you're a former Vue user? React doesnt watch the change of object or variables, in fact, you have to notify React that some value changes by setState
or state hook, and then React will handle the virtual dom result diff and rerender the real dom only in need.
React's one-way data flow (also called one-way binding, opposite to two-way binding) keeps everything modular and fast. You can check thinking-in-react for more detail.
Upvotes: 3