React button onClick requires two click to work second time - reactjs

When the img is pressed the side menu opens fine but when i close the side menu and try to open it again by pressing the img it requires two clicks to open. how do i make it so it works with one click even after i open and close the side menu?
const Header = () => {
let [isOpen, setIsOpen] = useState(false);
return (
<header className='homepage-header'>
<img src={menusvg} alt='' onClick={() => setIsOpen((isOpen) => !isOpen)} />
{
isOpen ? <SideMenu /> : null
}
<h1>Main Header</h1>
</header>
)
}
note: i have another button inside the SideMenu componenet that closes the menu.
SideMenu:
function SideMenu() {
let [isOpen, setIsOpen] = useState(true);
return (
<>
{
isOpen ? <div className='side-menu'>
<p>side menue</p>
<i class="fas fa-times" onClick={() => setIsOpen((isOpen) => !isOpen)}></i>
</div>
: null
}
</>
)
}

You are using two different states, pass isOpen and setIsOpen as props to SideMenu
Try this code
function SideMenu({ isOpen, setIsOpen }) {
return (
<>
{isOpen ? (
<div className="side-menu">
<p>side menue</p>
<button onClick={() => setIsOpen(isOpen => !isOpen)}>Close</button>
</div>
) : null}
</>
);
}
--
export default function App() {
let [isOpen, setIsOpen] = React.useState(false);
return (
<header className="homepage-header">
<img
src={'https://picsum.photos/200'}
alt=""
onClick={() => setIsOpen(isOpen => !isOpen)}
/>
{isOpen ? <SideMenu isOpen={isOpen} setIsOpen={setIsOpen} /> : null}
<h1>Main Header</h1>
</header>
);
}
Sample Code: https://stackblitz.com/edit/react-j4rxot?file=src%2FApp.js

The logic you have used to make isOpen true and false is bit wierd,(at least for me), although feels correct.
A simple way would be,
const Header = () => {
let [isOpen, setIsOpen] = useState(false);
return (
<header className='homepage-header'>
<img src={menusvg} alt='' onClick={() => isOpen ? setIsOpen(false) : setIsOpen(true)} />
{
isOpen ? <SideMenu /> : null
}
<h1>Main Header</h1>
</header>
)
}
Hope it works!

Related

How can I only open one popup at a time based on a map function in react?

I am making a movie dashboard where users can search for movies and save them to their watch list. I am trying to have a popup containing the movie information that will appear if the user clicks on the image card for each movie. This is kind of working, but the problem is that the popup shows for every movie when I only click on one. My code is shown before. I haven't styled the popup yet, but the information looks right for each movie. Thanks for your help!
function Watchlist() {
const { watchlist } = useContext(GlobalContext);
const [modal, setModal] = useState(false);
const toggleModal = () => {
setModal(!modal);
}
return (
<div className='watchlist'>
<h1 className='title'>your watchlist</h1>
{watchlist.length > 0 ? (
<div className="movies">
{watchlist.map((movie, index) => {
if(index % 3 === 1) {
return (
<div className="movie-wrapper">
<button name={movie.id} className="open-modal" onClick={toggleModal} key={movie.id}>
<Card img={`https://image.tmdb.org/t/p/w200${movie.poster_path}`} margin={30} key={movie.id}/>
</button>
{modal && (
<div className="movie-details">
<div className="modal-content">
<ResultCard movie={movie} key={movie.id}/>
<button className="close-modal" onClick={toggleModal}>X</button>
</div>
</div>
)}
</div>
)
} else {
<div className="movie-wrapper">
<button name={movie.id} className="open-modal" onClick={toggleModal} key={movie.id}>
<Card img={`https://image.tmdb.org/t/p/w200${movie.poster_path}`} margin={0} key={movie.id}/>
</button>
{modal && (
<div className="movie-details">
<div className="modal-content">
<ResultCard movie={movie} key={movie.id}/>
<button className="close-modal" onClick={toggleModal}>X</button>
</div>
</div>
)}
</div>
}
})}
</div>
) : (
<h2 className="no-movies">Add some movies!</h2>
)}
</div>
)
}
You need to keep the movie id you want to show the modal for:
const [modal, setModal] = useState(false);
const [selectedMovie, setSelectedMovie] = useState();
const toggleModal = (movieId) => {
setModal(!modal);
setSelectedMovie(movieId)
}
onClick={() => toggleModal(movie.id)}
and check the selected movie when rendering:
{modal && movie.id === selectedMovie && (...
It's because you are rendering a modal for each movie in your list.
Take the modal out of the map function, and keep it as a child of the component where it does not rely on any condition, any mapped list, or anything else for it to be rendered. It's kind of a convention (at least among the codebases I've come across) to put modals at the end of the component JSX, as a direct child of the root element of the component.
Hide it by default, and only show it when a user clicks on a movie, passing the relevant info into the modal.
It might look something like this:
function Watchlist() {
const { watchlist } = useContext(GlobalContext);
const [modal, setModal] = useState(false);
const [selectedMovie, setSelectedMovie] = useState({});
const toggleModal = () => {
setModal(!modal);
}
return (
<div className='watchlist'>
<h1 className='title'>your watchlist</h1>
{watchlist.length > 0 ? (
<div className="movies">
{watchlist.map((movie, index) => {
// Render the mapped list of JSX elements (movie cards?)
// Potentially the `onClick` event would trigger the `setSelectedMovie` function in addition to setting `toggleModal`
})}
</div>
) : (
<h2 className="no-movies">Add some movies!</h2>
)}
{modal && (
<div className="movie-details">
<div className="modal-content">
<ResultCard movie={selectedMovie} key={selectedMovie.id}/>
<button className="close-modal" onClick={toggleModal}>X</button>
</div>
</div>
)}
</div>
)
}

How to create a tabs-like menu but then slightly different in React app

I have multiple pages in my React app where I want to have this functionality. So in a certain React page I add CopyToClipboard and the children TabIcon:
<CopyToClipboard>
<TabIcon type="link" title="Test one" />
<TabIcon type="embed" title="Test two" />
</CopyToClipboard>
In another page I maybe have only:
<CopyToClipboard>
<TabIcon type="link" title="Test one" />
</CopyToClipboard>
The TabIcon component:
const TabIcon = ({ title, type, onClick }: Props) => {
const theme = useTheme();
const styles = Styles({ theme });
return (
<li>
<ButtonBase type="button" onClick={onClick} title={title} disableRipple>
<span css={styles.icon}>{type === 'embed' ? <EmbedIcon /> : <LinkIcon />}</span>
</ButtonBase>
</li>
);
};
The CopyToClipboard component:
const CopyToClipboard = ({ children }: Props) => {
const [isOpen, setIsOpen] = useState(false);
const [isCopied, setIsCopied] = useState(false);
const stringToCopy =
type === 'link'
? `https://www.mysite${path}`
: `<iframe>// iframe code etc here</iframe>`;
const handleClick = () => {
setIsOpen((current) => !current);
};
return (
<div>
<ul>
{children.map((item) => (
<TabIcon type={item.type} onClick={handleClick} />
))}
</ul>
{isOpen && (
<div>
<p>{stringToCopy}</p>
<ButtonBase type="button" onClick={handleCopyClick} disableRipple>
<span>{isCopied ? `${suffix} copied` : `Copy ${suffix}`}</span>
<span>{isCopied ? <CheckIcon /> : <CopyIcon />}</span>
</ButtonBase>
</div>
)}
</div>
);
};
export default CopyToClipboard;
So what I like to achieve is the following:
When clicking a button it toggles/shows the correct div with its associated content. When clicking the same button again it hides the div. When clicking the other button it shows the associated content of that button. When you click that button again it hides the div.
How to achieve this within my example?
You need a single state (text). If the text has a truthy value (actual text in this case), the div is displayed.
When you set the text, the handleClick compares the prevText and the new text (txt), and if they are equal it sets null as the value of text, which hides the div. If they are not equal, the new text is set, and the div is displayed.
const { useState } = React;
const Demo = () => {
const [text, setText] = useState(null);
const handleClick = txt => {
setText(prevText => prevText === txt ? null : txt);
};
return (
<div>
<ul>
<li>
<button onClick={() => handleClick('Button 1 text')}>
Button 1
</button>
</li>
<li>
<button onClick={() => handleClick('Button 2 text')}>
Button 2
</button>
</li>
</ul>
{text && (
<div>
<p>{text}</p>
</div>
)}
</div>
);
}
ReactDOM.createRoot(root)
.render(<Demo />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>

How to toggle Modal and pass/change data

I am new to reacting and having trouble understanding how to pass data meta into each modal when an image is clicked and update the modal with the clicked data info. Following in my bare minimum code for sake of example
app.js
<div className="movie">
<Modal >hello world/*making sure the static text is passed into as children prop*/</Modal>
{movies.length > 0 &&
movies.map((data) => {
return <Library key={data.id} {...data} searchTerm={searchTerm} />;
})}
</div>
modal.jsx
export default function Index({children}) {
const [isOpen, setIsOpen] = useState(true)
return (
isOpen && (
<div className='modalContainer'>
<div className="modal">
<div className="close">
<button onClick={()=>{
setIsOpen(false)
}}>close</button>
</div>
{children}
</div>
</div>
)
)
}
Library.jsx
import "./Library.scss";
import {Link} from "react-router-dom";
const IMG_API = "http://image.tmdb.org/t/p/w1280";
const Library = ({
title,
poster_path,
release_date,
}) => {
return (
<div>
<Link to="/modal">
<img src={IMG_API + poster_path} alt={title} />
</Link>
<div className="meta">
<h5>{title}</h5>
<p>{release_date.slice(0, 4)}</p>
</div>
</div>
);
};
export default Library;
You should declare the isOpen state in the upper-level component so you can actually open the modal on some kind of event.
Also, you should declare a props where to pass the actual text to the Modal component:
const [isOpen, setIsOpen] = useState(false)
const handleOpen = () => setIsOpen(true);
<div className='movie'>
<Modal isOpen={isOpen} setIsOpen={setIsOpen} text='Hello, world' />
{movies.length > 0 &&
movies.map((data) => {
return <Library key={data.id} {...data} searchTerm={searchTerm} />;
})}
<button type='button' onClick={() => handleOpen()}>Open modal</button>
</div>;
You should then change your Modal declaration as:
export default function Index({ isOpen, setIsOpen, text }) {
return (
isOpen && (
<div className='modalContainer'>
<div className="modal">
<div className="close">
<button onClick={()=>{
setIsOpen(false)
}}>close</button>
</div>
{text}
</div>
</div>
)
)
}

React <details> - have only one open at a time

I have a component with several elements. I'm trying to figure out how to update the code with hooks so that only one element will be open at a time - when a element is open, the other's should be closed. This is the code:
const HowItWorks = ({ content, libraries }) => {
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
const [open, setOpen] = useState(false);
const onToggle = () => {
setOpen(!open);
};
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${open ? "open" : "closed"}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{open ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};
Instead of having the open state be a boolean, make it be the ID of the element that is open. Then you can have a function that returns if the element is open by comparing the state with the ID.
const HowItWorks = ({ content, libraries }) => {
const [open, setOpen] = useState(0); //Use the element ID to check which one is open
const onToggle = (id) => {
setOpen(id);
};
const isOpen = (id) => {
return id === open ? "open" : "closed";
}
const Html2React = libraries.html2react.Component;
return (
<HowItWorksContainer>
{content.fields.map((tab, i) => {
return (
<details
key={i}
onToggle={onToggle}
className={`tab ${isOpen(i)}`}
>
<summary className="tab__heading">
<div className="wrapper">
<p>{tab.heading}</p>
{!!isOpen(i) ? (
<i className="icon kap-arrow-minus" />
) : (
<i className="icon kap-arrow-plus" />
)}
</div>
</summary>
<div className="tab__content">
<Html2React html={tab.content} />
</div>
</details>
);
})}
</HowItWorksContainer>
);
};

React.js - Disable background scroll when a modal is showing

I am trying to disable the background body scroll when a modal pop up window is open. I created the Modal using React portal. The only way I can think of doing this is by giving the body an overflow-y property when the Modal is open, but am unsure how to execute this..
my Modal.js looks like this
export default function Modal({open, onClose, image, title, description}){
if (!open) return null
return ReactDom.createPortal(
<>
<div id="overlay" />
<div id="modal" >
<button title={title} onClick={onClose}>close</button>
<h3>{title}</h3>
<img src={image} className="modal-img"/>
{description}
</div>
</>,
document.getElementById('portal')
)
}
and the component where I am 'rendering' the model
const ProjectCardUI = (props)=>{
const[isOpen, setIsOpen] = useState(false)
function openModal() {
setIsOpen(true)
// can I set the styling for '.body' or 'html' here?
}
return(
<div className="card text-center container-fluid d-flex">
<a className="modal-click" onClick ={openModal}>this is a link</a>
<Modal
open={isOpen}
onClose={() => setIsOpen(false)}
title={props.cardName}
image={props.imgsrc}
description={props.cardDescription}>
</Modal>
</div>
)
};
any help would be great.
You can directly manipulate the body style via the document object.
For example:
import React from "react";
import ReactDOM from "react-dom";
export default function Test() {
const setHidden = () => {
console.log(document.body.style.overflow);
if (document.body.style.overflow !== "hidden") {
document.body.style.overflow = "hidden";
} else {
document.body.style.overflow = "scroll";
}
};
return (
<div>
<button onClick={setHidden}>Click me! </button>
<h1>1</h1>
<h1>2</h1>
<h1>3</h1>
<h1>4</h1>
<h1>5</h1>
<h1>6</h1>
<h1>7</h1>
<h1>8</h1>
<h1>9</h1>
<h1>10</h1>
<h1>11</h1>
<h1>12</h1>
<h1>13</h1>
<h1>14</h1>
<h1>15</h1>
<h1>16</h1>
</div>
);
}
ReactDOM.render(<Test />, document.getElementById("container"));
Maybe something like this:
const ProjectCardUI = (props) => {
const [isOpen, setIsOpen] = useState(false);
function openModal() {
setIsOpen(true);
// can I set the styling for '.body' or 'html' here?
}
useEffect(() => {
const body = document.querySelector('body');
body.style.overflow = isOpen ? 'hidden' : 'auto';
}, [isOpen])
return (
<div className="card text-center container-fluid d-flex">
<a className="modal-click" onClick={openModal}>
this is a link
</a>
<Modal
open={isOpen}
onClose={() => setIsOpen(false)}
title={props.cardName}
image={props.imgsrc}
description={props.cardDescription}
></Modal>
</div>
);
};

Resources