Rendering different images in a modal based on what button is clicked - javascript

I would like to change the content inside a modal based on what button is clicked on.
Depending on the button clicked, a different array of images will be provided to render new <img> tags in the modal.
From my understanding the useState hook should be used here along with map to render each image from the provided image array.
Below is a simplified version of my current code.
const ModalGallery = () => {
...
const [ modalContent, setModalContent ] = useState([]);
const loadImages = imageArray => {
// code that uses map and setModalContent ?
};
return (
<>
{projects.map(project => {
return (
<Button
onClick={() => {
// call a funtion that changes the images rendered in the modal
loadImages(project.images);
}}
>
Display
</Button>
);
})}
<Modal>
{modalContent}
</Modal>
</>
);
};
export default ModalGallery;

You should maintain image urls in modalContent, then on click, change modalContent with image url's of corresponding project, which cause the Modal element to get updated.
const ModalGallery = () => {
...
const [modalContent, setModalContent] = useState([]);
const [showModal, setShowModal] = useState(false)
const modal = () => {
return(
<Model>
{
modalContent.map((img_url) =>{
return(<img src={img_url} key={img_url} />)
})
}
</Model>
)
}
return (
<>
{projects.map(project => {
return (
<Button
onClick={() => {
// call a funtion that changes the images rendered in the modal
setModalContent(project.images);
setShowModal(true)
}}
>
Display
</Button>
);
})}
{showModal ? modal : null}
</>
);
};

Related

How can i do to wrap different component with the same modal

I'm trying to wrap a modal in a different component but only the first showed up.
So I searched for a way to code my modal so I can accept different component open and close the right component instead of open the first component even if i click in another button
{open && <Modal>
<TeamForm />
</Modal>}
{openSavecard && <Modal>
<HikingSaveCards items={cartItems}/>
</Modal>}
// only the first one will display
const Modal = ({children}: Props) => {
const ContentRef = useRef<HTMLDivElement>(null)
const [open,setOpen] = useState(true)
useEffect(() => {
if(!open) return ;
function listener(e:any) {
if (ContentRef.current?.contains(e.target)) return;
setOpen(prev => !prev)
}
window.addEventListener('click', listener)
return () =>{ window.removeEventListener('click', listener)};
}, [open])
return open ? createPortal(
<>
<Background >
<Content ref={ContentRef}>
{children}
</Content>
</Background>
</>, document.querySelector('#portal')!) : null
};
export default Modal

How can I prevent the onmouseleave event from being triggered outside the browser?

There is a basket. When i enter the mouse, the dropdown opens and shows the items in the basket. When i leave the mouse, the dropdown closes. Inside the dropdown, i have a button for each item to remove items from the basket. When we click the button, a modal pops up, and we approve or reject. When this modal pops up, if we move the mouse away from the browser, onMouseEnter event is triggering and and causing the modal to close. How can i prevent this? I want the modal to close only when I click the backdrop or the reject button.
I passed hideDropdown function to child component(BasketItem) and tried to update isModalShow and dropdown state the same handleClick function. The dropdown closed, but the modal didn't pop up. How can i solve this? Can you help?
Basket component:
const Basket = () => {
const [isDropdownShow, setIsDropdownShow] = useState(false);
const { basket } = useAppContext();
const showDropdown = (e) => {
setIsDropdownShow(true);
};
const hideDropdown = (e) => {
setIsDropdownShow(false);
};
return (
<Wrapper
onMouseEnter={showDropdown}
onMouseLeave={hideDropdown}
isDropdownShow={isDropdownShow}
>
Basket
<span className='basket-length'>{basket.length}</span>
{isDropdownShow && <div className='white-border'></div>}
{basket.length === 0 && isDropdownShow && (
<div className='dropdown empty-basket'>Basket is empty</div>
)}
{isDropdownShow && (
<div className='dropdown'>
<ul>
{basket
.map((basketItem, index) => {
return (
<BasketItem
key={index}
basketItem={basketItem}
hideDropdown={hideDropdown}
/>
);
})
.sort(sortBasket)}
</ul>
</div>
)}
</Wrapper>
);
};
export default Basket;
BasketItem Component:
const BasketItem = ({ basketItem, hideDropdown }) => {
const [isModalShow, setIsModalShow] = useState(false);
const handleClick = () => {
hideDropdown();
setIsModalShow(true); //does not trigger
};
return (
<Wrapper>
<img src={basketItem.image} alt='product-item' />
<div>
<p className='basket-description'>{basketItem.description}</p>
<span className='basket-remove' onClick={handleClick}>
Remove
</span>
<Modal
isModalShow={isModalShow}
onClose={() => setIsModalShow(false)}
/>
</div>
</Wrapper>
);
};
export default BasketItem;
Modal:
const Modal = ({ isModalShow, onClose }) => {
if (!isModalShow) return null;
return ReactDom.createPortal(
<>
<Backdrop />
<Wrapper>
<button onClick={onClose}>CloseModal</button>
some content
</Wrapper>
</>,
document.getElementById('overlays')
);
};
export default Modal;

How to render a button in parent component based on children action in react js?

I have a list of movie cards when a user clicks on them, they become selected and the id of each movie card is transferred to an array named "selectedList".
I want to add a "let's go" button below the movie card but conditionally.
I mean when the array is empty the button should not be displayed and when the user clicked on at least a movie the button displays. the array should be checked each time and whenever it becomes equal to zero the button should disappear.
the thing is all the movie cards are the children of this page and I want to render the parent component based on children's behavior.
MY MAIN PAGE:
export default function Index(data) {
const info = data.data.body.result;
const selectedList = [];
return (
<>
<main className={parentstyle.main_container}>
<NavBar />
<div className={style.searchbar_container}>
<SearchBar />
</div>
<div className={style.card_container}>
{info.map((movie, i) => {
return (
<MovieCard
movieName={movie.name}
key={i}
movieId={movie._id}
selected={selectedList}
isSelected={false}
/>
);
})}
</div>
</main>
<button className={style.done}>Let's go!</button>
</>
);
}
**MOVIE CARD COMPONENT:**
export default function Index({ selected, movieName, movieId, visibility }) {
const [isActive, setActive] = useState(false);
const toggleClass = () => {
setActive(!isActive);
};
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
selected.splice(selected.indexOf(e.target.id), 1);
console.log(selected);
} else {
selected.push(e.target.id);
console.log(selected);
}
toggleClass();
};
return (
<div>
<img
className={isActive ? style.movie_selected : style.movie}
src={`images/movies/${movieName}.jpg`}
alt={movieName}
id={movieId}
onClick={pushToSelected}
/>
<h3 className={style.title}>{movieName}</h3>
</div>
);
}
You can use conditional rendering for that:
{selectedList.length > 0 && <button className={style.done}>Let's go!</button>}
Plus, you should change your selectedList to a state, and manage the update via the setSelectedList function:
import { useState } from 'react';
export default function Index(data) {
const info = data.data.body.result;
const [selectedList, setSelectedList] = useState([]);
Add the method to the MovieCardĀ as a property:
<MovieCard
movieName={movie.name}
key={i}
movieId={movie._id}
selected={selectedList}
setSelected={setSelectedList}
isSelected={false}
/>;
And update the list in the pushToSelected method:
export default function MovieCard({
selected,
setSelected,
movieName,
movieId,
visibility
}) {
const pushToSelected = (e) => {
if (selected.includes(e.target.id)) {
selected.splice(selected.indexOf(e.target.id), 1);
console.log(selected);
} else {
selected.push(e.target.id);
console.log(selected);
}
setSelected([...selected]);
toggleClass();
};

How to refactor an if else if with previous state when using useState Hook?

I have 2 details tag, each has a control to toggle it on/off. Code snippet here. Clicking Control A should toggle on/off page A, clicking Control B should toggle on/off page B.
I did it with an if else if plus 2 useState, this would not be feasible when there are multiple details. How can I refactor the code such that maybe the if else if can be avoided and it detects which Control I click in a cleverer way?
Page.js
const Page = ({ name, isOpen, setIsOpen }) => {
return (
<>
<details
open={isOpen}
onToggle={(e) => {
setIsOpen(e.target.open);
}}
>
<summary>Page {name} title</summary>
<div>Page {name} contents</div>
</details>
</>
);
};
export default Page;
Control.js
const Control = ({ toggle }) => {
return (
<>
<a onClick={() => toggle("A")} href="#/">
Control A
</a>
<br />
<a onClick={() => toggle("B")} href="#/">
Control B
</a>
</>
);
};
App.js
export default function App() {
const [isOpenA, setIsOpenA] = useState(false);
const [isOpenB, setIsOpenB] = useState(false);
const toggle = (name) => {
if (name === "A") {
setIsOpenA((prevState) => !prevState);
} else if (name === "B") {
setIsOpenB((prevState) => !prevState);
}
};
return (
<div className="App">
<Control toggle={toggle} />
<Page name={"A"} isOpen={isOpenA} setIsOpen={setIsOpenA} />
<Page name={"B"} isOpen={isOpenB} setIsOpen={setIsOpenB} />
</div>
);
}
You can use an array to represent open ones
const [openPages, setOpenPages] = useState([])
And to toggle filter the array
const toggle = (name) => {
if(openPages.includes(name)){
setOpenPages(openPages.filter(o=>o!=name))
}else{
setOpenPages(pages=>{ return [...pages,name]}
}
}
I would personally use an object as a map for your toggles as in something like:
const [isOpen, _setIsOpen] = useState({});
const setIsOpen = (pageName,value) => _setIsOpen({
...isOpen,
[pageName]: value
});
const toggle = (name) => setIsOpen(name, !isOpen[name]);
and then in the template part:
<Page name={"A"} isOpen={isOpen["A"]} setIsOpen={toggle("A")} />
In this way you can have as many toggles you want and use them in any way you want
I think this would be quite cleaner, also you should put the various page names in an array and iterate over them as in
const pageNames = ["A","B"];
{
pageNames.map( name =>
<Page name={name} isOpen={isOpen[name]} setIsOpen={toggle(name)} />)
}
At least that's how I would go about it
Adithya's answer worked for me.
For future reference, I put the full working code here. The onToggle attribute in Page.js is not needed. All required is passing correct true/false to open={isOpen} in Page.js.
App.js:
export default function App() {
const [openPages, setOpenPages] = useState([]);
const toggle = (name) => {
if (openPages.includes(name)) {
setOpenPages(openPages.filter((o) => o !== name));
} else {
setOpenPages((pages) => {
return [...pages, name];
});
}
};
return (
<div className="App">
<Control toggle={toggle} />
<Page name={"A"} isOpen={openPages.includes("A")} />
<Page name={"B"} isOpen={openPages.includes("B")} />
<Page name={"C"} isOpen={openPages.includes("C")} />
</div>
);
}
Page.js
const Page = ({ name, isOpen }) => {
return (
<>
<details open={isOpen}>
<summary>Page {name} title</summary>
<div>Page {name} contents</div>
</details>
</>
);
};
Control.js remains the same.

How to pass HTML attributes to child component in React?

I have a parent and a child component, child component has a button, which I'd like to disable it after the first click. This answer works for me in child component. However the function executed on click now exists in parent component, how could I pass the attribute down to the child component? I tried the following and it didn't work.
Parent:
const Home = () => {
let btnRef = useRef();
const handleBtnClick = () => {
if (btnRef.current) {
btnRef.current.setAttribute("disabled", "disabled");
}
}
return (
<>
<Card btnRef={btnRef} handleBtnClick={handleBtnClick} />
</>
)
}
Child:
const Card = ({btnRef, handleBtnClick}) => {
return (
<div>
<button ref={btnRef} onClick={handleBtnClick}>Click me</button>
</div>
)
}
In general, refs should be used only as a last resort in React. React is declarative by nature, so instead of the parent "making" the child disabled (which is what you are doing with the ref) it should just "say" that the child should be disabled (example below):
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({isDisabled, onButtonClick}) => {
return (
<div>
<button disabled={isDisabled} onClick={onButtonClick}>Click me</button>
</div>
)
}
Actually it works if you fix the typo in prop of Card component. Just rename hadnlBtnClick to handleBtnClick
You don't need to mention each prop/attribute by name as you can use javascript Object Destructuring here.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = (props) => {
return (
<div>
<button {...props}>Click me</button>
</div>
)
}
You can also select a few props and use them differently in the child components. for example, see the text prop below.
const Home = () => {
const [isButtonDisabled, setIsButtonDisabled] = useState(false)
const handleButtonClick = () => {
setIsButtonDisabled(true)
}
return (
<>
<Card text="I'm a Card" isDisabled={isButtonDisabled} onButtonClick={handleButtonClick} />
</>
)
}
const Card = ({text, ...restProps}) => {
return (
<div>
<button {...restProps}>{text}</button>
</div>
)
}

Categories

Resources