Search code examples
javascriptreactjsreact-hooksbootstrap-modalreact-bootstrap

How to prevent duplicate Modal Popup & not scrolling to last element in ReactJS


I'm creating a Project where I fetch data from server & render 10 data in 10 different Bootstrap card using map(). Each card have a button to popup a Modal. Beside I set a Link to button that will show me route of that data.

Information

  • Using react-bootstrap for modal with hooks
  • Using react-router-dom for show route
  • Using useState hooks to set data from fetched data to modal.
  • All of code are in one component.
  • info_prop & info are different but it works with same data.
  • I have import all needed things & don't have any warning or error
  • About Data Handling
    • First I get data with useGetDataQuery() using chapterId.
    • Map data & destructure info data & set to state with setInfo
    • Send to Modal props with info
    • Handle of sending to setInfo with handleModal. I also try without this function. That time I do it on onClick.

Problem

  • Route Problem with map()
    • I use a Button to show Modal & Wrapped the button with Link. Every Link has a uniqe ID like 1:1, 1:2, 1:3...1:10. If I click on 1:1 button it show me the content of 1:1. But when I close the modal the route auto change 1:1 to 1:3...1:10.
    • I can realize that there it's render a duplicate Modal behind main Modal. I can see only 3-4 lines of back modal.
  • Modal Problem
    • When I show 1-5 data with map & click button of popup modal, modal show normally with blur background.
    • When I dhow 1-10 data with map & click button of popup modal, background become pure black.(I think it's not normal)

Dependencies

  • react-bootstrap v5
  • bootstrap v5
  • react-router-dom v6

Code

  • Code of component. Modal in same component but in another function.
function TafsirModal(props) {
    return (
        <Modal
            {...props}
            size="md"
            aria-labelledby="contained-modal-title-vcenter"
            centered
        >
            <Modal.Header closeButton>
                <Modal.Title id="contained-modal-title-vcenter">
                    Heading
                </Modal.Title>
            </Modal.Header>
            <Modal.Body>
                <div>
                    {props.info}
                </div>
            </Modal.Body>
            <Modal.Footer>
                <Button onClick={props.onHide}>Close</Button>
            </Modal.Footer>
        </Modal>
    )
}

const InfoCom = () => {
    const { chapterId } = useParams()

    let [modalShow, setModalShow] = useState(false);
    let [info, setInfo] = useState('')
    const { data } = useGetDataQuery(chapterId)


    const handleModal = (info_prop) => {
        setInfo(info_prop)
        setModalShow(true)
    }

    return (
        <>
            <div className="container">
                <div className="row">
                    <div className="col-md-12">
                        {data.map(res => (
                            <Link to={`/li/${chapterId}/${res.res_key}`} key={res.res_key} >
                                <div key={res.id} className='card my-2'>
                                    <div className="card-body">
                                        <div className="d-flex flex-row">
                                            <Button onClick={() => handleModal(res.info[0].text)}>
                                                Get Info
                                            </Button>

                                            <TafsirModal
                                                show={modalShow}
                                                onHide={() => setModalShow(false)}
                                                info={info}
                                            />
                                        </div>
                                    </div>
                                </div>
                            </Link>
                        ))}
                    </div>
                </div>
            </div>
        </>
    )
}
export default InfoCom


Solution

  • The issue looks like it's the way you're handling showing your modal with show={modalShow} Whenever you click on a button to show your modal all of them are showing because they all have show modal true from modalShow. instead of using showModal state try this:

    let [activeModal, setActiveModal] = useState('');
    
    function handleModal(info_prop) {
      setInfo(info_prop)
      // if you have an id or something use that instead of text in set activeModal
      setActiveModal(info_prop)
    }
    // in map
    <Button onClick=(() => handleModal(res.info[0].text)></Button>
    
    <TafsirModal
      show={activeModal === res.info[0].text ? true : false}
      onHide=(setActiveModal(''))
    />
    

    Another solution that I more commonly use is to render the modal outside of the map towards the top level of the the component and this way you could use the same logic you currently have and this way we are rendering only one modal which would be better in my opinion and easier to read. We can do this my simply moving <TafsirModal /> up a few levels in the tree

    <>
                <div className="container">
                    <div className="row">
                        <div className="col-md-12">
                            {data.map(res => (
                                <Link to={`/li/${chapterId}/${res.res_key}`} key={res.res_key} >
                                    <div key={res.id} className='card my-2'>
                                        <div className="card-body">
                                            <div className="d-flex flex-row">
                                                <Button onClick={() => handleModal(res.info[0].text)}>
                                                    Get Info
                                                </Button>
                                            </div>
                                        </div>
                                    </div>
                                </Link>
                            ))}
                        </div>
                    </div>
                   {activeModal && <TafsirModal
                      show={modalShow}
                      onHide={() => setModalShow(false)}
                      info={info}
                     />
                    }
                </div>
            </>