How can I make react-bootstrap's Dropdown open on mouse hover? - javascript

Ok the question is simple. I need to make the dropdown open when mouse hover rather than on click.
Currently my code is as follow.
<Nav>
<NavDropdown
onMouseEnter = {()=> isOpen=true}
open={isOpen}
noCaret
id="language-switcher-container"
>
<MenuItem>Only one Item</MenuItem>
</NavDropdown>
</Nav>
as you can see, I tried onMouseEnter but no effect. Can someone solve this issue? What attribute should I pass in to achive this.
The API page is here react-bootstrap API page

export class Nav extends React.Component {
constructor(props) {
super(props)
this.state = { isOpen: false }
}
handleOpen = () => {
this.setState({ isOpen: true })
}
handleClose = () => {
this.setState({ isOpen: false })
}
render() {
return (
<Nav>
<NavDropdown
onMouseEnter = { this.handleOpen }
onMouseLeave = { this.handleClose }
open={ this.state.isOpen }
noCaret
id="language-switcher-container"
>
<MenuItem>Only one Item</MenuItem>
</NavDropdown>
</Nav>
)
}
}
Hope this solves your issue.

A much cleaner pure CSS solution here:
.dropdown:hover .dropdown-menu {
display: block;
}

Having just run into this issue myself, I found out you need both the bootstrap CSS and a parameter for react-bootstrap. Add the following CSS:
.dropdown:hover>.dropdown-menu {
display: block;
}
Then you need to add the parameter renderMenuOnMount={true} for the child elements to render on load:
<NavDropdown renderMenuOnMount={true}>
<NavDropdown.Item href="#">Item #1</NavDropdown.Item>
</NavDropdown>

It's as simple as that :)
<NavDropdown onMouseEnter={(e) => document.getElementById("idofthiselement").click()} onMouseLeave={(e) => document.getElementById("idofthiselement").click()} title="Others" id="idofthiselement">

This seems like a hacky solution, but it was the easiest way to keep my app running after updating.
With the latest update the dropdown-menu isn't rendered until the button is clicked.
I've been using this in my css:
.dropdown:hover .dropdown-menu {
display: block;
}
and added this to my component
componentDidMount() {
// TODO: Fix so menu renders automatically w/out click
const hiddenNavToggle = document.getElementById('navbar__hamburger_wrapper-hidden_click')
hiddenNavToggle.click()
hiddenNavToggle.click()
}
I added a wrapping div with the given id just to click for this.
I'd love to hear another solution.

base on the answer of #Rei Dien
onMouseEnter = { this.handleOpen.bind(this) }
onMouseLeave = { this.handleClose.bind(this) }

Inspired by Rei Dien's answer, here's how you could do this using React Hooks.
export const Menu = () => {
const [menuOpen, toggleMenuOpen] = useState(false);
return (
<Dropdown
onMouseEnter={() => {
toggleMenuOpen(true);
}}
onMouseLeave={() => {
toggleMenuOpen(false);
}}
show={menuOpen}
>
<Dropdown.Toggle>
Menu
</Dropdown.Toggle>
<Dropdown.Menu>
<Dropdown.Item href="#">Menu!!!</Dropdown.Item>
</Dropdown.Menu>
</Dropdown>
);
};

Another way
const HoverDropDown = ({ children, ...rest }) => {
const [show, setShow] = useState(false);
return (
<NavDropdown {...rest} show={show} onMouseEnter={() => setShow(true)} onMouseLeave={() => setShow(false)}>
{children}
</NavDropdown>
);
}
<Nav>
<HoverDropDown id="language-switcher-container" title="Menu">
<Dropdown.Item>Item 1</Dropdown.Item>
</HoverDropDown>
</Nav>

You could replicate this https://codepen.io/bsngr/pen/frDqh which uses the following JS
$('ul.nav li.dropdown').hover(function() {
$(this).find('.dropdown-menu').stop(true, true).delay(200).fadeIn(500);
}, function() {
$(this).find('.dropdown-menu').stop(true, true).delay(200).fadeOut(500);
});
Or alternatively you could install a plugin like this https://github.com/CWSpear/bootstrap-hover-dropdown
EDIT: Perfectly fine solutions to bootstrap but I noticed now you said its react and I havent used that before but I dont see why the js above wouldnt work even if it requires tweaking

Related

How to Close Floating Menu when clicking anywhere else but the menu? React JS

I was struck while creating a Floating menu in react JS, the task required is to close the floating menu when a click is recorded anywhere outside the menu, the code snippet is as follows
class SectionMenu extends Component {
state = {
isOpen: false,
activeSection: '',
sectionIds: [],
coordinates: []
}
render() {
const { sections, hideMenu } = this.props;
const { sectionIds } = this.state;
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={this.state.isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
iconResting={}
iconActive={}
onClick={() => this.setState({isOpen:
!this.state.isOpen})}
size={}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
}
I am struck on how to create a useRef/useEffect hook function, is there any method to to it done without useRef as well?
if not how do I do it with useRef/useEffect and/or any other hook?
Try to refactor your code using functional component:
const SectionMenu = ({ sections, hideMenu }) => {
const [sectionsIds, setSectionsIds] = useState([]);
const [isOpen, setIsOpen] = useState(false);
useEffect(() => {
// whatever effect
}, [])
return (
<React.Fragment>
<FloatingMenu
id="floating-menu"
slideSpeed={10}
direction="left"
isOpen={isOpen}
spacing={16}
style={hideMenu ? {display: "none"} : null}
>
<MainButton
onClick={() => setIsOpen((prev) => !prev)}
/>
{"Random Code to bring up each menu item"}
</FloatingMenu>
</React.Fragment>
)
}
Yes, I've ran up into the same issue and this worked for me . You can change the onClick() as follows
let isClicked=true;
const {/*function name here*/} =()=>{
if(isClicked){
document.querySelector("{/*classname here*/}").style = "display:block "
isClicked=false;
}
else{
document.querySelector("{/*classname here*/}").style = "display:none"
isClicked=true;
}
}
This will toggle between true and false.

React - Access the state of a parent from the children, without nested function

Hello,
I am coming to you today for the first time because I have not found a solution to my problem.
I have been using react for a few weeks, Don't be too cruel about the quality of my code 😁.
Problem :
I am looking to access the state of a parent from their children.So I want to be able to access the setHeight function and the height variable for example from a child component.
Please note :
However, to keep some flexibility, I don't want to have any Components inside our.
I looked at redux to be able to do this, but the problem is that the data is global so creating multiple dropdowns would not be possible.
(Unless I didn't understand too much, redux is quite complex)
Diagram :
I have created a diagram to explain it a little better.,
I'd like the children of DropdownMenu to be able to access the state of the latter, Also, the different Dropdowns must have their own state independently.
So ideally I want to keep the same structure as find very flexible, and the possibility to create several dropdown.
Code :
I Share my four components :
export default function Navbar () {
return (
<nav className={styles.navbar}>
<ul className={styles.navbarNav}>
<NavItem icon={<NotificationsIcon />} />
<NavItem icon={<AccessTimeFilledIcon />} />
<NavItem icon={<FileOpenIcon />}>
<DropdownMenu>
<DropdownSubMenu menuName="Home">
<DropdownItem>My Profile</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="pages">Pages</DropdownItem>
<DropdownItem>IDK</DropdownItem>
<DropdownItem>Test</DropdownItem>
</DropdownSubMenu>
<DropdownSubMenu menuName="pages">
<DropdownItem>Pages</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="home">Home</DropdownItem>
</DropdownSubMenu>
</DropdownMenu>
<DropdownMenu>
<DropdownSubMenu menuName="config">
<DropdownItem>Foo</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="theme">Configuration</DropdownItem>
<DropdownItem>Bar</DropdownItem>
<DropdownItem>Baz</DropdownItem>
</DropdownSubMenu>
<DropdownSubMenu menuName="theme">
<DropdownItem>Hi StackOverflow</DropdownItem>
<DropdownItem leftIcon={<AccessTimeFilledIcon />} rightIcon={<ChevronRightIcon />} goToMenu="config">Theme</DropdownItem>
</DropdownSubMenu>
</DropdownMenu>
</NavItem>
</ul>
</nav>
);
};
type Props = {
children?: React.ReactNode | React.ReactNode[];
leftIcon?: React.ReactNode | JSX.Element | Array<React.ReactNode | JSX.Element>;
rightIcon?: React.ReactNode | JSX.Element | Array<React.ReactNode | JSX.Element>;
goToMenu?: string;
goBack?: boolean;
OnClick?: () => void;
};
export default function DropdownItem({ children, leftIcon, rightIcon, goToMenu, goBack, OnClick }: Props) {
const handleClick = OnClick === undefined ? () => { } : OnClick;
return (
<a className={styles.menuItem} onClick={() => {
goToMenu && setActiveMenu(goToMenu);
setDirection(goBack ? 'menu-right' : 'menu-left');
handleClick();
}}>
<span className={styles.iconButton}>{leftIcon}</span>
{children}
<span className={styles.iconRight}>{rightIcon}</span>
</a>
);
}
type Props = {
menuName: string;
children: React.ReactNode | React.ReactNode[];
}
enum Direction {
LEFT = 'menu-left',
RIGHT = 'menu-right'
}
export default function DropdownSubMenu (props: Props) {
const [direction, setDirection] = useState<Direction>(Direction.LEFT);
const calcHeight = (element: HTMLElement) => {
if (element) setMenuHeight(element.offsetHeight);
};
return (
<CSSTransition in={activeMenu === props.menuName} unmountOnExit timeout={500} classNames={direction} onEnter={calcHeight}>
<div className={styles.menu}>
{props.children}
</div>
</CSSTransition>
);
}
type Props = {
children: React.ReactNode | React.ReactNode[];
}
export default function DropdownMenu (props: Props) {
const [activeMenu, setActiveMenu] = useState<string>('home');
const [menuHeight, setMenuHeight] = useState<number | null>(null);
const dropdownRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const child = dropdownRef.current?.firstChild as HTMLElement;
const height = getHeight(child);
if (height)
setMenuHeight(height);
}, []);
return (
<div className={styles.dropdown} style={{ height: `calc(${menuHeight}px + 2rem)` }} ref={dropdownRef}>
{props.children}
</div>
);
}
Conclusion :
More concretely I don't know what to put instead :
In DropdownSubMenu to set the menu height (setMenuHeight), and gets the active menu (activeMenu).
In DropdownItem, set the active menu, (setActiveMenu) and set the direction of the CSS animation (setDirection).
Source :
My code is adapted from these sources, But I want to make this code more professional, flexible and polymorphic :
https://github.com/fireship-io/229-multi-level-dropdown
I've been tried :
I tried to look at Redux, but I understood that it was only state global.
So it doesn't allow to define a different context for each component.
I tried to look at React 18, without success.
I have searched the StackOverflow posts, I have searched the state retrieval from the parents.
The use of components inside a component solves in effect the problem but we lose all the flexibility.
There are multiple ways to access a parent state from its children.
Pass the state as props
The preferred way is to pass the state and/or the change function to the children.
Example :
const App = () => {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<div>
<button onClick={handleOpen}>Open modal</button>
<Modal onClose={handleClose} open={open} />
</div>
);
};
const Modal = ({ open, onClose }) => (
<div className={open ? "open" : "close"}>
<h1>Modal</h1>
<button onClick={onClose}>Close</button>
</div>
);
ReactDOM.render(<App />, document.querySelector("#app"));
Demo: https://jsfiddle.net/47s28ge5/1/
Use React Context
The first method becomes complicated when the children are deeply nested and you don't want to carry the state along the component tree.
You can then share a state across multiple children by using context.
const AppContext = React.createContext(undefined);
const App = () => {
const [open, setOpen] = React.useState(false);
const handleOpen = () => setOpen(true);
const handleClose = () => setOpen(false);
return (
<AppContext.Provider value={{ open, onClose: handleClose }}>
<div>
<button onClick={handleOpen}>Open modal</button>
<Modal />
</div>
</AppContext.Provider>
);
};
const Modal = () => {
const { open, onClose } = React.useContext(AppContext);
return (
<div className={open ? "open" : "close"}>
<h1>Modal</h1>
<button onClick={onClose}>Close</button>
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
Demo: https://jsfiddle.net/dho0tmc2/3/
Using a reducer
If your code gets even more complicated, you might consider using a store to share a global state across your components.
You can take a look at popular options such as:
Mobx: https://mobx.js.org/ (my personal favorite)
Redux: https://redux.js.org/
RxJS: https://rxjs.dev/
I can say welcome to react in this moment and i glad for you
OK, i could understand what is your problem.
but there isn't problem and this bug cause from your low experience.
As i understand you want to click on a dropdown and open it.
and here we have nested dropdown.
I think it's your answer:
You should declare a state on each dropdown and don't declare state in parent.

Make multitple controlled material-ui tooltips open one at the time

I have a component where i use a few other componentes. I used to have the problem when a select menu was open, the tooltip overlaps the menu and don't stop showing. Now that i have make the Tooltip 'controlled' when i hover on one component it shows all the tooltips, and when click in one stop appearing (this is what i want but on hover one at the same time). This is how it looks:
I have my tooltip like this outside the function of the component im working on:
function Tooltip(props) {
const classes = tooltipStyles();
return <TooltipBase placement="bottom" classes={classes} {...props} />;
}
And this is how it looks in every component i mannage inside the main component:
<Tooltip title="Estatus" open={openTooltip}>
<div
onMouseEnter={() => {
setOpenTooltip(true);
}}
onMouseLeave={() => {
setOpenTooltip(false);
}}
>
<Chip
small
onIconClicked={null}
label="Por hacer"
avatarProps={{
size: 'extraSmall',
backgroundColor: '#F57C00',
icon: null,
}}
isDropdown
onChipClicked={handleOpen}
withPopover
popoverKey="status"
popoverOpen={chipStatusOpen}
popOverContent={PopupStatus}
onPopoverClose={handleClose}
/>
<PopupFilters
anchorEl={anchorElPopupFilters}
onClose={() => setAnchorElPopupFilters(null)}
/>
</div>
</Tooltip>
These are the functions im using to open/close the tooltips an select/popups
const handleClose = () => {
setChipStatus(false);
setAnchorEl(null);
};
const handleOpen = () => {
setChipStatus(true);
setOpenTooltip(false);
};
And the useStates that i use to make it work
const [chipStatusOpen, setChipStatus] = useState(false);
const [openTooltip, setOpenTooltip] = useState(false);
I want to open one at a time. How can i achieve that? What am i doing wrong?

open ant design popover from two link

I have two component: 1:StudentList 2: Major in react and antd.
StudentList Component rendered a list of students.
Major Component made a list of majors that you can pick them. After selecting major, the selected major title display on the top of the students list. and the list will be filtered according to the selected major.
This is StudentList component contain Major component:
class StudentList extends Component {
render(){
return(
<>
<Major/>
<h5>20 student found in <a>selected major</a></h5>
<List>
//this is the list of students and is not related to this question
</List>
</>);
}
}
This is Major Component with a filter button to open the popover:
class Major extends Component {
render() {
return (
<Popover
trigger="click"
content={content} //list of majors
>
<Button>
<FilterOutlined /> Select major to filter
</Button>
</Popover>
);
}
}
When I click on the Select major to filter button, the popover open to select majors. I want to change the code in order to open this popover from two place:
1- click on Select major to filter button in the Major component
2- click on selected major in the title in StudentList component.
Notice: I want to open the same popover in the same place (similar to when I click on Select major to filter button)
Maybe it could handle with state and handleVisibleChange function. but I don't know how to handle it from 2 components. I glad to hearing your solutions.
You can use the visible and onVisibleChange property from Antd's tooltip because they are used by the PopOver as well. You can find an easy example from Andt how to control a PopOver by visible in the docs.
To get the button click you can use onClick from antd's Button Api.
The desired example using React Components:
class Major extends Component {
componentDidUpdate(prevProps) {
// Typical usage (don't forget to compare props):
if (this.props.value !== prevProps.value) {
this.setState({ visible: this.props.value });
}
}
state = {
visible: false
};
hide = () => {
this.setState({
visible: false
});
};
handleVisibleChange = visible => {
this.setState({ visible });
// this.props.onChange(visible); // add me to open popover on every click on studenlist
};
render() {
return (
<Popover
trigger="click"
content={<a onClick={this.hide}>Close</a>}
visible={this.state.visible}
onVisibleChange={this.handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
}
class StudentList extends Component {
state = {
visible: false
};
onClick = () => {
this.setState({ visible: !this.state.visible });
};
render() {
return (
<>
{/* <Major value={this.state.visible} onChange={setVisible} /> */}
<Major value={this.state.visible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={this.onClick}>Select major from Studenlist</Button>
</>
);
}
}
Component example as a CodeSandBox.
Here is a simple example for your request using react hooks and simple buttons to open the PopOver:
function Major({ value, onChange }) {
const [visible, setVisible] = useState(value);
useEffect(() => {
value && setVisible(value);
}, [value]);
const hide = () => setVisible(false);
const handleVisibleChange = visible => {
setVisible(visible);
onChange(visible);
};
return (
<Popover
trigger="click"
content={<a onClick={hide}>Close</a>}
visible={visible}
onVisibleChange={handleVisibleChange}
>
<Button>Select major to filter</Button>
</Popover>
);
}
function StudentList() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
return (
<>
<Major value={visible} onChange={setVisible} />
<h5>
20 student found in <a>selected major</a>
</h5>
<Button onClick={onClick}>Select major from Studenlist</Button>
</>
);
}
The depended working CodeSandBox.
Edit1: Added React Component example.

Close dropdown menu on scroll

I am currently working on a project using Ant Design and on one page I have a dropdown Menu. When you click on the menu it opens the dropdown but if you scroll down the menu still keeps open. I wish to implement that when the user is scrolling down the menu closes.
I tried to implement a handleScroll() function in order to use is with the provided prop onVisibleChange. However I am not sure what I should add in the function to get it work.
handleScroll = (e) => {
window.addEventListener('scroll', () => {
console.log('scrolling');
})
}
<Dropdown onVisibleChange={visible => this.handleScroll(
console.log(visible)) } trigger={['click']} overlay={
<Menu>
<Menu.Item key="1"
onClick={() => this.scrollTo(this.whyRef)}>
<Icon icon={u1F427} /> <strong>WHY</strong>
</Menu.Item>
</Menu>
}>
<Dropdown>
You need to add the event listener in the constructor and handle the visibility state of the dropdown yourself. Try something like this:
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
visible: false
};
window.addEventListener("scroll", this.closeMenu);
}
toggleMenu = () => {
this.setState({ visible: !this.state.visible });
};
closeMenu = () => {
this.setState({ visible: false });
};
render() {
return (
<div>
<Dropdown
overlay={menu}
onVisibleChange={this.toggleMenu}
visible={this.state.visible}
trigger={["click"]}
>
<a className="ant-dropdown-link" href="#">
Click me <Icon type="down" />
</a>
</Dropdown>
</div>
);
}
}
Working example: https://codesandbox.io/s/2ovjzwqz9y

Categories