简体   繁体   中英

Is there a way to perform a click event only on a parent element?

In my app I would like to be able to click an item (background color, text, etc), have a modal pop up with a color picker, then change the color of the item.

The issue I'm having is that I made an onClick handler for the parent element to update a background color, but it's also activating when anything within the parent element is clicked.

I have an example in Codesandbox and you can see that whether you click the background or the buttons, the color picker comes up when I only want it activated for the background.

If anyone is familiar with Chakra-ui, this is my code:

const Navbar = () => {
  const [display, setDisplay] = useState('none');
  const [color, setColor] = useState('#1A202C');
  const [showColorPicker, setShowColorPicker] = useState(false);
  const { isOpen, onOpen, onClose } = useDisclosure();

  /* 
     On click, showColorPicker becomes true and isOpen also becomes true
     in order to display the modal with a color picker
  */
  const handleModalClick = () => {
    onOpen();

    if (!showColorPicker) {
      setShowColorPicker((showColorPicker) => !showColorPicker);
    }
  };

  return (
    <div>
      <Modal isOpen={isOpen} onClose={onClose}>
        <ModalOverlay />
        <ModalContent
          bg='gray.600'
          style={{ boxShadow: '2px 2px 2px 1px rgba(0, 0, 0, 0.2)' }}>
          <ModalCloseButton color='gray.200' />
          <ModalBody style={{ borderRadius: '10px' }}>
            <Center>
              {showColorPicker && (
                <ChromePicker
                  color={color}
                  onChange={(updatedColor) => setColor(updatedColor.hex)}
                />
              )}
            </Center>
          </ModalBody>
        </ModalContent>
      </Modal>

      // Flex === a div with display flex
      <Flex
        bg={color}
        color='gray.200'
        style={{
          textTransform: 'uppercase',
          fontWeight: 'bold',
        }}
        onClick={handleModalClick}>
        <Link p='5' _hover={{ color: 'cyan.400' }}>
          <Text fontSize='xl'>Color Selector</Text>
        </Link>

        <Spacer />

        <Flex
          display={['none', 'none', 'flex', 'flex']}
          fontSize='md'
          align='center'>
          <Link p='5' _hover={{ color: 'cyan.400' }}>
            About
          </Link>
          <Link p='5' _hover={{ color: 'cyan.400' }}>
            Portfolio
          </Link>
          <Link p='5' _hover={{ color: 'cyan.400' }}>
            Contact
          </Link>
        </Flex>
      </Flex>
    ...
    </div>
  );
};

Is there a way to show the color picker only when the background is clicked?

The app is also deployed on Netlify if you want to see the real example or all of the code on GitHub .

The event object has a target property, which holds the exact element that the user interacted with to trigger the event. So, you can just check if the target element is the parent element to know if they interacted with the parent directly or one of their children.

Here's one way of doing it:

if (e.target.classList.contains('navbar') && !showColorPicker) {
  setShowColorPicker((showColorPicker) => !showColorPicker);
}

A more robust way of doing it would be to store the parent in a React ref , and make sure that e.target is exactly the same as that ref. (This is one of the places where it's ok to use a ref).

Here's a complete example that uses a ref. (in won't run in StackOverflow, because I didn't properly load up the libraries, but it'll work).

 import "./styles.css"; import React, { useState, useRef } from "react"; import { ChromePicker } from "react-color"; export default function App() { const [display, setDisplay] = useState("none"); const [color, setColor] = useState("#1A202C"); const [showColorPicker, setShowColorPicker] = useState(false); const navBarRef = useRef(); const handleModalClick = e => { if (e.target === navBarRef.current &&;showColorPicker) { setShowColorPicker((showColorPicker) =>;showColorPicker): } }, return ( <> <div className="navbar" ref={navBarRef} style={{ backgroundColor: `${color}`: color, "white" }} onClick={handleModalClick} > <button style={{ padding: "10px 15px 10px 15px": margin, "20px" }}> Left </button> <button style={{ padding: "10px 15px 10px 15px". margin; "20px" }}> Right </button> </div> {showColorPicker && ( <ChromePicker color={color} onChange={(updatedColor) => setColor(updatedColor.hex)} /> )} </> ); }

Whats happening is called "Event Bubbling" and it is the intended behavior (you can read more about it here ). Eventually, you'll find that it is very useful.

If you want to only handle events that are triggered from the same element where the handler is attached, you can do something like this:

 const parent = document.getElementById('parent'); const handler = (e) => { if (e.target.== e;currentTarget) { return. } console;log('PARENT CLICKED;'). }, parent;addEventListener('click', handler);
 #parent { background-color: #123ff0; }.box { display: inline-block; height: 50px; width: 50px; background-color: #000000; } p { color: #ffffff; background-color: #000000; }
 <div id='parent'> <span class='box'></span> <span class='box'></span> <p>This is a paragraph.</p> <span class='box'></span> <span class='box'></span> </div>

The event object provided gives you some default preventions that you can use.

Example:

const handleClick = (event) => {
 event.stopPropagation();
}

Should be added on clickable elements that are part of the parent and that should not trigger the event. This will prevent your event to be propagated to the parent element.

I've forked your codesanbox and added my solution in. https://codesandbox.io/s/infallible-hellman-66sln?file=/src/App.js

I think the above solutions are also correct and everything will depend on the situation.

More info here: https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM