Search code examples
javascriptreactjskeyboard-eventssemantic-ui-reactremoveeventlistener

removeEventListener doesn't work on Modal close


I am building a React app which uses Semantic UI React. I have some thumbnails which open to the Modal component to show the full-sized image in the modal. I also have buttons for navigating through the next and previous. Along with the button clicks, I would like to use arrow keys to navigate. I can add event listener to the window when modal opens. But when I close the modal and open another, a duplicate event listener added to the window. This means that I need to remove the event listener when the modal is closed. However, the onClose and onUnmount props of the Semantic UI React Modal component do not apply the removal. How can I get it to apply? Anything other than removeEventListener can run in onClose or onUnmount, but removeEventListener doesn't run.

Here's the whole Attachment component:

import React, { Fragment, useState } from 'react'
import { Button, Card, Modal, Image } from 'semantic-ui-react'
import parse from 'html-react-parser'
import { IMedia } from '../../app/models/media'

interface IProps {
    attachedMedia: IMedia, 
    gallery: IMedia[], 
    featured: boolean
}

const Attachment: React.FC<IProps> = ({ attachedMedia, gallery, featured }) => {
    const [open, setOpen] = useState(false)
    const [index, setIndex] = useState(gallery.indexOf(attachedMedia))
    const [disabledNext, setDisabledNext] = useState(false)
    const [disabledPrev, setDisabledPrev] = useState(false)

    const handlePrev = () => {
        if (index > 0) {
            setIndex(index - 1)
            setDisabledNext(false)
            if (index === 1) setDisabledPrev(true)
        } 
    }

    const handleNext = () => {
        if (index < gallery.length - 1) {
            setIndex(index + 1)
            setDisabledPrev(false)
            if (index === gallery.length - 2) setDisabledNext(true)
        }
    }

    const handleClose = () => {
        setIndex(gallery.indexOf(attachedMedia))
        setDisabledPrev(false)
        setDisabledNext(false)
        setOpen(false)
        window.removeEventListener('keydown', handleKeyPress)
    }

    const handleMount = () => {
        if (index === 0) setDisabledPrev(true)
        if (index === gallery.length - 1) setDisabledNext(true)
        window.addEventListener('keydown', handleKeyPress)
    }

    const handleKeyPress = (e: KeyboardEvent) => {
        console.log(e)
    }

    return (
        <Fragment>
            {featured ? 
                (attachedMedia.media_details.sizes.medium?.source_url !== undefined) ? 
                    <Image src={attachedMedia.media_details.sizes.medium.source_url} ui={false} onClick={() => {setOpen(true)}} />
                :
                    <Image src={attachedMedia.media_details.sizes.full.source_url} ui={false} onClick={() => {setOpen(true)}} />
            :
                <Card className="attachment"><Image src={attachedMedia.media_details.sizes.thumbnail.source_url} onClick={() => setOpen(true)} /></Card>
            }
            
            <Modal open={open} centered={false} onClose={handleClose} onMount={handleMount}>
                <Modal.Header>
                    {parse(gallery[index].title.rendered)}
                </Modal.Header>
                <Modal.Content image scrolling>
                    <Image src={gallery[index].media_details.sizes.full.source_url} wrapped fluid />
                    <Modal.Description>
                        {parse(gallery[index].description.rendered)}
                    </Modal.Description>
                </Modal.Content>
                <Modal.Actions>
                    <Button disabled={disabledPrev} onClick={handlePrev}>Previous image</Button>
                    <Button disabled={disabledNext} onClick={handleNext}>Next image</Button>
                </Modal.Actions>
            </Modal>
        </Fragment>
    )
}

export default Attachment

Here, handleClose function contains the removeEventListener, but the removal is not applied to window despite rest of the code in handleClose can run effectively.

The only trial I was able to run removeEventListener was to use it in the handleKeyPress function. But that's not what I want, it removes the listener after one key press. I want to be able to press keys as long as the modal is open.

How do I get removeEventListener into effect when the modal is closed?


Solution

  • I have derived a solution from this question. Using a useEffect and removing the event listener in the cleanup function provided me with the desired results. Here is the code block:

    useEffect(() => {
            if (open) window.addEventListener('keydown', handleKeyPress)
    
            return () => {
                window.removeEventListener('keydown', handleKeyPress)
            }
        }, [open])