Research on propagation has not given me answers. I have a React list using divs. Each item has a clickable row that takes the user to another page. But within the row, there are dropdown elements for updating the item. stopPropagation has been added to the dropdowns so that the user does not go to another page.
However, if you open the dropdown and then click off to the side because you changed your mind, the row action gets triggered anyway and takes the user to another page. How do I stop that from happening? I cannot target only the row container for redirecting to another page because I can't seem to add a reference to that element that I can match on in event.target. Is there another way to achieve this?
DROPDOWN
const select = () => (
<Select value={value} onChange={(e) => onChange(e)}>
{items.map((option) => (
<MenuItem className={menuItemClass} key={option.id} value={option.id}>
{option.name}
</MenuItem>
))}
</Select>
)
ONCHANGE
const onChange = async (event) => {
event.stopPropagation();
updateItem();
...
ROW
<TableRowBody onClick={(event) => onClickRow(rowItem, event)}>
...
<select />
...
</TableRowBody>
ONCLICKROW
const onClickRow = (value, event) => {
setValue(value);
};
If the clickable element can be a sibling of the select, you can do a css hack like this:
select:focus ~ .clickable-div{
pointer-events:none
}
.clickable-div{
position: absolute;
height: 100%;
width: 100%;
}
When the select is open (focus), it's sibling, .clickable-div will not respond to clicks.
<TableRowBody >
....
<div onClick={(event) => onClickRow(rowItem, event)} className="clickable-div"></div>
<select />
...
</TableRowBody>
Related
So I'm trying to set up a mui-autocomplete component with additional buttons (Clear all (clear all values and close dropdown) + Apply (set value and close dropdown)) using ListboxComponent.
Issues:
when selecting options from the bottom of the list, the scroll position is reset to the top
cannot close the dropdown programmatically
Here is the ListboxComponent
ListboxComponent={(listBoxProps) => {
return (
<div>
<ul {...listBoxProps} />
<div>
<button
onMouseDown={(event) => {
// Disable blur
event.preventDefault();
}}
onClick={() => {
// clear values
setSelected([]);
}}
>
Clear All
</button>
<button
onMouseDown={(event) => {
// Disable blur
event.preventDefault();
}}
onClick={() => {
// apply value
}}
>
Apply
</button>
</div>
</div>
);
The options are rendered as follows:
renderOption={(optionProps, option, optionState) => {
return (
<li {...optionProps}>
<Checkbox
icon={icon}
checkedIcon={checkedIcon}
checked={optionState.selected}
/>
{option}
</li>
);
}}
So I'm using state to keep track of saving the selected values:
const [selectedResult, setSelected] = useState([]);
And when the option is selected - the state is updated
onChange={(event, selectedOptions) => {
setSelected(selectedOptions);
}}
But when the state changes, the component is re-rendered and the scroll is reset. It also seems that I can't use local variables to store the intermediate result, as the state won't update and the checkbox won't update.
StackBlitz link
Is there anything I can do to achieve this?
I'm creating a file list with MUI DataGrid. The user able to check the checkbox in DataGrid to make their selection. I want the checkbox to reset after the user perform some action such as delete the selected file.
The problem I'm facing is after I perform the delete action, the checkbox are still checked in the same spot. For example before I press the delete button:
After I press the delete button:
The checkbox are still checked on the second row. How do I reset the checkbox programmatically?
const [selectedFile, setSelectedFile] = useState([]); // To keep selected file
const [files, setFiles] = useState([]); // To keep uploaded file
const deleteSelectedFile = () => {
const filteredFileList = files.filter(
(item) => !selectedFile.includes(item)
);
setFiles(filteredFileList);
};
<DataGrid
rows={displayFile ? displayFile : []}
columns={columns}
pageSize={3}
checkboxSelection
onSelectionModelChange={({ selectionModel }) =>
setSelectedFile(selectionModel)
}
/>
selectionModel is not an array of RowData, but RowId, so in your delete handler, you need to check if the selectionModel includes an item.id, not item:
const deleteSelectedFile = () => {
const filteredFileList = files.filter(
(item) => !selectedFile.includes(item.id)
);
setFiles(filteredFileList);
};
And because you're using controlled selection, you need to provide the selectionModel props to the DataGrid too:
const [selectionModel, setSelectionModel] = React.useState([]);
<DataGrid
{...props}
onSelectionModelChange={setSelectionModel}
selectionModel={selectionModel} // add this line
/>
Live Demo
You could try this, Controlled selection in datagrid
<div style={{ height: 400, width: '100%' }}>
<DataGrid
checkboxSelection
onSelectionModelChange={(newSelection) => {
setSelectionModel(newSelection.selectionModel);
}}
selectionModel={selectionModel}
{...data}
/>
<div aria-hidden onClick={() => setSelectionModel([])}>
deselect all
</div>
</div>
onSelectionModelChange & selectionModel props can be used to control the selection values in data-grid.
Refer this code sandbox
Using the mui-datatable library in react, create a custom column using customBodyRender to add a menu and when you click on one of the options, it will perform an action.
The problem is that when you click on the action, the data that enters the function are always those of the last record in the table, regardless of selecting the menu in the first row, the data is always the last.
What I need is that when I click on the menu of a row, it returns the data of that row.
Sample code: https://codesandbox.io/s/wonderful-mclaren-ivvlq?file=/src/App.js
I've also experienced this. The way I solved it is to set the row selected index in a state variable when selecting the Menu icon, and not inside MenuItem. The Index is passed on from customBodyRenderLite or customBodyRender.
const [anchorIndex, setAnchorIndex] = useState(0);
const handleClick = (event, index) => {
setAnchorEl(event.currentTarget);
setAnchorIndex(index);
};
const iconButtonElement = (index) => {
return (
<div>
<IconButton
aria-label="More"
aria-owns={open ? "long-menu" : null}
aria-haspopup="true"
onClick={event => handleClick(event, index)}
>
<MoreVertIcon color={"action"}/>
</IconButton>
{rightIconMenu(index)}
</div>
)
}
When something should be action on the menu item, it would use the value in the state.
const rightIconMenu = () => {
return (
<Menu elevation={2} anchorEl={anchorEl} open={open} onClose={onMenuClose}>
<MenuItem onClick={handleCellClick}>View</MenuItem>
</Menu>
)
};
function handleCellClick() {
history.push('/vehicleForm', {data: currentDataSet[anchorIndex]});
}
I have been writing a custom Material-UI Select dropdown which has an optional text field at the top to allow the user to search / filter items in the Select if there were many entries.
I am struggling with how to keep the Select open when I click on the text field (rendered as an InputBase) and just have the normal behavior (of closing the Select when a regular MenuItem is selected.
CodeSandbox here : https://codesandbox.io/s/inspiring-newton-9qsyf
const searchField: TextField = props.searchable ? (
<InputBase
className={styles.searchBar}
onClick={(event: Event) => {
event.stopPropagation();
event.preventDefault();
}}
endAdornment={
<InputAdornment position="end">
<Search />
</InputAdornment>
}
/>
) : null;
return (
<FormControl>
<Select
className={styles.root}
input={<InputBase onClick={(): void => setIconOpen(!iconOpen)} />}
onBlur={(): void => setIconOpen(false)}
IconComponent={iconOpen ? ExpandMore : ExpandLess}
{...props}
>
{searchField}
{dropdownElements.map(
(currEntry: string): HTMLOptionElement => (
<MenuItem key={currEntry} value={currEntry}>
{currEntry}
</MenuItem>
)
)}
</Select>
</FormControl>
);
As you can see above I've tried using stopPropagation and preventDefault but to no avail.
check out this codesandbox link: https://codesandbox.io/s/busy-paper-9pdnu
You can use open prop of Select API
I was able to make a controlled open select by providing open prop as a react state variable and implementing correct event handlers. To make it controlled you must provide onOpen and onClose props of the Select and make sure the open prop stays true when the custom textfield is clicked.
One more important thing I had to do was override the default keyDown behavior of the Select component. If you open up a Select and start typing into it, it shifts focus to the select option that matches what you are typing. For example, if you Select had an option with the text Foobar and if you start typing Food and water, it would cause focus to shift from your custom text input onto the Foobar option. This behavior is overridden in the onKeyDown handler of the custom textfield
Working sandbox here
Edit: even though this worked in the codepen, I had to add onChange={handleOpen} to the Select as well to get it working on a real browser with React and Next.
You can still use stopPropagation to make it work
// open state
const [isSelectorOpen, setisSelectorOpen] = useState(false)
// handle change
const handleChange = event => {
const { value } = event.target
event.stopPropagation()
// set your value
}
// selector
<Select
multiple
open={isSelectorOpen}
onChange={handleChange}
input={(
<Input
onClick={() => setisSelectorOpen(!isSelectorOpen)}
/>
)}
// other attribute
>
<MenuItem>a</MenuItem>
<MenuItem>b</MenuItem>
<MenuItem>c</MenuItem>
</Select>
In my case, none of the above worked, but this did the trick to stop closing the Select:
<MenuItem
onClickCapture={(e) => {
e.stopPropagation();
}}>
You can also change onMouseEnter to change some default styling that comes with using MenuItem (like pointer cursor when mouse enters its layout)
onMouseEnter={(e) => {
e.target.style.backgroundColor = "#ffffff";
e.target.style.cursor = "default";
}}
In my case i also needed to remove the grey clicking effect that MenuItem makes on click, which is a new object generated in MuiTouchRipple-root, so changing display to none did the trick.
sx={{
"& .MuiTouchRipple-root": {
display: "none",
},
}}
I am using Ant Select component inside Dropdown component. Here is my index file which renders Dropdown
const getMenu = filter => (
<MenuContainer
...
/>
);
<Dropdown
overlay={getMenu(searchFilter)}
trigger={['click']}
visible={this.state.search}
onVisibleChange={val =>
this.handleDropdownVisibility(val, searchFilter)
}
>
...
</Dropdown>
Here is my MenuContainer which return Select Component inside it
handleSelectChange = val => {
this.setState({
selectedValue: val,
});
};
<Select
ref="selectBox"
onChange={this.handleSelectChange}
style={{ width: '100%' }}
>
{numberComparision.map((item, i) => {
return (
<Option key={i} value={item.id}>
{item.name}
</Option>
);
})}
</select>
so on clicking select value onVisibleChange fires and closes dropdown
In current v3.3.1 there is no API to prevent to close the Dropdown list.
As a solution I can offer this custom component.
Item has a property clickable which indicates will be the droplist closed after click or not. You can set true/false or css name of an element which should not trigger closing drop-list.
Change Menu.Item where the select is contained to a Menu.ItemGroup, those do not trigger the onVisibleChange when clicked.
You are mixing components that are not meant to be mixed here, I believe.
Dropdown expects its overlay to be a menu of some sorts. Or at least something static that does not open yet another dynamic <div> layer.
Select already has a dropdown type behaviour. So your Dropdown opens the Select which opens the Select dropdown, and then they both react to the click event and close.
It is currently not clear from your question and screenshot what you are actually trying to achieve, that could not be achieved using just a Select. You could try clarifying that.