Currently I'm doing this to close my topbar menu but it isn't what I need because it only closes when the menu icon is clicked.
It needs to close when I click anywhere on the website. Is there an easy and efficient way to achieve this with react?
Navbar.js
const handleToggle = () => {
setActive(!isActive);
};
<div className="account" onClick={handleToggle}><img src={davatar} className="avatar" alt="accountmenu" width="40" height="40" /></div>
There is a package called react-cool-onclickoutside. You can use that to solve this issue
OR you can u can create a custom useOutsideHook refer here
import { useState, useEffect, useRef } from "react";
// Usage
function App() {
// Create a ref that we add to the element for which we want to detect outside clicks
const ref = useRef();
// State for our modal
const [isModalOpen, setModalOpen] = useState(false);
// Call hook passing in the ref and a function to call on outside click
useOnClickOutside(ref, () => setModalOpen(false));
return (
<div>
{isModalOpen ? (
<div ref={ref}>
Hey, I'm a modal. Click anywhere outside of me to close.
</div>
) : (
<button onClick={() => setModalOpen(true)}>Open Modal</button>
)}
</div>
);
}
// Hook
function useOnClickOutside(ref, handler) {
useEffect(
() => {
const listener = (event) => {
// Do nothing if clicking ref's element or descendent elements
if (!ref.current || ref.current.contains(event.target)) {
return;
}
handler(event);
};
document.addEventListener("mousedown", listener);
document.addEventListener("touchstart", listener);
return () => {
document.removeEventListener("mousedown", listener);
document.removeEventListener("touchstart", listener);
};
},
// Add ref and handler to effect dependencies
// It's worth noting that because passed in handler is a new ...
// ... function on every render that will cause this effect ...
// ... callback/cleanup to run every render. It's not a big deal ...
// ... but to optimize you can wrap handler in useCallback before ...
// ... passing it into this hook.
[ref, handler]
);
}
Related
im facing this weird behavior when trying to update the parent component with an set function to the child with props
this hook is to open and close the modal to edit an element
//PARENT FILE
//hook
const [isEditModalOpen, setEditModalOpen] = useState(false)
//more code...
//modal
{isEditModalOpen && <EditExcerciseModal setEditModalOpen={setEditModalOpen} isEditModalOpen={isEditModalOpen} />}
and this is the child code
//CHILD FILE
export const EditExcerciseModal = ({setEditModalOpen, excerciseInfo,fetchExcercisesFromRoutine})
//more code etc etc
<div className="addExcerciseModalContainer">
<span onClick={() =>{ setEditModalOpen(false) }} className="xModal">X</span>
i checked and the onClick is working. if i change the parent state manually the Modal works fine and closes.
the weird case when it its working is when instead of calling the set function i create a function with a setTimeout without time like this:
function closeModal(){
setTimeout(() => { setEditModalOpen(false)}, 0);
}
any ideas?
thanks for the help
You need to create a separation of concern. A Modal consists of three parts
The Modal of its Self.
The Content of the Modal.
And the container of the two.
You should be using the useState() hook and calling setEditModalOpen in the same containing component.
You need to make sure that you're declaring and setting state inside the same component.
// children would be the content of the modal
const Modal = ({ children, selector, open, setOpen }) => {
// we need the useEffect hook so that when we change open to false
// the modal component will re-render and the portal will not be created
useEffect(() => {
setOpen(false);
//provide useEffect hook with clean up.
return () => setOpen(true);
}, [selector]);
return open ? createPortal(children, selector) : null;
};
export const EditExerciseModal = ({ close }) => {
return (
<div>
{/* Instead of creating a handler inside this component we can create it in it's parent element */}
<span onClick={close}>X</span>
{/* Content */}
</div>
);
};
export const ModalBtn = () => {
const [isEditModalOpen, setEditModalOpen] = useState(false);
// this is where it all comes together,
// our button element will keep track of the isEditModalOpen variable,
// which in turn will update both child elements
// when true useEffect hook will re-render Modal Component only now it "will" createPortal()
// when our EditExerciseModal alls close it will set change the isEditModalOpen to false
// which will be passed to the Modal component which
// will then cause the component to re-render and not call createPortal()
return (
<>
<button onClick={() => setEditModalOpen(true)}>EditExerciseModal</button>
{setEditModalOpen && (
<Modal
open={isEditModalOpen}
setOpen={setEditModalOpen}
selector={'#portal'}>
<div className='overlay'>
<EditExerciseModal close={() => setEditModalOpen(false)} />
</div>
</Modal>
)}
</>
);
};
I want to add an eventListener to a node in my React component. I am selecting the node with a useRef hook. I am useCallback since the useRef is null on it's first render.
const offeringsContainer = useRef(null);
const setOfferingsContainer = useCallback((node) => {
if (node !== null) {
offeringsContainer.current = node;
}
}, []);
My issue is that my useEffect is not reacting to any changes done to the offeringContainer ref. Meaning, offeringContainer.current is null.
useEffect(() => {
checkHeaderState();
offeringsContainer.current.addEventListener("wheel", onOfferingsContainerWheel, { passive: false });
}, [offeringsContainer.current]);
This is my JSX:
return (
<Fragment>
<div className="card-business-products-services-title-text">
Products & Services
</div>
<div
className="card-business-products-services-container"
id="card-business-products-services-container"
onWheel={onOfferingsContainerWheel}
ref={setOfferingsContainer}
>
{renderOfferings()}
</div>
</Fragment>
);
I know I am doing something incorrectly, but my useEffect hook should be listening to any changes from offeringsContainer.current.
You can just past offeringsContainer to the ref of the component. useEffect will be invoked only when there is first rendering that's why your offeringsContainer.current will not be null.
And you forgot to remove listener after the component will be unmounted.
Your code should be like this;
const offeringsContainer = useRef(null);
useEffect(() => {
checkHeaderState();
offeringsContainer.current.addEventListener(
"wheel",
onOfferingsContainerWheel,
{ passive: false }
);
return () => offeringsContainer.current.removeEventListener("wheel");
}, []);
return (
<Fragment>
<div className="card-business-products-services-title-text">
Products & Services
</div>
<div
className="card-business-products-services-container"
id="card-business-products-services-container"
onWheel={onOfferingsContainerWheel}
ref={offeringsContainer}
>
{renderOfferings()}
</div>
</Fragment>
);
Example: https://codesandbox.io/s/wizardly-banzai-3fhju?file=/src/App.js
Remark: this is not a duplicate of Change the state when clicking outside a component in React
I do not want to handel mousedown/mouseup events.
I would like to do change the state of my component "from the outside". So the component will wait until some action id performed by the HTML code (button click).
The click can change something in the DOM, but is there a way (another then watching for buttondown events) to detect the change?
The result will be like this:
<div id="root">
... the component will be rendered here, hidden at the beginning
</div>
<button onClick=....>Show the Component</button>
You have at least a couple of options:
Use a portal, either to move the button into the React tree even though it's elsewhere in the DOM, or to render your desired component elsewhere. But if you want to keep the button entirely outside of React, this option doesn't apply.
If you want to leave the button entirely outside of React, use a parent component around the component you want rendered/not-rendered, and have that parent component listen for clicks on the button element. Hook up that click handler in componentDidMount or a useEffect hook with no dependencies, and remove it in componentWillUnmount or a cleanup callback you return from your useEffect.
Or driving it from the other side, use non-React code to hook the click and then respond to the click by mounting the component directly on id="root" via ReactDOM.render.
Here's an example of #2:
const {useState, useEffect} = React;
const TheComponent = () => {
return <div>This is the component</div>;
};
const Example = () => {
const [showing, setShowing] = useState(false);
useEffect(
() => {
const clickHandler = () => {
setShowing(flag => !flag);
};
console.log("Hooking click on button");
document.getElementById("the-button").addEventListener("click", clickHandler);
// return cleanup handler:
return () => {
console.log("Unhooking click on button");
document.getElementById("the-button").removeEventListener("click", clickHandler);
};
},
[] // <== No deps = mount/unmount only
);
return (
<div>
{showing && <TheComponent />}
</div>
);
};
const App = () => {
const [mounted, setMounted] = useState(true);
return (
<div>
{mounted && <Example />}
<div>
<input type="button" value="Unmount the parent component entirely" onClick={() => setMounted(false)} />
</div>
</div>
);
};
ReactDOM.render(<App />, document.getElementById("root"));
<div>React <code>id="root"</code>:</div>
<div id="root"></div>
<div style="margin-top: 10px">Outside of React:</div>
<input type="button" id="the-button" value="Toggle">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
Here's an example of #3:
const {useState, useEffect} = React;
const TheComponent = () => {
return <div>This is the component</div>;
};
const root = document.getElementById("root");
let mounted = false;
document.getElementById("the-button").addEventListener("click", () => {
if (mounted) {
ReactDOM.unmountComponentAtNode(root);
} else {
ReactDOM.render(<TheComponent />, root);
}
mounted = !mounted;
});
<div>React <code>id="root"</code>:</div>
<div id="root"></div>
<div style="margin-top: 10px">Outside of React:</div>
<input type="button" id="the-button" value="Toggle">
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
So I'm simply trying to toggle the visibility of a React component on a specific keypress.
This is working perfectly, but it seems every Time i toggle the component visibility using ctrl+`, the process gets slower and slower, and my console.log(e) from the onKey function in my app component gets called 10,50,100, 1000 times etc.
This is what I have so far
ReactDOM.render(
<React.StrictMode>
<Router>
<App />
</Router>
</React.StrictMode>,
document.getElementById('root')
);
function App() {
const [compHidden, toggleComp] = useState(true);
const onKey = (e) => {
console.log(e)
if(e.ctrlKey && e.key === '`') {
toggleComp(!compHidden);
}
}
document.addEventListener('keydown', onKey);
return (
<ComponentToHide hidden={compHidden}/>
);
}
function ComponentToHide(props) {
return (
<div style={{display: props.hidden ? 'none' : 'flex'}}>
<p>Visible</p>
</div>
)
}
Do not use document.addEventListener inside the render function, as that function is called every time your component renders and it will add a new event listner each time.
Instead, use the React Hook React.useEffect() to add the event listner only once as the component is mounted:
function App() {
const [compHidden, setCompHidden] = useState(true);
useEffect(() => {
const onKey = (e) => {
console.log(e)
if(e.ctrlKey && e.key === '`') {
setCompHidden(value => !value);
}
}
document.addEventListener('keydown', onKey);
return () => document.removeEventListener('keydown', onKey);
}, [])
return (
<ComponentToHide hidden={compHidden}/>
);
}
Two things to note:
useEffect's second argument, just an empty array, tells React only to run that function once at the beginning of the components lifecycle
You can return a cleanup function that is ran as your component is unmounted. We use this here to remove the event listener to fix an additional memory leak.
I wanted to hide menu slider onclicking a body in reactjs. how can i do that in function using react.js.
document.querySelector('.open-menu').onclick = function(){
html.classList.add('active');
};
document.querySelector('.close-menu').onclick = function(){
html.classList.remove('active');
};
html.onclick = function(event){
if (event.target === html){
html.classList.remove('active');
}
};
I want this same functionality in react js.
Check the code below.
import React, { useState } from 'react';
const SomeComponent = () => {
const [isMenuOpen, showMenu] = useState(false)
const toggleMenu = () => showMenu(!isMenuOpen)
return (
<>
{isMenuOpen && <MenuCompoment />}
<div onClick={toggleMenu}><App /></div>
</>
)
}
This is a stripped down version of code I've used before.
UseEffect on mounting of the Menu adds an event listener on the document for the click event.
When a click happens it uses closest to look up the parent tree of elements for an id (note the '#')
If it finds one, then the click happened on the menu otherwise it happened on any other element so close.
When the menu is disposed the return function of useEffect is called and removes the event handler.
import React, {useState, useEffect} from 'react';
const Page = () => {
const [toggle, setToggle] = useState(false);
return <div>
<button type="button" onClick={e => setToggle(!toggle)}>Toggle</button>
{ toggle && <Menu show={toggle} hide={() => setToggle(false)}/>}
</div>
}
const Menu = ({show, hide}) => {
useEffect(() => {
document.addEventListener("click", listen);
return () => {
document.removeEventListener("click", listen);
}
}, []);
const listen = e => {
if (!e.target.closest("#menu")) {
hide();
}
}
return <div className="menu" id="menu">
<span>I'm a menu</span>
</div>;
}
i think setting onclick event on the menuItems like this will Work
onClick={()=> setOpen(!open)}
export function SidebarMenu({open, setOpen}) {
return (
<div open={open}>
<Link to="#" title="Home" onClick={()=> setOpen(!open)} >Home</Link>
</div>
)
}
Probably too late for answer but since I saw it in active feed, I will try my best to answer it.
I can see that if your menu is open, you want to hide it if clicked anywhere else. I have used useRef to store the menu node and I compare it to the document whenever its open, if it is, I close the menu
Codesandbox link