How to toggle DropdownMenu when clicking a button in React? - javascript

I have this DropdownMenu component that works just fine. Basically, if you click on "Click me" button you'll see a DropdownMenu then if you either click outside the DropdownMenu or select an option by clicking on it the Dropdown disappears and this is expected.
My issue is that I want to be able to toggle the Dropdown Menu when clicking on "Click me" button, as of right now if I click on "Click me" button the menu shows up, then if I click again on "Click me" button the DropdownMenu won't go away (I want to toggle it). Can anyone point me in the right direction and tell me what I'm missing or need to do please? Thanks a lot in advance!
Here's a LIVE DEMO of my code, and here are the 2 functions that handle the toggle for my DropdownMenu and handle the ClickOutside.
const handleToggle = () => setOpen((isOpen) => !isOpen);
const handleClickOutside = (evt: MouseEvent) =>
!menuListRef?.current?.contains(evt.target as HTMLElement) &&
setOpen(false);

You should define a ref also on your button:
const buttonRef = useRef<HTMLDivElement | null>() as React.MutableRefObject<HTMLInputElement>;
...
<StyledMenuButton onClick={handleToggle} ref={buttonRef}>
And add a check for it not to be the clicked element in handleClickOutside:
const handleClickOutside = (evt: MouseEvent) => {
if (!menuListRef?.current?.contains(evt.target as HTMLElement) &&
!buttonRef?.current?.contains(evt.target as HTMLElement)) {
setOpen(false);
}
}
Working sandbox.

const buttonRef = useRef<HTMLDivElement | null>() as React.MutableRefObject<HTMLInputElement>;
.
.
.
const handleClickOutside = (evt: MouseEvent) => {
const dropdownClicked = menuListRef?.current?.contains(
evt.target as HTMLElement
);
const buttonToOpenClicked = buttonRef?.current?.contains(
evt.target as HTMLElement
);
// If nether the button or dropdown menu has been clicked
// You haven't clicked "outside"
if (!dropdownClicked && !buttonToOpenClicked) {
console.log("Clicked outside");
setOpen(false);
}
};
.
.
.
<StyledMenuButton onClick={handleToggle} ref={buttonRef}>

Related

Navigation - Outside Click

Since adding an event listener to the document to close the navigation on an outside click, the burger menu has gone from 100% effective to activating the active class's to being less effective and not always creating the event. Any help will be much appreciated.
const burger = document.querySelector('.nav-mob-menu');
const mobNav = document.querySelector('.mobile-nav-side');
burger.addEventListener('click', () => {
mobNav.classList.toggle('active');
burger.classList.toggle('active');
})
document.querySelectorAll('.links').forEach(e =>
e.addEventListener('click', () => {
burger.classList.remove('active');
mobNav.classList.remove('active');
}))
document.addEventListener('click', (e) => {
if(e.target.id != 'nav-mob-menu' && e.target.id != 'mobile-nav-side'){
mobNav.classList.remove('active');
burger.classList.remove('active');
}
})

Check if a div element can receive paste event i.e if it is focused

I am capturing a paste event in a div element.
<div onPaste={(e)=>console.log(e.clipboardData.getData("Text"))}}>
</div>
This, however only works if the element has been clicked into (and the user hasn't clicked outside of it)
How do I indicate to the user that the element is able to receive a paste (i.e is focused, I suppose)
document.activeElement remains focused on body I suppose because the div is not an input
You should use react hook useState for that:
import { useState } from 'react'
const App = () => {
const [focused, setFocused] = useState(false)
const onFocus = () => {
setFocused(true)
}
const onBlur = () => {
setFocused(false)
}
return (
<div>
<div onFocus={onFocus} onBlur={onBlur}>{focused ? 'focused' : 'not focused'}</div>
</div>
)
}
export default App
And now if the focused variable is true, the element is focused and if it's false the element is blurred.

React Dropdown acessibility onClick

at the current point, this code works, but when the user clicks to hide the menu, the useClickOutside fires too, the menu toggles off and on again... would there any way to fix that so when clicks outside it closes but when clicks the button it toggles on/off ?
const useClickOutside = (ref, handler) => {
useEffect(() => {
const clickHandler = (event) => {
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener('mousedown', clickHandler);
return () => {
document.removeEventListener('mousedown', clickHandler);
};
});
};
const Settings = () => {
const ref = useRef();
const [toggle, setToggle] = useState(false);
useClickOutside(ref, () => setToggle(false));
return (
<div className='settings'>
<button onClick={() => setToggle(!toggle)} className='settings__button'>
Menu
</button>
{toggle && (
<div ref={ref} className='settings__panel'>
<Link className='settings__links' to='/user/settings'>
Your Profile
</Link>
<Link className='settings__links' to='/user/settings'>
Todos history
</Link>
<Link className='settings__links' to='/user/settings'>
Settings
</Link>
<Link className='settings__links' value={'Logout'} to='/user/login'>
Logout
</Link>
</div>
)}
</div>
);
};
You might consider adding a onBlur event on the .settings div with a tabIndex=0.
You can then then capture blurs of the div and test if the event came from within the div or not.
const onBlur = (e: FocusEvent < HTMLElement > ) => {
if (opened?) {
const element = e.relatedTarget;
if (element == null) {
// dropdown was blured because window lost focused. probably close.
} else if (element != e.currentTarget) {
if (!e.currentTarget.contains(element as Node)) {
// blured element is not in .settings. close
}
}
}
};
If you want to get fancy you can also add a keydown and close on escape.
const onKeyDown = (e: KeyboardEvent) => {
if (e.key === "Escape") {
// close!
}
);
Here is a code sandbox that implements these items.
You could make use of event stopPropagation. Add the call event.stopPropagation() to your onClick handler function that hides the menu.
<button
onClick={(e) => {
e.stopPropagation();
setToggle(!toggle);
}}
className='settings__button'
>
Menu
</button>
This will prevent that the onClick event bubbles upwards to the next event listener which would be your onClickOutside listener.
UPDATE:
This will only work if your event listener is listening for onClick events. Your inline onClick event listener will stop the propagation of the event of type click only.
document.addEventListener('click', clickHandler);
return () => {
document.removeEventListener('click', clickHandler);
};

How to prevent react-dates SingleDatePicker to open on focus?

I'm using react-dates SingleDatePicker. The visibility of the calendar is controlled by the focused prop. When using tabs on the website, I don't want the calendar to open, I want it to open on enter key. Is there a way to achieve this? Thanks!
You should to control "focused" property and onFocusChange method. Also set the listener to container. For example
.....
const [focusedInput, setFocusedInput] = useState(null);
const handleUserKeyPress = (evt) => {
if (evt.keyCode === 13 && !evt.shiftKey) setFocusedInput(true);
};
....
<div onKeyUp={handleUserKeyPress}>
<SingleDatePicker
focused={focusedInput}
onFocusChange={({ focused }) => {
const focus = focusedInput && focused ? true : false;
setFocusedInput(focus);
}}
.....
/>
</div>
.....

How do I get my modal to close when a user clicks on the container?

I'm currently studying a full stack course and my modal isn't behaving as expected
I'm a bit lost on what to do as I can't find any documentation anywhere and while clicking on the close button or pressing ESC works, clicking outside of the box doesn't.
The following code is how it has been suggested I approach the issue but, it doesn't work. I've honestly stared at this for about an hour and just can't connect the dots on what is (not) happening? Please excuse all the commenting and additional code as I'm still learning so, it's how I'm able to follow what's going on:
function showModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.add('is-visible');
}
function hideModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.remove('is-visible');
}
//modal IFFE
document.querySelector('#modal-button').addEventListener('click', () => {
showModal();
});
//-- show modal --
function showModal(title, text) {
var $modalContainer = document.querySelector('#modal-container');
//Selects the element with the associated id
// Clear all content for the selected element
$modalContainer.innerHTML = '';
var modal = document.createElement('div'); //creates a div element withing selected element
modal.classList.add('modal'); //assigns new class to the div element
// Add the new modal content
var closeButtonElement = document.createElement('button'); //creates the close button
closeButtonElement.classList.add('modal-close'); //assigns a class to the new (close) button
closeButtonElement.innerHTML = "×"; //inserts text within the new(close) button
closeButtonElement.addEventListener('click', hideModal);
var titleElement = document.createElement('h1');
titleElement.innerText = title;
var contentElement = document.createElement('p');
contentElement.innerText = text;
modal.appendChild(closeButtonElement);
modal.appendChild(titleElement);
modal.appendChild(contentElement);
$modalContainer.appendChild(modal);
$modalContainer.classList.add('is-visible');
}
document.querySelector('#modal-button').addEventListener('click', () => {
showModal('PokéMon', 'Here is all of the info about your PokéMon');
});
window.addEventListener('keydown', (e) => {
var $modalContainer = document.querySelector('#modal-container');
if (e.key === 'Escape' && $modalContainer.classList.contains('is-
visible')) {
hideModal();
}
});
$modalContainer.addEventListener('click', (e) => {
var target = e.target;
if (target === $modalContainer) {
hideModal();
}
});
Expected result: User clicks outside of the modal (on the container) and the modal closed.
Current result: No change in state, modal remains active and visible. Only by clicking on the close button (x) or by pressing ESC is the desired result achievable.
By Looking at this code I am not sure what is actually supposed to make the modal visible or hide it. Without access to your css (if you have any). I am assuming that all you are doing is adding and removing the class .is-visible from the #modal-container element.
I would suggest that you apply this class to the modal itself, and then you could toggle this class on and off,
Modify your code to do this by doing something like this (added on top of your code):
function showModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.add('is-visible');
document.querySelector('.modal').classList.remove('hide-el')
}
function hideModal() {
var $modalContainer = document.querySelector('#modal-container');
$modalContainer.classList.remove('is-visible');
document.querySelector('.modal').classList.add('hide-el')
}
Where hide-el in your css is:
.hide-el {
display: none;
}
You could also modify your code to appply the is-visible class to your modal element. You should always try to attach the class/id to the element you want to manipulate if you have that option.
Or if you do not have access to a css file:
document.querySelector('.modal').style.display = "none"
and
document.querySelector('.modal').style.display = "block"
Also, your code seems very verbose, was this boilerplate part of the assignment?
heres a working example: https://codepen.io/mujakovic/pen/zVJRKG
The code was in the incorrect place in the end and should have looked something like this:
modal.appendChild(closeButtonElement);
modal.appendChild(titleElement);
modal.appendChild(contentImage);
modal.appendChild(contentHeight);
modal.appendChild(contentElement);
$modalContainer.appendChild(modal);
$modalContainer.classList.add('is-visible');
$modalContainer.addEventListener('click', (e) => { //listening for an event (click) anywhere on the modalContainer
var target = e.target;
console.log(e.target)
if (target === $modalContainer) {
hideModal();
}
});
};
window.addEventListener('keydown', (e) => { //listening for an event (ESC) of the browser window
var $modalContainer = document.querySelector('#modal-container');
if (e.key === 'Escape' && $modalContainer.classList.contains('is-visible')) {
hideModal();
}
});
This is because the action was originally being called on page load and targeted within the window instead of being called within the container and being loaded when the modal loads.
Thank for your help

Categories

Resources