1

I have a list of dynamically generated items with information about real estate objects. Every item has a button. When clicked a contact form pops up. However when a button is clicked the event fires on all of the items in the list instead of only on that single item? I solved it with javascript event delegation but that's not the react-way to handle this. What is the best way to do this in react? I'm using React v18, React-Hooks and Redux-Toolkit. Data is fetched from a mongoDB database using Express. Thanks you!

    // Items list with pagination and map component
    // Map component also contains the button!
    const Content = () => {

    const [show, setShow] = useState(false)
    const [currentNumber, setCurrentNumber] = useState(1)
    const [newList, setNewList] = useState([])
    const [errorMessage, setErrorMessage] = useState(false)

    const realestates = useSelector(state => state.realestate)
    const { loading, realestate, error } = realestates

    const dispatch = useDispatch()

    useEffect(() => {
        dispatch(fetchData())
        if (realestate) {
            setShow(true)
        }
        if (error) {
            setErrorMessage(true)
        }
    }, [dispatch, error, realestate])


    const pageNumbers = []
    const resultsPerPage = 4
    const pages = Math.ceil(realestate.length / resultsPerPage)

    for (let i = 1; i <= pages; i++) {
        pageNumbers.push(i)
    }

    const pagination = (number) => {
        setCurrentNumber(number)
    }

    const slicedList = useCallback(() => {
        const data2 = realestate.slice(((currentNumber - 1) * resultsPerPage), (currentNumber * resultsPerPage))
        setNewList(data2)
    }, [currentNumber, realestate])


    useEffect(() => {
        slicedList()
    }, [slicedList])



    return (
        <div className="content2">
            {errorMessage ? <div>{error}</div> : ""}

            //List item

            {show ? newList.map(item => {

                const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;

                return (
                    <div className="content2_property" key={_id}>
                        <div className="content2_castleImageBox"><img src={img} alt="" className="content2_castleImage" /></div>
                        <div className="content2_info">
                            <div className="title"><h5>{name}</h5></div>
                            <div className="location">
                                <div><span>Region:</span> {region}</div>
                                <div><span>Departement:</span> {departement}</div>
                                <div><span>City:</span> {city}</div>
                            </div>
                            <div className="icons">
                                <div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
                                <div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
                                <div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
                                <div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
                            </div>
                            <div className="price"><span>Price:</span> {item.price === 'Not for Sale' ? price : `$${price},-`}</div>
                        </div>

                        <Map region={region} map={map} />

                    </div>
                )
            }) : <Loader />}

            // Pagination buttons

            <div className="btns">
                {pageNumbers.map((number, index) => {
                    return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
                        <button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
                })}
            </div>
        </div>
    )
}

export default Content

Map component with button

    const Map = ({ region, map }) => {

    const [showRegionName, setShowRegionName] = useState(false)

    const handleMouseOver = () => {
        setShowRegionName((prev) => !prev);
    }

    const makeEnquiry = () => {
        //show contact form
    }

    return (
        <div className="mapEnquiryBox">
            <div className="map" onMouseEnter={handleMouseOver} onMouseLeave={handleMouseOver}>
                <img src={map} alt="map" />
                <div className={showRegionName ? "regionName" : "regionName hide"} >{region}</div>
            </div>
            <button className="enquiry" onClick={makeEnquiry}>Make enquiry</button>
        </div>
    )
}

export default Map
Noud
  • 145
  • 9

2 Answers2

0

I think, the issue is with the key in the component. This is how React differentiated between the components.

This is how I recently made my pagination:

Parent

{pageNumbersArray.map(pageNumber => (
            <PaginationButton
              key={pageNumber}
              active={pageNumber === currentPage}
              disabled={false}
              onClick={() => handlePageChange(pageNumber)}
              title={pageNumber}
            />
          ))}

Pagination Button

export default function PaginationButton({title, onClick, active, disabled}) {
  return (
    <button onClick={disabled ? null : onClick}>
      <span>
        {title}
      </span>
    </button>
  );
}

This might come in handy for you.

Shameel Uddin
  • 511
  • 2
  • 10
  • Not exactly what I'm looking for. The issue is not with the pagination buttons but the buttons in the list items that fire all at once when clicked. Its a state management thing. Could you remove your answer please so other people will still be invited to react on my question. Thank you. – Noud Aug 18 '22 at 06:09
0

Problem solved. I had to split the items list into separate components. One for the list container , one for the list items and one for every single list item. In the single item component I managed the state of that particular item, so when clicking a button the event only fires on that particular item. Like so:

The list container

<div className="content2">
    {errorMessage ? <div>{error}</div> : ""}

   <ListItems newList={newList} show={show}/>

    <div className="btns">
        {pageNumbers.map((number, index) => {
            return (number === currentNumber) ? <button className="paginationBtn active" onClick={() => pagination(number)} key={index} >{number}</button> :
                <button className="paginationBtn" onClick={() => pagination(number)} key={index} >{number}</button>
        })}
    </div>
</div>

The list items

    const ListItems = ({ newList, show }) => {
    return ( 
        <>
        {show ? newList.map(item => {

                const { _id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price } = item;

                return (
                    <SingleProperty id={_id} area={area} bathrooms={bathrooms} bedrooms={bedrooms} city={city} 
                    departement={departement} region={region} img={img} livingspace={livingspace} map={map} name={name} price={price}/>
                )
            }) : < Loader />}
         </>   
    )
}

An the single item component

    const SingleProperty = ({ id, area, bathrooms, bedrooms, city, departement, region, img, livingspace, map, name, price}) => {

   const [ showForm, setShowForm ] = useState(false)

    return (
        <div className={!showForm ? "content2_property" : "content2_property enlarge"} key={id}>
            <div className="content2_property_castleImageBox"><img src={img} alt="castle" className="content2_property_castleImage" /></div>
            <div className="content2_property_info">
                <div className="title"><h5>{name}</h5></div>
                <div className="location">
                    <div><span>Region:</span> {region}</div>
                    <div><span>Departement:</span> {departement}</div>
                    <div><span>City:</span> {city}</div>
                </div>
                <div className="icons">
                    <div className="icon">{bedrooms}<img src={bedroomsIcon} alt="" /></div>
                    <div className="icon">{bathrooms} <img src={bathroomsIcon} alt="" /></div>
                    <div className="icon">{livingspace}<img src={livingspaceIcon} alt="" /></div>
                    <div className="icon">{area}ha<img src={areaIcon} alt="" /></div>
                </div>
                <div className="price"><span>Price:</span> {price === 'Not for Sale' ? price : `$${price},-`}</div>
            </div>

            <Map region={region} map={map} setShowForm={setShowForm}/>

        </div>
    )
}
Noud
  • 145
  • 9