Home > Enterprise >  How to show Modal after clicking a button in another component React?
How to show Modal after clicking a button in another component React?

Time:06-01

I have this two components:

import React, { useState, useEffect } from "react";
import axios from "axios";
import Button from "../../../components/Button";
import NewAreaModal from "./NewAreaModal";

function GestioneAree() {

    const [aree, setAree] = useState([]);
    const [show, setShow] = useState(false);

    useEffect(() => {
        axios.get("http://localhost:8080/aree/all").then((res) => {
            setAree(res.data);
            console.log(res.data);
        });
    }, []);

    const showModal = () => {
        setShow(true);
    }

    return (
        <div className="bg-white rounded-lg">
            <div className="px-4 py-5 sm:px-6 rounded">
                <div className="-ml-4 -mt-2 flex items-center justify-between flex-wrap sm:flex-nowrap">
                    <div className="ml-4 mt-2">
                        <h3 className="text-lg leading-6 font-medium text-gray-900">Aree nel Sistema</h3>
                    </div>
                    <div className="ml-4 mt-2 flex-shrink-0">
                        <Button type="button" decoration="primary" text="Crea Nuova Area" onClick={showModal}/>
                    </div>
                </div>
            </div>
            <div className="flex flex-col">
                <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
                    <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
                        <div className="shadow overflow-hidden border-t border-gray-200 sm:rounded-lg">
                            <table className="min-w-full divide-y divide-gray-200">
                                <thead className="bg-gray-50">
                                    <tr>
                                        <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                            Toponimo
                                        </th>
                                        <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                            Territorio
                                        </th>
                                        <th scope="col" className="relative px-6 py-3">
                                            <span className="sr-only">Edit</span>
                                        </th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {aree.map((area) => (
                                        <tr key={area.idArea} className="bg-white border-b">
                                            <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{area.toponimo}</td>
                                            <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{area.territorio.nome}</td>
                                            <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
                                                <button type="button" className="text-green-600 hover:text-green-900" >
                                                    Modifica
                                                </button>
                                            </td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                            <NewAreaModal show={show} />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default GestioneAree;

and

import React, { useState, useRef, Fragment } from "react";
import { Dialog, Transition } from "@headlessui/react";
import Button from "../../../components/Button";

function NewAreaModal() {
    const [open, setOpen] = useState(true);

    const cancelButtonRef = useRef(null);

    return (
        <Transition.Root show={open}>
            <Dialog as="div" className="relative z-10" initialFocus={cancelButtonRef} onClose={setOpen}>
                <Transition.Child as={Fragment} enter="ease-out duration-300" enterFrom="opacity-0" enterTo="opacity-100" leave="ease-in duration-200" leaveFrom="opacity-100" leaveTo="opacity-0">
                    <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
                </Transition.Child>
                <div className="fixed z-10 inset-0 overflow-y-auto">
                    <div className="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0">
                        <Transition.Child as={Fragment} enter="ease-out duration-300" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterTo="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
                            <Dialog.Panel className="relative bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full">
                                <div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
                                    {
                                        // Form here
                                    }
                                </div>
                                <div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
                                    <Button type="button" text="Crea Area" decoration="primary" otherCSS={"w-full justify-center sm:ml-3 sm:w-auto sm:text-sm"} onClick={() => setOpen(false)}/>
                                    <Button type="button" text="Annulla" decoration="secondary" otherCSS={"w-full justify-center sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"} onClick={() => setOpen(false)} ref={cancelButtonRef}/>
                                </div>
                            </Dialog.Panel>
                        </Transition.Child>
                    </div>
                </div>
            </Dialog>
        </Transition.Root>
    );
}

export default NewAreaModal;

What I want to do is opening the component NewAreaModal.js when I'm clicking the Crea Nuova Area button... what is wrong with my code?

I have tried many ways but it dosen't work. For example, I tried to pass a prop to NewAreaModal called show and then I putted this variable inside useState in const [open, setOpen] = useState(show) but the <Transition> component says that is missing the show prop even if I passed a boolean variable...

This code is missing some functions for the comunication with the backend because I just started!

I'm practicing React since 2 months so I don't have a lot of experience...

Thank you guys for the patience!

CodePudding user response:

I have tried many ways but it dosen't work. For example, I tried to pass a prop to NewAreaModal called show and then I putted this variable inside useState in const [open, setOpen] = useState(show) but the <Transition> component says that is missing the show prop even if I passed a boolean variable...

Passing in the show prop is the right idea, but you are missing a few things, and overcomplicating in some other areas.

The code in the GestioneAree component looks correct to me - you have a show state variable that you pass in to NewAreaModal, and set this to true when the button is clicked to open the modal. This is all correct. All you need to do is to have NewAreaModal use that to determine whether to show the component's contents or not.

To do that you need to first have NewAreaModal accept show as a prop:

function NewAreaModal({ show }) {
  // component code
}

This show prop will then be the only thing that controls whether the modal is open or not. So you don't need, or want, any state here. So remove this line:

const [open, setOpen] = useState(true);

and instead use the show prop where you were using open before - in particular pass it to the Transition.Root, which is component you don't show but I assume this is what will actually show or hide the contents:

<Transition.Root show={show}>

That's essentially it, but there's another important way you will have to change your code. Your modal component appears to have a button inside it to close the modal. That would work when you had the open (or show) property in state, as you could have the button set that state to false. That doesn't work directly when show is a prop instead.

But there's an easy fix - to simply have your modal component accept a function that closes the modal.

You already have an openModal function defined in your parent component:

const showModal = () => {
    setShow(true);
}

and you can do something similar to make a "closeModal" function:

const closeModal = () => {
    setShow(false);
}

Then you can pass this function in as a prop to NewAreaModal:

<NewAreaModal show={show} closeModal={closeModal}/>

Finally, NewAreaModal needs to accept this prop:

function NewAreaModal({ show, closeModal }) {
  // component code
}

and call it when the close button is clicked, by replacing onClick={() => setOpen(false)} by onClick={closeModal} for each button where you have this.

(Note that if you are using NewAreaModal elsewhere you will need to ensure this closeModal prop is passed in each time it is used.)

CodePudding user response:

Your parent component seems ok to me. It would say the issue comes from how you handle the button interaction and props in the child.

I would start by trying something like that :

Parent:

   import NewAreaModal from "./NewAreaModal";

function GestioneAree() {

    const [aree, setAree] = useState([]);
    const [show, setShow] = useState(false);

    useEffect(() => {
        axios.get("http://localhost:8080/aree/all").then((res) => {
            setAree(res.data);
            console.log(res.data);
        });
    }, []);

    const showModal = () => {
        setShow(true);
    }

    return (
        <div className="bg-white rounded-lg">
            <div className="px-4 py-5 sm:px-6 rounded">
                <div className="-ml-4 -mt-2 flex items-center justify-between flex-wrap sm:flex-nowrap">
                    <div className="ml-4 mt-2">
                        <h3 className="text-lg leading-6 font-medium text-gray-900">Aree nel Sistema</h3>
                    </div>
                    <div className="ml-4 mt-2 flex-shrink-0">
                        <Button type="button" decoration="primary" text="Crea Nuova Area" onClick={showModal}/>
                    </div>
                </div>
            </div>
            <div className="flex flex-col">
                <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
                    <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
                        <div className="shadow overflow-hidden border-t border-gray-200 sm:rounded-lg">
                            <table className="min-w-full divide-y divide-gray-200">
                                <thead className="bg-gray-50">
                                    <tr>
                                        <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                            Toponimo
                                        </th>
                                        <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
                                            Territorio
                                        </th>
                                        <th scope="col" className="relative px-6 py-3">
                                            <span className="sr-only">Edit</span>
                                        </th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {aree.map((area) => (
                                        <tr key={area.idArea} className="bg-white border-b">
                                            <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{area.toponimo}</td>
                                            <td className="px-6 py-4 whitespace-nowrap text-sm font-medium text-gray-900">{area.territorio.nome}</td>
                                            <td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
                                                <button type="button" className="text-green-600 hover:text-green-900" >
                                                    Modifica
                                                </button>
                                            </td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                            <NewAreaModal show={show} setShow={(bool) => setShow(bool) />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    );
}

export default GestioneAree;

Child :

import React, { useState, useRef, Fragment } from "react";
 import { Dialog, Transition } from "@headlessui/react";
 import Button from "../../../components/Button";
        
    function NewAreaModal({show, setShow}) {
    
        const cancelButtonRef = useRef(null);
   
    
        return (
            <Transition.Root show={show}>
                <Dialog as="div" className="relative z-10" initialFocus={cancelButtonRef} onClose={setOpen}>
                    <Transition.Child as={Fragment} enter="ease-out duration-300" enterFrom="opacity-0" enterTo="opacity-100" leave="ease-in duration-200" leaveFrom="opacity-100" leaveTo="opacity-0">
                        <div className="fixed inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
                    </Transition.Child>
                    <div className="fixed z-10 inset-0 overflow-y-auto">
                        <div className="flex items-end sm:items-center justify-center min-h-full p-4 text-center sm:p-0">
                            <Transition.Child as={Fragment} enter="ease-out duration-300" enterFrom="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95" enterTo="opacity-100 translate-y-0 sm:scale-100" leave="ease-in duration-200" leaveFrom="opacity-100 translate-y-0 sm:scale-100" leaveTo="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95">
                                <Dialog.Panel className="relative bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:max-w-lg sm:w-full">
                                    <div className="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
                                        {
                                            // Form here
                                        }
                                    </div>
                                    <div className="bg-gray-50 px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
                                        <Button type="button" text="Crea Area" decoration="primary" otherCSS={"w-full justify-center sm:ml-3 sm:w-auto sm:text-sm"} onClick={() => setShow(false)}/>
                                        <Button type="button" text="Annulla" decoration="secondary" otherCSS={"w-full justify-center sm:mt-0 sm:ml-3 sm:w-auto sm:text-sm"} onClick={() => setShow(false)} ref={cancelButtonRef}/>
                                    </div>
                                </Dialog.Panel>
                            </Transition.Child>
                        </div>
                    </div>
                </Dialog>
            </Transition.Root>
        );
    }
    
    export default NewAreaModal;
  • Related