'Trouble setting state with onClick function only with ImageMapper component (react-img-mapper)

I'm trying to create an interactive image map using the ImageMapper component from react-img-mapper (based on react-image-mapper) in my ReactJS app. I want to be able to click an "area" on ImageMapper map, and have it update clickedAreas state according to the function clickArea.

In the app, I render a list with checkmarks, and the checkmarks become green when the corresponding list item is included in clickedAreas state.

I call clickArea via onClick on list items themselves and it works as expected, but when I call it via the ImageMapper onClick prop it doesn't, and I'm having trouble figuring out why.




Here's a screen recording showing the differences in action.

NOTE: the ImageMapper onClick seems to be using the initially set state (useState([])), because when I give it an array with an element initially, it just keeps using that initial state with that element.

Here's a screenshot regarding the onClick prop from react-img-mapper documentation Here's a screenshot regarding the onClick prop from react-img-mapper documentation





Here's a screenshot of my code: Here's a screenshot of my code.





Here's my code also:

import React, { useState } from "react"
import ImageMapper from 'react-img-mapper';


function ImageMapQQ(props) {

  const URL = "../images/qqmap.png"
  const MAP = {
    name: "qq-map",
    areas: [
      { id: "NW1/4NW1/4", title: "NW1/4NW1/4", name: "NW1/4NW1/4", shape: "rect", coords: [0,0,50,50] },
      { id: "NE1/4NW1/4", title: "NE1/4NW1/4", name: "NE1/4NW1/4", shape: "rect", coords: [50,0,100,50] },
      { id: "NW1/4NE1/4", title: "NW1/4NE1/4", name: "NW1/4NE1/4", shape: "rect", coords: [100,0,150,50] },
      { id: "NE1/4NE1/4", title: "NE1/4NE1/4", name: "NE1/4NE1/4", shape: "rect", coords: [150,0,200,50] },

      { id: "SW1/4NW1/4", title: "SW1/4NW1/4", name: "SW1/4NW1/4", shape: "rect", coords: [0,50,50,100] },
      { id: "SE1/4NW1/4", title: "SE1/4NW1/4", name: "SE1/4NW1/4", shape: "rect", coords: [50,50,100,100] },
      { id: "SW1/4NE1/4", title: "SW1/4NE1/4", name: "SW1/4NE1/4", shape: "rect", coords: [100,50,150,100] },
      { id: "SE1/4NE1/4", title: "SE1/4NE1/4", name: "SE1/4NE1/4", shape: "rect", coords: [150,50,200,100] },

      { id: "NW1/4SW1/4", title: "NW1/4SW1/4", name: "NW1/4SW1/4", shape: "rect", coords: [0,100,50,150] },
      { id: "NE1/4SW1/4", title: "NE1/4SW1/4", name: "NE1/4SW1/4", shape: "rect", coords: [50,100,100,150] },
      { id: "NW1/4SE1/4", title: "NW1/4SE1/4", name: "NW1/4SE1/4", shape: "rect", coords: [100,100,150,150] },
      { id: "NE1/4SE1/4", title: "NE1/4SE1/4", name: "NE1/4SE1/4", shape: "rect", coords: [150,100,200,150] },

      { id: "SW1/4SW1/4", title: "SW1/4SW1/4", name: "SW1/4SW1/4", shape: "rect", coords: [0,150,50,200] },
      { id: "SE1/4SW1/4", title: "SE1/4SW1/4", name: "SE1/4SW1/4", shape: "rect", coords: [50,150,100,200] },
      { id: "SW1/4SE1/4", title: "SW1/4SE1/4", name: "SW1/4SE1/4", shape: "rect", coords: [100,150,150,200] },
      { id: "SE1/4SE1/4", title: "SE1/4SE1/4", name: "SE1/4SE1/4", shape: "rect", coords: [150,150,200,200] },
    ]
  }

  const areas = MAP.areas
  const [clickedAreas, setClickedAreas] = useState([])

  function clickArea(id) {
    console.log("clickedAreas: ", clickedAreas)

    if (clickedAreas.includes(id)) {
      console.log("includes")
      setClickedAreas(() => clickedAreas.filter(el => el != id))
    }
    else {
      console.log("doesn't include")
      setClickedAreas(() => [...clickedAreas, id])
    }
  }

  return (
    <>
      <ImageMapper src={URL} map={MAP}
        stayMultiHighlighted={true}
        toggleHighlighted={true}
        fillColor={"rgba(150, 213, 255, 0.6)"}

        onClick={(area) => clickArea(area.id)} // THIS ONCLICK ISN'T WORKING CORRECTLY
      />

      <div className="bold">AREAS</div>
      {areas.map(area =>
          <>
          <div className="d-flex pointer" onClick={() => clickArea(area.id)}> {/* THIS ONCLICK IS WORKING CORRECTLY */}
            <i className={`fas fa-check-circle ${clickedAreas.includes(area.id) ? "text-green" : "text-black opacity-10"}`}></i>
            <div key={area.id}>{area.id}</div>
          </div>
          </>
        )
      }
    </>
  )
}

export default ImageMapQQ


Solution 1:[1]

I still don't know why it doesn't work as originally written, and would still like more insight on that if anyone's got it.

Ultimately, the ImageMapper onClick just keeps using the initial state (empty array) whenever a click occurs -- I can tell this because I have it console.log the current clickedAreas state at the start of the clickArea function that's called when the image map is clicked.

It seems to update the state (because the corresponding checkmark turns green), but it seems to always start from the initial state every time the ImageMapper onClick function is called (console.log shows empty array every time).

That all being said, I HAVE found a workaround to make it work as desired.

Instead of the ImageMapper onClick calling the clickArea function that later sets state, I changed it to setting a different state immediately (areaId), triggering a useEffect that then calls the clickArea function that later sets the state I actually wanted to be working with, lol. Hacky, but works.

(Added the nanoid in there to make it unique upon every click, so that the setAreaId function within ImageMapper onClick would always run -- if I just had area.id, it wouldn't run if that area.id was already the areaId state.... aka, I couldn't get the clickArea function to run to remove it from the clickedAreas state)

The small red circles 1-3 are the new parts of the code: The small red circles 1-3 are the new parts of the code

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 amota