'React Material UI Select conditional rendering shows value as out of range
The use case is creating an order that has multiple order lines. I have two lists of Products: Preferred and All. Every item in Preferred is in All. I have a boolean state variable ShowPreferredProducts to toggle between which list is passed in the Select to render the MenuItem list.
<Select
label="itemNumber"
{(showPreferredProducts ? preferredProducts : allProducts)
.map((product, idx) => (
<Mui.MenuItem
key={`${product.itemNumber}_${idx}`}
value={product.itemNumber}
/>
{product.itemName}
</Mui.MenuItem>
))}
<ListSubheader sx={{ position: 'sticky', bottom: '0' }}>
<Mui.Box sx={{ pointerEvents: 'none' }}>
<Mui.Button
sx={{ pointerEvents: 'all' }}
onClick={(event) => {
setShowPreferredProducts((prev) => !prev);
}}>
{showPreferredProducts ? 'Show Preferred Products' : 'Show All Products'}
</Mui.Button>
</Mui.Box>
</ListSubheader>
</Select>
When the button is clicked, we re-render the MenuItem list in the Select. When a user selects a product that is in All, but not in Preferred, while showPreferredProducts is true, any products in list All now display as empty (as it cannot find the value in the currently rendered list).
The user wants to always see the Preferred list first (at least for new order lines). A hacky way to keep all items being displayed is to force showPreferredProducts to false in an onClose and it to true in an onOpen. This does not fix the display issue while order lines are being edited, but it does keep the display looking as it should when the order is not being updated.
What is the preferred way of solving this? It "works", but it feels like there is a better way.
Edit:
Found a working solution. There may be a better way but this works and does not cause warnings and correctly allows for searching as well.
Since preferredProducts is a subset of allProducts, we render allProducts and at the MenuItem level we choose to display or not based on some functional programming.
// ensure product's item number and the MenuItem's
// item number is equal since this is done on the `MenuItem` level.
const isProductInSearchValue = (product: Product, itemNumber: string) =>
product.itemNumber === itemNumber &&
(!productSearchValue ?? product.SomeValueToSearch.includes(productSearchValue);
// pick a list to filter from based on showPreferredProducts
const isItemVisibleInProductList = (itemNumber: string) =>
(showPreferredProducts && preferredProducts.some((prod) => isProductInSearchValue(prod, itemNumber))) ||
(!showPreferredProducts && allProducts.some((prod) => isProductInSearchValue(prod, itemNumber)));
<Select
label="itemNumber"
{(allProducts) // render all products not dependent on boolean
.map((product, idx) => (
<Mui.MenuItem
key={`${product.itemNumber}_${idx}`}
value={product.itemNumber}
sx={{display: isItemVisibleInProductList(product.itemNumber) ? 'block' : 'none'}} // set display if true
/>
{product.itemName}
</Mui.MenuItem>
))}
<ListSubheader sx={{ position: 'sticky', bottom: '0' }}>
<Mui.Box sx={{ pointerEvents: 'none' }}>
<Mui.Button
onClick={(event) => {
setShowPreferredProducts((prev) => !prev);
}}>
{showPreferredProducts ? 'Show Preferred Products' : 'Show All Products'}
</Mui.Button>
</Mui.Box>
</ListSubheader>
</Select>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
