I have in React an accordion with buttons that slide up/down content, like on this image
I made some functional components and things look like this:
<SlideButtonContainer>
<SlideButton title="Title goes here">
Content goes here
</SlideButton>
<SlideButton title="Title goes here">
Content goes here
</SlideButton>
</SlideButtonContainer>
Clicking a SlideButton calls a function handleClick that changes the state opened of the component, making it slide its content up on false or down on true.
But how could I make all the other SlideButtons inside SlideButtonContainer set their state opened to false?
SlideButton uses SlideDown from this repo and is as follows:
export default function SlideButton(props) {
const [opened, setOpened] = useState(false);
function handleClick() {
setOpened(!opened);
}
return (
<div className="slideButton">
<div className="slideButtonButton" onClick={handleClick}>
<div>
{props.title}
</div>
</div>
<div className="slideButtonContents">
<SlideDown>
{opened ? props.children : null}
</SlideDown>
</div>
</div>
);
}
SlideButtonContainer is just a styled div
export default function SlideButtonContainer(props) {
return (
<div className="slideButtonContainer">
{ props.children }
</div>
);
}
Solved.
In SlideButtonContainer I use React.cloneElement on render and set refs to its children, and I can use children's function with this loop:
for (var ref in this.refs) {
this.refs[ref].slideUp();
}
Related
I have a child component <Welcome /> which I'd like to add a fade out transition using Material ui Fade component.
I want to be able to hide it on two conditions:
if the useEffect's timeout is reached
if the user clicks upon the a different element (Pano in this case)
I initially had my component controlling it's own state logic, but realized in order to make the second condition possible, I needed to place the conditional logic in the parent and pass the props down.
Code is:
function App() {
const [isVisible, setIsVisible] = useState(true);
useEffect(() => {
setTimeout(() => {
setIsVisible(false);
}, 8000);
}, []);
return (
<div>
<Fade in={isVisible} exit={!isVisible}>
<Welcome setIsVisible={isVisible} isVisible={isVisible} />
</Fade>
<Pano ... />
</div>
and the child component to fade in/out:
export const Welcome = (props) => {
const shown = props.isVisible
return shown ? (
<div className="wrapper">
<img src={...} alt="welcome instructions" />
<p>Click and drag to explore.</p>
<p>Select icons for more information.</p>
</div>
) : <div />;
};
Ended up swapping to Framer Motion for this. Realized I just wanted the exit animation since I already had a soft blinking to the component when it's loaded in which I could fade slower on the first keyframe. Framer's <AnimatePresence> worked just fine for this with a timeout.
I'm trying to implement a feature to close a menu component when clicking outside of it. To achieve this I check if current target is present in ref node. The problem is when I click on Icon component... Inspecting it, e.target happens to be img from Icon component, and if I search it on ref.current, it's not present... Is there a way to link parent and child nodes together to achieve this condition ref.current.contains(e.target) when i click on a child component?
Parent component:
function Menu ({showMenu, close}) {
const ref = useRef(null)
useEffect(() => {
document.addEventListener('click', handleClickOutside)
return () =>{
document.removeEventListener('click', handleClickOutside)
}
}, [])
function handleClickOutside(e) {
if (ref.current && !ref.current.contains(e.target) && showMenu) {
close()
}
}
return (
<div ref={ref}>
<Icon action={openMenu2}/>
<h1>Menu</h1>
</div>
)
}
Child
function Icon ({action}) {
return (
<div onClick={() => action()}>
<i>
<img src={imageSrc} alt="icon"></img>
</i>
</div>
)
}
if u just want to use child Component's ref , u can pass ref to props.action
//Child
const cRef = useRef(null)
function Icon ({action}) {
return (
<div ref= onClick={() => action(cRef)}>
<i>
<img src={imageSrc} alt="icon"></img>
</i>
</div>
)
}
then u can use it in parent Component
or u can move those logic to child component
To achieve this I check if current target is present in ref node. The problem is when I click on Icon component... Inspecting it, e.target happens to be img from Icon component, and if I search it on ref.current, it's not present...
You can pass menu toggle function directly to down children to children,
Toggle Function
With this approach you don't need to check if showMenu is true because
this function will close the menu if it is open, and it will open if it is close
Assuming your state is at the parent of Menu component
const toggleMenu = () => {
setMenuOpen(!menuOpen);
};
Then in Menu component pass menuOpen as menu's state and toggleMenu as function to change it.
function Menu({ toggleMenu, menuOpen }) {
return (
<div style={{ display: "flex", alignItems: "center" }}>
<Icon toggleMenu={toggleMenu} />
<h1>Menu : {`${menuOpen ? "open" : "closed"} in Menu component`}</h1>
</div>
);
}
And in Icon component you can toggle the menu
function Icon({ toggleMenu }) {
return (
<div onClick={toggleMenu}>
<img
style={{ width: 35, cursor: "pointer" }}
alt="hamburger-menu"
src="https://cdn4.iconfinder.com/data/icons/wirecons-free-vector-icons/32/menu-alt-512.png"
/>
</div>
);
}
export default Icon;
In action on codesandbox :https://codesandbox.io/s/eager-joliot-zb3t7?file=/src/Menu.jsx:54-299
If your app is getting complicated with passing states and passing state change functions I recommend you to check redux pattern.
Here link for redux : https://react-redux.js.org/
I am trying to make a modal reusable:
this is my component:
class OverleyModal extends Component {
constructor(props) {
super(props);
}
openModal = () => {
document.getElementById("myOverlay").style.display = "block";
}
closeModal = () => {
document.getElementById("myOverlay").style.display = "none";
}
render() {
return (
<React.Fragment>
<div id="myOverlay" className="overlay">
<div className="overlay-content">
<p>content goes there</p>
</div>
</div>
</React.Fragment>
)
}
}
export default OverleyModal;
The above component is working great for the purpose of modal, that is why i didn't include here CSS/style, the issue not about CSS.
I want, when i mount this component on any compoenet like thise below:
<overleyModal open={true} />
if open=true, the modal will be visiable
<overleyModal open={false} />
and if open={false}
the modal will disappear
You can see how i deal open and close modal in the coponent method openModal() and closeModal()
But i am going through the trouble to make it reliable, I just want to use open as props, nothing else. if open is true, it will appear and if false, it will disappear.
Can anyone please help me in this case?
You need to make use of props and control the opening and closing through it by conditionally rendering it. You can also make the content generic by passing it as props too
class OverlayModal extends Component {
render() {
const { open, content } = this.props
return open? <React.Fragment>
<div id="myOverlay" className="overlay">
<div className="overlay-content">
<p>{content}</p>
</div>
</div>
</React.Fragment>: null
}
}
export default OverlayModal;
and use it like
<OverlayModal open={true} content={content goes there'} />
or even better you can define the content as children to give you more styling options
class OverlayModal extends Component {
render() {
const { open, children} = this.props
return open? <React.Fragment>
<div id="myOverlay" className="overlay">
<div className="overlay-content">
{children}
</div>
</div>
</React.Fragment>: null
}
}
export default OverlayModal;
and using as
<OverlayModal open={true} ><p>content goes there</p></OverlayModal >
open can be a property value and modal can be rendered conditionally based on the prop value. There is no need to directly access dom element.
return props.open ? (
<div id="myOverlay" className="overlay">
<div className="overlay-content">
<p>content goes there</p>
</div>
</div>
) : null;
I have a React js App (No JQuery please) drop down menu that I trigger when the ▼ character is clicked. With my code below, the dropdown menu disappears only when the same ▼ is clicked again. I want to make it disappear when the user clicks anywhere else on the page. How to change the code below to make the dropdown menu disappear when the user clicks anywhere else on the page as well and not just by clicking the same icon ▼ ?
Toggler Icon:
<span className="show_more" onClick={this.toggleOptions}>
<MaterialIcon icon="keyboard_arrow_down" />
</span>
Code for toggling: (used by many components so can the fix be only here ?)
import React, { Component } from 'react'
...
import MaterialIcon from '../icons/material-icon'
import HeaderLogo from './logo'
export default class Header extends Component {
state = {
showOptions: false,
}
toggleOptions = () => this.setState({ showOptions: !this.state.showOptions })
render() {
let { showOptions } = this.state
return (
<div className="header">
<div className="row-container container">
<div className="col-l">
<HeaderLogo />
</div>
<div className="col-m">
<Search />
</div>
<div className="col-r">
<div className="header_right">
<HeaderTopLinks />
<span className="show_more" onClick={this.toggleOptions}>
<MaterialIcon icon="keyboard_arrow_down" />
</span>
</div>
{showOptions ? (
<HeaderOptions toggleOptions={this.toggleOptions} />
) : null}
</div>
</div>
</div>
)
}
}
The answer can be found here
But to sum it up, you have to listen for clicks on the document, and write a function that will walk the tree and tell you if the click occurred inside or outside your component
Here are the important bits from that link to add to your component:
handleClickOutside(event) {
if (this.wrapperRef && !this.wrapperRef.contains(event.target)) {
alert('You clicked outside of me!');
this.setState({ showOptions: false });
}
}
componentDidMount() {
document.addEventListener('mousedown', this.handleClickOutside);
}
componentWillUnmount() {
document.removeEventListener('mousedown', this.handleClickOutside);
}
render() {
let { showOptions } = this.state;
return <div className="header" ref={(node) => this.setWrapperRef = node}>...all the rest of the component goes here...</div>;
}
For the record, there are many ways of accomplishing this, this is just one approach.
I am working on implementing "invite someone" functionality on a webapp. I have a "invite" button. Now I want that whenever someone clicks on it, a popup should apear on screen asking for an email id as input, which I can use to send an invite link. So I am not sure how to have that popup with an input field.
Apologies if someone find this question too basic. I am pretty new to react.js
You can handle the open and close state of your popup with the component state. You can either use css or Javascript to show or hide the popup.
If you use CSS you need to pass a css class to the popup like so:
class App extends Component {
state = {
open: false
}
handlePopUp(){
this.setState({open: !this.state.open})
}
render() {
return (
<div className="App">
<button onClick={() => this.handlePopUp()}>Open / close</button>
<div className={`modal ${this.state.oepn}`}>
{/* form content here */}
</div>
</div>
);
}
}
If you are using Javascript you will need to have a conditional like so:
class App extends Component {
state = {
open: false
}
handlePopUp(){
this.setState({open: !this.state.open})
}
render() {
return (
<div className="App">
<button onClick={() => this.handlePopUp()}>Open / close</button>
{
this.state.oepn ?
<div className="modal">
{/* form content here */}
</div>
: null
}
</div>
);
}
}
export default App;
I would recommend handling it with CSS so you can do some nice css transitions