Search code examples
javascriptreactjsreact-hooksreact-state-management

React handleClose() function logging to console but not closing child element


So I'm trying to make a small online store for a personal business. I'm not very good with React, but I feel like I've made some headway and might be able to finish something at least functional, if not very well composed or pretty.

The issue I'm having is with a popup window not closing when the button I've placed is pressed. The button calls a function defined in the function it is called in. I have a Products.js:

import React from 'react';
import Card from './Card';
import data from './data';
import '../styles/card.css';

const Products = () => {
return (
 <div className="cards-list"/>
{data.map(item => (
 <Card key={item.id} item={item} />
))}
 </div\>
);
};

export default Products;

which contains a series of cards:

import React, { useState, useEffect }  from 'react';
import CardPopup from './CardPopup';
import '../styles/card.css';

const Card = ({ item }) => {
  const [showPopup, setShowPopup] = React.useState(false);
  const [cardImage, setCardImage] = useState(null);

  useEffect(() => {
    import(`../assets/${item.image}`).then((image) => {
      setCardImage(image.default);
    });
  }, [item.image]);

  const handleClick = () => {
    setShowPopup(true);
  };

  const handleClose = () => {
    console.log("handleClose called");
    setShowPopup(false);
  };

  return (
    <div className="card" onClick={handleClick}>
      <img src={cardImage} alt={item.title} className="card-image" />
      <div className="card-view">
        <div className="card-view-text">View</div>
      </div>
      <div className="card-product">{item.title}</div>
      {showPopup && (
        <CardPopup
          handleClose={handleClose}
          title={item.title}
          price={item.price}
          description={item.description}
          image={cardImage}
        />
      )}
    </div>
  );
};

export default Card;

When clicked the card correctly opens this CardPopup.js

import React from 'react';
import { createPortal } from 'react-dom';
import '../styles/CardPopup.css';

const CardPopup = ({ handleClose, title, price, description, image }) => {
  const popupRoot = document.getElementById('popup-root');

  return createPortal(
    <>
      <div className="card-popup-overlay"/>
      <div className="card-popup">
        <img src={image} alt={title} className="card-popup-image" />
        <div className="card-popup-content">
          <div className="card-popup-title">{title}</div>
          <div className="card-popup-price">{price}</div>
          <button className="card-popup-close" onClick={handleClose}>
            X
          </button>
          <div className="card-popup-description">{description}</div>
        </div>
      </div>
    </>,
    popupRoot
  );
};

export default CardPopup;

Inside index.js I've appended a dom element for the popup to sit in,

const popupRoot = document.createElement('div');
popupRoot.id = 'popup-root';
document.body.appendChild(popupRoot);

I've tried logging handleClose() to the console, and that works correctly, and I tried using functions, const, and classes for Card and CardPopup, but that hasn't had any impact and I couldn't get it working with classes. I'm also not getting any feedback from the console error-wise which is troubling.

I think there's some basic concept I'm misunderstanding here that is causing this issue.

I'm not asking for someone to fix the code, but if there's any concepts or react elements I'm blatantly misusing that are causing this error any advice would be greatly appreciated.


Solution

  • If you look at the rendered React tree, your popup is nested within the card.

    If you close your popup, your onClick handler of the close button is called first, and afterwards it goes to the parents and calls the onClick handler of the card, opening the popup again.

    There are 2 ways to solve this:

    Inside the parent, move the popup outside the card:

     return <>
        <div className="card" onClick={handleClick}>
          ...
        </div>
          {showPopup && (
            <CardPopup
              handleClose={handleClose}
              title={item.title}
              price={item.price}
              description={item.description}
              image={cardImage}
            />
          )}
      </>;
    

    Or stop event propagation inside the onClick handler for the closing action

      const handleClose = (e) => {
        e.stopPropagation()
        console.log("handleClose called");
        setShowPopup(false);
      };
    

    See also: https://react.dev/reference/react-dom/createPortal#caveats