简体   繁体   中英

React close all child modal from parent

I've three components with the following tree:

<Update>
  <ExpenseItem>
    <ExpenseItemModal>

Update takes an array of expenses and render a ExpenseItem component for each expense. I'm using an hook to handle modal visibility. As you can expect, i'm using this modal to edit the expense attributes.

A toggle method is imported from useModal hook on ExpenseItem to open and close the modal. What I expect is to click outside of the modal and close it. But if I've another ExpenseItem with the modal set to true, it will close the current, but it will still show the other one. I want to click outside of the modal (maybe on Update component) and close all modals at once, to avoid multiple modals opened. Actually I want only on modal open at once.

These are the following components:

Upload

import { useState, useEffect } from 'react';
import useModal from '../hooks/useModal';
import ExpenseItem from './expenseItem';
import axios from 'axios';

function Update({ data }) {
    useEffect(() => console.log('update component', expenses));
    const saveToDatabase = () => {
        axios.post('http://localhost:3001/expenses', expenses).then((res) => {
            console.log('data is saved to database');
        });
    };
    const { setIsShowing } = useModal();
    const closeModals = () => setIsShowing(false);
    const [ expenses, setExpenses ] = useState(data);
    return (
        <div>
            {expenses.map((expense, index) => {
                return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
            })}
            <button onClick={() => saveToDatabase()}>Save</button>
        </div>
    );
}

export default Update;

ExpenseItem

import useModal from '../hooks/useModal';
import EditExpenseModal from './editExpenseModal';

function ExpenseItem(props) {
    const { isShowing, toggle, setIsShowing } = useModal();
    let { description, date, credit, debit } = props.expenses[props.index];
    const updateValue = (expense, setExpenses, success) => {
        const expenses = [ ...props.expenses ];
        expenses.splice(props.index, 1, {
            ...expense
        });
        setExpenses(expenses);
        success();
    };
    return (
        <div>
            <div className="expense-box" onClick={toggle}>
                <p>{date}</p>
                <div className="expense-info">
                    <p className="expense-info--description">{description}</p>
                    <p className="expense-info--debit">{debit}</p>
                    <p className="expense-info--credit">{credit}</p>
                </div>
            </div>
            <EditExpenseModal
                isShowing={isShowing}
                hide={toggle}
                expense={props.expenses[props.index]}
                updateExpense={updateValue}
                setExpenses={props.setExpenses}
            />
            <style jsx>{`
                .expense-box {
                    width: 800px;
                    border: 1px solid black;
                    border-radius: 2px;
                    margin: 25px auto;
                    padding: 0 10px;
                }
                .expense-info {
                    display: flex;
                }
                .expense-info--description {
                    margin: 0 auto 0 0;
                }
                .expense-info--debit {
                    color: red;
                }
                .expense-info--credit {
                    color: green;
                }
            `}</style>
        </div>
    );
}

export default ExpenseItem;

EditExpenseModal

import { useState, useEffect, Fragment } from 'react';
import { createPortal } from 'react-dom';

const EditExpenseModal = ({ expense, isShowing, hide, updateExpense, setExpenses }) => {
    const { description, date, credit, debit } = expense;
    useEffect(() => {
        document.body.style.overflow = 'hidden';
        return () => (document.body.style.overflow = 'unset');
    }, []);
    const [ expenseItem, setExpenseItem ] = useState({
        date,
        description,
        category: null,
        subcategory: null,
        credit,
        debit
    });
    const handleInputChange = (e) => {
        const { name, value } = e.target;
        setExpenseItem({ ...expenseItem, [name]: value });
    };
    return isShowing
        ? createPortal(
                <Fragment>
                    <div>
                        <div className="form">
                            <form>
                                <ul>
                                    <li className="form-inputs">
                                        <label>Date</label>
                                        <input type="text" name="date" defaultValue={date} onChange={handleInputChange} />
                                    </li>
                                    <li className="form-inputs">
                                        <label>Description</label>
                                        <input
                                            type="text"
                                            name="description"
                                            defaultValue={description}
                                            onChange={handleInputChange}
                                        />
                                    </li>
                                    <li className="form-inputs">
                                        <label>Category</label>
                                        <input type="text" name="category" onChange={handleInputChange} />
                                    </li>
                                    <li className="form-inputs">
                                        <label>Subcategory</label>
                                        <input type="text" name="subcategory" onChange={handleInputChange} />
                                    </li>
                                    <li className="form-inputs">
                                        <label>Credit</label>
                                        <input
                                            type="text"
                                            name="credit"
                                            defaultValue={credit}
                                            onChange={handleInputChange}
                                        />
                                    </li>
                                    <li className="form-inputs">
                                        <label>Debit</label>
                                        <input
                                            type="text"
                                            name="debit"
                                            defaultValue={debit}
                                            onChange={handleInputChange}
                                        />
                                    </li>
                                </ul>
                            </form>
                            <button onClick={() => updateExpense(expenseItem, setExpenses, hide)}>save</button>
                            <button onClick={hide}>close</button>
                        </div>
                        <style jsx>{`
                            .form {
                                background: grey;
                                display: flex;
                                flex-direction: column;
                                position: absolute;
                                height: 100vh;
                                top: 0;
                                right: 0;
                                width: 40%;
                            }
                            .form-inputs {
                                display: flex;
                                flex-direction: column;
                                list-style-type: none;
                                padding: 1rem 2rem;
                            }
                        `}</style>
                    </div>
                </Fragment>,
                document.body
            )
        : null;
};

export default EditExpenseModal;

useModal Hook

import { useState } from 'react';

const useModal = () => {
    const [ isShowing, setIsShowing ] = useState(false);

    function toggle() {
        setIsShowing(!isShowing);
    }

    return {
        isShowing,
        setIsShowing,
        toggle
    };
};

export default useModal;

I don't mind to change these modal structure to make it work.

In this case, to avoid these scenarios you can write a separate method to close modal,

inside ExpenseItem.js

<EditExpenseModal
                isShowing={isShowing}
                hide={hideModal}  //instead of toggle
                ...
 >

and write hideModal method to close modal by passing directly 'false' value instead of using! operator. like this in useModal Hook :

function hideModal() {
        setIsShowing(false);
    }

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