I'm in the process of teaching myself React and have been constructing a simple budgeting system to do it. I'm using React-Bootstrap for the design and have built a few different modals to handle certain functionality in my app.
The app works just fine, however as a developer I feel that things could be tightened up quite a bit in terms of how my code is designed.
One of the places that could be designed better is how I'm handling multiple modals. As you can see I currently have 4 modals, all of which are being passed a variety of props (that are also passed into BudgetContainer). Some of this functionality (such as "setOpenModal") is necessary due to the fact that I need to be able to control which modal is opened from various places within the app.
However this design results in having to pass a ton of props back and forth all across my app in order get all the data/functions to be present where I need it to be. After some research I see that this is what is referred to as 'prop-drilling'.
I've looked into some other methods such as some sort of global state-management approach (such as Redux), but these all seem like a very heavy-handed solution.
I also considered moving the location of the modals onto the same component that triggers them to show, however then I end up with random modals scattered all throughout the app rather than being in one area.
Is there some design pattern I'm overlooking here? I'm a just a React newb? I feel like modals are a common enough feature in web development that a solution must already be present.
import React, { useState } from 'react'
import { Row, Col } from 'react-bootstrap-v5'
import { useAsync } from 'react-async'
import budgetApi from '@/api/budgetApi.js'
import { transactionDefault } from '@/objectDefaults/transaction.js'
import BudgetContainer from './components/BudgetContainer.js'
import Cashflow from './components/Cashflow.js'
import UnbudgetedTransactions from './components/UnbudgetedTransactions.js'
import BudgetModal from '@/common/modals/BudgetModal'
import DeleteBudgetModal from '@/common/modals/DeleteBudgetModal'
import TransactionDetailModal from '@/common/modals/TransactionDetailModal'
import TransactionSplitModal from '@/common/modals/TransactionSplitModal'
function BudgetContent() {
const [activeBudget, setActiveBudget] = useState({})
const [activeTransaction, setActiveTransaction] = useState(transactionDefault)
const [activeDate, setActiveDate] = useState(new Date())
const [openModal, setOpenModal] = useState('')
// Retrieve the budgeting data
const { data, error, isPending, run } = useAsync({
promiseFn: budgetApi.getPromise,
deferFn: budgetApi.getDefer,
watch: activeDate,
onResolve: (resolvedData) => {
// Update the activeBudget's data
let freshActiveBudget = resolvedData.data.budgeted_transactions.filter(
freshBudget => freshBudget.id == activeBudget.id
)
setActiveBudget(freshActiveBudget[0] ?? {})
},
// PHP's Carbon library can easily parse ISO dates
period: activeDate.toISOString()
})
return (
<div>
<Row>
<Col md={7}>
<BudgetContainer
budgets={data && data.data.budgeted_transactions}
activeDate={activeDate}
setActiveDate={setActiveDate}
isPending={isPending}
activeBudget={activeBudget}
setActiveBudget={setActiveBudget}
setActiveTransaction={setActiveTransaction}
setOpenModal={setOpenModal}
/>
</Col>
<Col md={5}>
<Row>
<Col>
<Cashflow />
</Col>
</Row>
<Row className="pt-2">
<Col>
<UnbudgetedTransactions
transactions={data && data.data.unbudgeted_transactions}
setOpenModal={setOpenModal}
setActiveTransaction={setActiveTransaction}
/>
</Col>
</Row>
</Col>
</Row>
{<TransactionDetailModal
show={openModal == 'transaction_detail'}
setOpenModal={setOpenModal}
activeTransaction={activeTransaction}
refreshBudgets={run}
budgets={data && data.data.budgeted_transactions}
/>
<TransactionSplitModal
show={openModal == 'transaction_split'}
setOpenModal={setOpenModal}
activeTransaction={activeTransaction}
/>
<BudgetModal
show={openModal == 'create_budget'}
setOpenModal={setOpenModal}
refreshBudgets={run}
activeBudget={activeBudget}
/>
<DeleteBudgetModal
show={openModal == 'delete_budget'}
setOpenModal={setOpenModal}
refreshBudgets={run}
activeBudget={activeBudget}
setActiveBudget={setActiveBudget}
/>}
</div>
)
}
export default BudgetContent
In the end I just chose to use React's Context API to share the information needed by the modals between elements and avoid prop drilling.