Click OutSide co close sidebar reactjs - javascript

I create a function when I click outside of the sidebar it will hide it and I also have a button that toggles show and hide the sidebar. But when I combined both of them together, the button did not work properly, it only show the sidebar but can't close it, only when I click outside it will close the sidebar
Click OutSide to close function:
const ref = useRef(null);
useEffect(() => {
document.addEventListener("mousedown", Clickout);
return () => {
document.removeEventListener("mousedown", Clickout);
};
}, []);
const Clickout = (eve) => {
if (ref.current && !ref.current.contains(eve.target)) {
setShow(false);
}
};
My Return:
return (
<header>
<div className="head">
<div className="logo">
<img src={logo} alt="logo" />
</div>
<button
className="burger"
onClick={() => {
setShow(!showMenu);
console.log("here");
}}
>
<div className={`${showMenu ? "change" : ""} bur1 `}></div>
<div className={`${showMenu ? "change" : ""} bur2 `}></div>
<div className={`${showMenu ? "change" : ""} bur3 `}></div>
</button>
</div>
<nav className={showMenu ? "active" : ""} ref={ref}>
<ul>
{navItem.map((item) => {
const { id, url, text } = item;
return (
<li key={id}>
<a href={url}>{text}</a>
</li>
);
})}
</ul>
</nav>
</header>
);
};
Nav bar CSS:
nav {
position: fixed;
right: -100%;
top: 0;
width: 60%;
height: 100vh;
text-align: center;
padding-top: 15vh;
transition: 0.8s ease;
background-color: blue;
}
nav.active {
right: 0;
transition: 0.5s;
}
Thank you.

you can use another state for manage button onclick when menu is open:
const [disableBtn, setDisableBtn] = useState(false);
and in Clickout function manage it:
const Clickout = (eve) => {
if (showMenu && ref.current && !ref.current.contains(eve.target)) {
setShow(false);
setDisableBtn(true)
} else {
setDisableBtn(false)
}
};
and in button for onclick use condition:
if (!disableBtn) setShow(true);

Updating state this way setShow(!showMenu) does not immediately update the state.Rather it schedules the update(You can read the docs). When your setState depends on your previous state (in this case showMenu depends on previous state) use this technique: (prev) => setState(!prev) instead. So, simply updating your onClick will solve the issue.
<button className="burger"
onClick={() => {
(prevShowMenu) => setShow(!prevShowMenu)
}}>
(Let me know in the comments if this was helpful)

Related

How to animate a sliding cart with react spring with a toggle button

I have almost got this workign but not quite sure what I am doing wrong. It will slide in when I click the toggle button, but it wont slide out when I click it again, it will just rerun the slide in animation.
Any help would be great
I have the following state and toggle function
const [close, setClose] = useState(false)
const toggleCart = () => {
setClose(!close)
}
following component
<CartItems close={close} location={location} />
import React, { useState } from "react"
import tw, { styled } from "twin.macro"
import { useTransition, animated } from "react-spring"
const CartWrapper = styled.div`
.test {
position: fixed;
top: 0px;
z-index: 5000;
right: 0;
height: 100vh;
background: lightgrey;
padding: 25px;
}
`
export function CartItems({ location, close }) {
const transitions = useTransition(close, null, {
enter: { transform: "translate3d(100%,0,0)" },
leave: { transform: "translate3d(0%,0,0)" },
})
return (
<>
<CartWrapper>
{transitions.map(({ props }) => {
return (
<animated.div className="test" style={props}>
<h2>Shopping Cart</h2>
{cart}
<p>Total: {formattedTotalPrice}</p>
<form onSubmit={handleSubmitCheckout}>
{/* include validation with required or other standard HTML validation rules */}
<input
name="name"
placeholder="Name:"
type="text"
onChange={e => setName(e.target.value)}
/>
<input
name="giftMessage"
placeholder="Gift Message:"
type="text"
onChange={e => setGiftMessage(e.target.value)}
/>
<input type="submit" />
</form>
<button onClick={clearCart}>Remove all items</button>
</animated.div>
)
})}
{/* <button onClick={handleSubmit}>Checkout</button> */}
</CartWrapper>
</>
)
}
In your example there is a second item during the transition, one entering, and one leaving. That's why you see always the entering animation.
If you use a boolean instead of array in the useTransition you have to insert a condition in the render method to prevent the second item. Just like the third example in the useTransition doc. https://www.react-spring.io/docs/hooks/use-transition
transitions.map(({ item, props, key }) => {
return (
item && <animated.div className="test" style={props} key={key}>
Now it basically works, but a slight modification in the useTransition is necessary.
const transitions = useTransition(close, null, {
from: { transform: "translate3d(100%,0,0)" },
enter: { transform: "translate3d(0%,0,0)" },
leave: { transform: "translate3d(100%,0,0)" }
});
I have a working example here: https://codesandbox.io/s/toggle-react-spring-transition-ju2jd

How to close a modal with a handleClick, outside of the modal?

Actually, I use isAvatarUserMenuOpen prop into the UserDataPresentation class, to know if the modal is opened or not. I use this state to generate a condition that affect the onClick to open and close the modal. But i need to close this modal with any click made outside this modal, actually it only closes in the same button that open it.
I have been doing a handleClick, that add a listener when the modal is opened, and when i click outside the modal, it shows an alert "close de modal!". I need to remove this alert, and find a way to close the modal, just as the onclick that open and close the modal do.
export class UserDataPresentation extends React.Component<Props> {
node: any
componentWillMount() {
document.addEventListener('mousedown', this.handleClick, false)
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClick, false)
}
handleClick = (e: { target: any }) => {
if (!this.node.contains(e.target)) {
alert('close de modal!')
return
}
}
render() {
const { openMenuUserModal, closeMenuUserModal, isAvatarUserMenuOpen } = this.props
return (
<React.Fragment>
<div className="user-data-container" ref={isAvatarUserMenuOpen ? (node) => (this.node = node) : null}>
<div className="text">
<p>Wolfgang Amadeus</p>
</div>
<div className="avatar">
<img src={avatarPhoto} />
</div>
<a href="#" onClick={isAvatarUserMenuOpen ? closeMenuUserModal : openMenuUserModal}>
<div className="svg2">
<SVG src={downArrow} cacheGetRequests={true} />
</div>
</a>
</div>
</React.Fragment>
)
}
}
I come across this problem quite often and always do the following.
I mix css positioning and react hooks to create a modal. the overlayer div covers the whole div container so when you click any where in the container apart from the modal, the modal dissapears. z-index:1 on #modal makes sure the modal is stacked above the overlayer.
const Modal = () => {
const [modal, setModal] = React.useState(false);
return (
<div id='container'>
<button onClick={() => setModal(true)}>toggle modal</button>
{modal && <div id='overlayer' onClick={() => setModal(false)}></div>}
{modal && <div id='modal'>modal</div>}
</div>
);
};
ReactDOM.render(<Modal/>, document.getElementById("react"));
#container{
position: relative;
height: 200px; width:200px;
border: 1px solid black;
}
#container * {position: absolute;}
#overlayer{
height: 100%; width:100%;
}
#modal{
background: blue;
height: 30%; width:30%;
top: 12%; z-index: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
You should be able to call this.props.closeMenuUserModal() in your handleClick function.

Resetting setTimeout function to prevent an action

I may be thinking about this the wrong way. So any guidance would be very useful.
I have an element, then when you click opens up a number of radio buttons. Clicking one of the radio buttons triggers a setTimeout of 2300ms this will add a class Frequency--closing to an element Frequency which triggers an animation in the css, when the timeout finishes, that component gets removed from the DOM.
I am using the timeout as means to allow an animation before it gets removed from the DOM.
What I am aiming for is when I click a radio button and then click another radio button before the component gets removed from the DOM that the timer resets, so on the 2nd click on it waits another 2300ms before it gets removed from the DOM.
Below is the component that uses state to dictate whether the element is opened or closed with the onToggle() function
const SavedSearchBox = ({ modifier, search, loading }) => {
const [frequencyToggled, setFrequencyToggled] = useState(false);
const [closeFrequency, setCloseFrequency] = useState(false);
const timeoutRef = useRef(null);
const onToggle = () => {
if (frequencyToggled) {
setCloseFrequency(true);
timeoutRef.current = setTimeout(
() => setFrequencyToggled(!frequencyToggled),
2300
);
} else {
setCloseFrequency(false);
setFrequencyToggled(!frequencyToggled);
clearTimeout(timeoutRef.current);
}
};
useEffect(() => () => clearTimeout(timeoutRef.current), []);
return (
<div
className={`SavedSearchBox ${modifier ? modifier : ""} ${
loading && loading.status ? "SavedSearchBox--loading" : ""
}`}
>
<div className="SavedSearchBox-footer">
{frequencyToggled ? (
<Frequency
closeFrequency={closeFrequency}
onChange={() => onToggle()}
/>
) : (
<div onClick={() => onToggle()}>Click Here</div>
)}
</div>
</div>
);
};
The onToggle() function also gets called from the child component Frequency. This component also includes the class Frequency--closing for the closing animation.
const Frequency = props => {
const [active, setActive] = useState(0);
const inputs = [
["Instant Alerts", "Instant"],
["Twice Daily Alerts", "Twice"],
["Daily Alerts", "Daily"],
["Weekly Alerts", "Weekly"],
["No Email Alerts", "None"]
];
return (
<div
className={`Frequency ${
props.closeFrequency ? "Frequency--closing" : ""
}`}
>
<div className="Frequency-list">
{inputs.map(([text, value], i) => (
<div key={i} className="Frequency-listItem">
<label className="Frequency-listLabel">
{text}
<input
type="radio"
checked={value === inputs[active][1]}
onChange={() => {
props.onChange();
setActive(i);
}}
value={value}
/>
<span className="Frequency-listRadio" />
</label>
</div>
))}
</div>
</div>
);
};
Here are the animations in the case that it is that is perhaps causing this issue
.Frequency-listItem {
animation: enter $Frequency-duration $Frequency-easing backwards;
transform: translateY(0%);
transition: transform $Frequency-duration ease;
display: flex;
justify-content: left;
align-items: center;
#for $i from 1 through 6 {
&:nth-of-type(#{$i}) {
animation-delay: calc(#{$i} * #{$Frequency-delay});
}
}
}
.Frequency--closing .Frequency-listItem {
animation: exit $Frequency-exitDuration $Frequency-easing both 1;
transform: translateY(100%);
transition: transform $Frequency-exitDuration ease;
#for $i from 6*-1 through -1 {
&:nth-of-type(#{abs($i)}) {
animation-delay: calc(#{abs($i)} * #{$Frequency-exitDelay} + 2s);
}
}
}
#keyframes enter {
from {
opacity: 0;
padding: 0;
margin: 0;
max-height: 0;
transform: scale(.7);
}
}
#keyframes exit {
from {
opacity: 1;
max-height: 100%;
transform: scale(1);
}
to {
opacity: 0;
padding: 0;
margin: 0;
max-height: 0;
transform: scale(.7);
}
}
Here is a CODESANBOX if you wish to test it out
Any help would be greatly appreciated!

How to have the children affect the parent in React?

I have a situation where I have something like this:
<DropDown>
<p>Select an option:</p>
<button onClick={() => console.log("opt 1")}>Option 1</button>
<button onClick={() => console.log("opt 1")}>Option 2</button>
</DropDown>
The DropDown component is one that I wrote, that renders this.props.children in a drop-down fashion. The DropDown has an onClick call that makes it close.
DropDown looks something like this (simplified):
class DropDown extends Component {
state = {
open: false
};
render() {
return (
<div className={`drop-down ${this.state.open ? "open" : "closed"}`}>
<div className="closed-version">
<div className="header" onClick={this.open}>
<div className="header-contents">Click here to select an option</div>
</div>
</div>
<div className="open-version">
<div className="content" onClick={this.closeWithoutSelection}>
{this.props.children}
</div>
</div>
</div>
);
}
open = () => {
this.setState({ open: true }, () => {
if (this.props.onOpen) {
this.props.onOpen();
}
});
};
closeWithoutSelection = () => {
this.setState({ open: false }, () => {
if (this.props.onCloseWithoutSelection) {
this.props.onCloseWithoutSelection();
}
});
};
}
The issue I'm running into is that I want to do something different to the DropDown whether it was closed selecting an option or not. How do I go about doing that?
In your DropDown component you have some state and you are probably rendering your children like this:
this.props.children
Instead you can use render props to pass state, methods or anything else down to your children without having to handle it outside of the DropDown component at the parent level.
class DropDown extends Component {
// constructor / state, methods...
yourSpecialMethod(item) {
// do your special thing here
}
render() {
// pass state or methods down to children!
return this.props.children(yourSpecialMethod)
}
}
Then modify your render slightly:
<DropDown>
{handleSpecialMethod =>
<>
<p>Select an option:</p>
<button onClick={() => handleSpecialMethod("opt 1")}>Option 1</button>
<button onClick={() => handleSpecialMethod("opt 1")}>Option 2</button>
</>
}
</DropDown>
UPDATED
Accordingly #pupeno (and he is right), the dropdown logic should be within the dropdown itself. However, we should pass a callback function in order to deal with the chosen data.
class DropDown extends React.Component {
state = {
open: false,
};
toggleDropdown = (e) => {
this.setState({
open: !this.state.open,
});
const value = e.target.getAttribute('value');
if ( value !== "null") {
this.props.selectItem(value);
}
};
render() {
const { selectItem } = this.props;
const { open } = this.state;
return (
<div className={open ? "drop-down open" : "drop-down"}>
<div onClick={this.toggleDropdown} value="null">Select</div>
<div onClick={this.toggleDropdown} value="1">Item 1</div>
<div onClick={this.toggleDropdown} value="2">Item 2</div>
<div onClick={this.toggleDropdown} value="3">Item 3</div>
</div>
);
}
};
class App extends React.Component {
requestItem = (item) => {
alert(`Request item ${item}`);
};
render() {
return (
<div>
<DropDown selectItem={this.requestItem}/>
</div>
);
}
}
ReactDOM.render(
<App />,
document.querySelector('#app')
);
.drop-down {
border: 1px solid black;
width: 180px;
height: 30px;
overflow: hidden;
transition: height 0.3s ease;
}
.open {
height: 120px;
}
.drop-down > div {
padding: 5px;
box-sizing: border-box;
height: 30px;
border-bottom: 1px solid black;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
You could use an onChange function and call the parent function that is passed down as a prop. This will "affect the parent" as you asked.
this.props.onChange() or something similar.

Remove class after adding / Restart CSS animation onClick in React

I'm using CSS transform to animate an AwesomeFont icon when I click it
.animate {
-webkit-animation-duration: 400ms;
-webkit-animation-name: animation
}
AFAIK, to trigger the animation I need to add the above class to an element after it's being rendered. So I do this in React
class Class extends Component {
state = {
likes: 5,
play: false
}
handleClick = () => {
this.setState((p) => { return {play: p.play ? false : true}; })
}
render() {
<a onClick={this.handleClick} href="#" />
<Icon className={this.state.play ? 'animate' : ''} name="thumbs-up" />
{this.state.likes}
</a>
}
}
which works pretty well if I'm just toggling the states with each click, but now I want to trigger the animation on every click
...
handleClick = () => {
this.setState((p) => { return {play: p.play ? false : true}; })
}
render() {
<a onClick={this.handleClick} onMouseUp={() => this.setState(()=>{return {play:false}; })} href="#" />
<Icon className={this.state.play ? 'animate' : ''} name="thumbs-up" />
{this.state.likes}
</a>
}
}
It's not pretty, but I just want to illustrate a point, and I don't think it is working as it should because clicking on the number part of the link repeatedly doesn't trigger the animation but clicking the Icon does.
I tried adding the class onMouseDown and removing it onMouseUp but React just batches up the setStates, resulting in nothing. Anyone with any ideas how should I approach this problem?
Please try to change a tag to div tag. The click and other mouse events work fine on div and it's child elements. See an example:
<div id='wrapper' onClick='wrapperClick()' onMouseUp='wrapperMouseUp()'>
This is parent
<div id='child'>
I am child
</div>
</div>
#wrapper {
width:100px;
height:100px;
border:1px solid black;
}
#child {
padding-top:10px;
border:1px solid red;
}
function wrapperClick() {
alert('you clicked me')
}
function wrapperMouseUp() {
alert('Mouse Up')
}
Just note that mouse up fires before click event.
handleMouseUp = () => {
setCounter()
}
handleClick = () => {
setCounter()
}
setCounter() {
this.setState((p) => { return {play: p.play ? false : true}; })
}
render() {
<div onClick={this.handleClick} onMouseUp={this.handleMouseUp} href="#" />
<Icon className={this.state.play ? 'animate' : ''} name="thumbs-up" />
{this.state.likes}
</div>
}
}

Categories

Resources