hiding and showing array of images - javascript

So I have an array of images, which I would like to hide or show on a click of a button.
right now when I try to hide the image, it will hide the entire array.
import "./main.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import React, { useEffect, useState } from "react";
import {
faCircleChevronLeft,
faCircleChevronRight,
faCircleXmark,
} from "#fortawesome/free-solid-svg-icons";
const Main = ({ galleryImages }) => {
const [slideNumber, setSlideNumber] = useState(0);
const [openModal, setOpenModal] = useState(false);
const [pics, setPics] = useState([]);
const [show, toggleShow] = useState(true);
// buttons next to name of diff charts (hide/show chart)
const handleOpenModal = (index) => {
setSlideNumber(index);
setOpenModal(true);
};
const removeImage = (id) => {
setPics((oldState) => oldState.filter((item) => item.id !== id));
};
// const hide = () => {
// setShow(false)
// }
const handleCloseModal = () => {
setOpenModal(false)
}
useEffect(()=> {
setPics(galleryImages)
},[]);
return (
<div>
<button onClick={() => toggleShow(!show)}>toggle: {show ? 'show' : 'hide'}</button>
{show &&
<div>
{pics.map((pic) => {
return (
<div style = {{marginBottom:'100px'}}>
{pic.id}
<img
src={pic.img}
width='500px'
height='500px'
/>
<button onClick ={() => removeImage(pic.id)}>Delete</button>
</div>
)
})}
</div>
I tried making a state component to try to hide and show the images, however it will hide the entire array instead of the individual image

i would add a show var to the galleryImages array and then set it so you get control of each image like this
import { useState } from "react";
import { v4 as uuidv4 } from "uuid";
import "./main.css";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import React, { useEffect, useState } from "react";
import {
faCircleChevronLeft,
faCircleChevronRight,
faCircleXmark,
} from "#fortawesome/free-solid-svg-icons";
function Main({ galleryImages }) {
const [slideNumber, setSlideNumber] = useState(0);
const [openModal, setOpenModal] = useState(false);
const [pics, setPics] = useState([]);
// buttons next to name of diff charts (hide/show chart)
const toggleShow = ({ id, show }) => {
setPics((oldState) =>
oldState.map((item) => {
if (item.id !== id) return item;
return { ...item, show: !show };
})
);
};
const removeImage = (id) => {
setPics((oldState) => oldState.filter((item) => item.id !== id));
};
useEffect(() => {
setPics(
galleryImages.map((galleryImage) => {
return { ...galleryImage, show: true };
})
);
}, []);
return (
<div>
<div>
{pics.map((pic) => {
return (
<>
<button
onClick={() => toggleShow({ show: pic.show, id: pic.id })}
>
toggle: {pic.show ? "show" : "hide"}
</button>
{pic.show && (
<div style={{ marginBottom: "100px" }}>
{pic.id}
<img src={pic.img} width="500px" height="500px" />
<button onClick={() => removeImage(pic.id)}>Delete</button>
</div>
)}
</>
);
})}
</div>
</div>
);
}
export default Main;
`

If you would like the option to hide individual pics, to accomplish this you are correct in your state component approach.
First you can create a pic component that has its own state with a hide/show button:
export default function Pic({pic}) {
const [showPic, setShowPic] = useState(true);
const handleClick = () => {setShowPic(!showPic)}
return (
<div>
<div style={showPic ? {display : "block"} : {display : "none"}}>
<img
src={pic.img}
width='500px'
height='500px'
/>
</div>
<button onClick={handleClick}>{showPic ? 'Hide' : 'Show'}</button>
</div>
)
}
Next, you can import this component into your main file
import Pic from 'location/Pic.js';
and map each pic to a <Pic> component.
{pics.map((pic) => <Pic pic={pic} key={pic.id}/>)}
Now the images will be shown each with their own Hide/Show button that can toggle their display with the state contained within each <Pic/> component. This is good practice because toggling one image will not cause a re-render of the entire image gallery.

Related

How to call useEffect again on button click in react?

I am beginner in react.
I am making a small project. How to add Product in cart and I am stuck at re Rendering useEffect.
so what I want is to re Render the useEffect on button click.
how do I do that?
Here Is my Cart Component
import React, { useContext, useEffect, useState,useCallback } from "react";
import { UserContext } from "./Context";
import { useHistory } from "react-router";
import cartImage from "../assets/man-shopping.png";
import Axios from "axios";
const Cart = () => {
const { user, serUser } = useContext(UserContext);
const history = useHistory();
const [product, setProduct] = useState([]);
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
setProduct(
product.filter((item) => {
return item.Id !== res.data.Id;
})
);
});
};
useEffect(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user]);
return (
<div
className="d-flex align-items-center justify-content-center"
style={{ height: "90vh" }}
>
{user.role === undefined ? (
<div>
<button
className="btn btn-lg btn-primary bg-green"
onClick={() => {
history.push("/login");
}}
>
Please Login
</button>
</div>
) : (
<div>
{product.length === 0 ? (
<figure className="figure">
<img
src={cartImage}
alt="cart"
style={{ width: "100px", height: "100px" }}
/>
<figcaption className="figure-caption text-xs-right">
No Product Added
</figcaption>
</figure>
) : (
<div className="d-flex">
{product.map((item) => {
return (
<div className="card">
<img
src={new Buffer.from(item.pimg).toString("ascii")}
className="img-fluid crd-img"
/>
<div className="card-body">
<h5 className="card-title">{item.pname}</h5>
<p className="card-text">{item.pprice}</p>
<button
className="btn btn-primary"
onClick={() => removeFromCart(item)}
>
Remove
</button>
</div>
</div>
);
})}
</div>
)}
</div>
)}
</div>
);
};
export default Cart;
so I made the removeFromCart function as useEffect dependency so it works fine
but its calls the backend again and again.
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
setProduct(
product.filter((item) => {
return item.Id !== res.data.Id;
})
);
});
};
useEffect(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user,removeFromCart]);
Is there any other way to re render useEffect
Put axios.get in a function.
const getProduct = useCallback(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user]);
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
getProduct();
});
};
useEffect(() => {
getProduct();
}, [getProduct]);
What you need is not useEffect. Use a 'state' to store the items and add or delete items using setState when 'state' gets updated react will automatically re-render. And you can use the updated state to save in your database. Also update state before calling axios
You can add a boolean state like this :
const [isClicked, setIsCliked] = useState(false);
And toogle the boolean value whenever the button is clicked
const removeFromCart = (item) => {
Axios.delete(`http://localhost:3002/cart/${item.Id}`).then((res) => {
setProduct(
product.filter((item) => {
return item.Id !== res.data.Id;
})
);
setIsCliked(bool => !bool)
});
};
And your useEffect may now look like this :
useEffect(() => {
Axios.get(`http://localhost:3002/cart/${user.Id}`).then((res) => {
setProduct(res.data);
});
}, [user.id,isClicked]);
There must be a better way but this should work

Click anywhere to close dropdown React

Hi I am creating an app where the user can search for books by title. The user can search and each book result has a dropdown. so I have many dropdowns on a single page (the search results page). I am trying to make a dropdown close when the user clicks outside of the dropdown button (which is a div). Currently I can open the dropdown by clicking on the dropdown button and only close it when clicking on the dropdown button again.
I need the dropdown to close when clicking anywhere except the dropdown options. How would I go about doing this?
ButtonDropDown.js
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { BsFillCaretDownFill } from 'react-icons/bs';
const ButtonDropDown = ({ choices, label }) => {
const [active, setActive] = useState(false);
const toggleClass = () => {
setActive(!active);
};
return (
<div className="dropdown">
<button onClick={toggleClass} type="button" className="dropbtn">
<BsFillCaretDownFill />
</button>
<div
id="myDropdown"
className={`dropdown-content ${active ? `show` : `hide`}`}
>
<div>{label}</div>
{choices.map((choice) => (
<div>{choice}</div>
))}
</div>
</div>
);
};
ButtonDropDown.propTypes = {
choices: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.string,
};
ButtonDropDown.defaultProps = {
label: 'Move to...',
};
export default ButtonDropDown;
Book.js
import React from 'react';
import PropTypes from 'prop-types';
import ButtonDropDown from './ButtonDropDown';
const Book = ({ title, authors, thumbnail }) => {
return (
<div className="book">
<img src={thumbnail} alt={title} className="book-thumbnail" />
<div className="book-title">{title}</div>
<div className="book-authors">{authors}</div>
<ButtonDropDown
choices={['Currently Reading', 'Want to Read', 'Read', 'None']}
/>
</div>
);
};
// Move to..., currently reading, want to read, read, none
Book.propTypes = {
thumbnail: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
authors: PropTypes.arrayOf(PropTypes.string),
};
Book.defaultProps = {
authors: [],
};
export default Book;
SearchPage.js
import React, { useEffect, useState } from 'react';
import { BsArrowLeftShort } from 'react-icons/bs';
// import { debounce } from 'debounce';
import SearchBar from '../components/SearchBar';
import { search } from '../api/BooksAPI';
import Book from '../components/Book';
const SearchPage = () => {
const [query, setQuery] = useState('');
const [data, setData] = useState([]);
// const [isLoading, setIsLoading] = useState(true);
const handleChange = (e) => {
setQuery(e.target.value);
};
useEffect(() => {
const bookSearch = setTimeout(() => {
if (query.length > 0) {
search(query).then((res) => {
if (res.length > 0) {
setData(res);
} else setData([]);
});
} else {
setData([]); // make sure data is not undefined
}
}, 1000);
// bookSearch();
// console.log(data); // undefined initially since we didnt search anything
return () => clearTimeout(bookSearch);
// if (data !== []) setIsLoading(false);
// setIsLoading(true);
}, [query]);
return (
<div>
<SearchBar
type="text"
searchValue={query}
placeholder="Search for a book"
icon={<BsArrowLeftShort />}
handleChange={handleChange}
/>
<div className="book-list">
{data !== []
? data.map((book) => (
<Book
key={book.id}
title={book.title}
authors={book.authors}
thumbnail={book.imageLinks.thumbnail}
/>
))
: 'ok'}
</div>
</div>
);
};
export default SearchPage;

React toggle view functionality in the parent via child component

I am trying to toggle view between list of meals and meal details. I have placed a button in the child component Meal.js to the Meals.js which is meant to be the list and the details view.
Can you please help me fix this issue. Seems like its not working even with the conditional rendering method I've used in the code below.
Meal.js
import { useState } from 'react'
import './Meal.css'
const Meal = (props) => {
const [isToggled, setIsToggled] = useState(false);
const sendIdHandler = () => {
if (isToggled === true) {
setIsToggled(false);
}
else {
setIsToggled(true);
}
props.onSaveIdHandler(props.id, isToggled)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Meals.js
import Meal from './Meal/Meal'
const Meals = (props) => {
let toggleCondition = false;
const saveIdHandler = (data, isToggled) => {
toggleCondition = isToggled;
const mealDetails = props.mealsMenuData.findIndex(i =>
i.id === data
)
console.log(mealDetails, toggleCondition)
}
return (
<div>
{toggleCondition === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{toggleCondition === true &&
<div>Horray!</div>
}
</div>
);
}
export default Meals;
UPDATE
Finally figured how to do this properly. I put the condition true/false useState in the parent instead and have Meal.js only send the id I need to view the item
Code is below..
Meals.js
import { useState } from 'react'
import Meal from './Meal/Meal'
import MealDetails from './MealDetails/MealDetails'
const Meals = (props) => {
const [show, setShow] = useState(false);
const [mealId, setMealId] = useState(0);
const saveIdHandler = (data) => {
setShow(true);
setMealId(props.mealsMenuData.findIndex(i =>
i.id === data)
)
console.log(props.mealsMenuData[mealId].ingridients)
}
const backHandler = () => {
setShow(false)
}
return (
<div>
{show === false &&
props.mealsMenuData.map(item =>
<Meal
key={item.id}
id={item.id}
label={item.label}
onSaveIdHandler={saveIdHandler}
/>
)
}
{show === true &&
<div>
<MealDetails data={props.mealsMenuData[mealId]} />
<button onClick={backHandler}>Back</button>
</div>
}
</div>
);
}
export default Meals;
Meal.js
import './Meal.css'
const Meal = (props) => {
const sendIdHandler = () => {
props.onSaveIdHandler(props.id)
}
return (
<div
className='meal'
onClick={sendIdHandler}
>
{props.label}
</div>
);
}
export default Meal;
Your problem in sendIdHandler: You can update like this:
const sendIdHandler = () => {
const newIsToggled = !isToggled;
setIsToggled(newIsToggled)
props.onSaveIdHandler(props.id, newIsToggled)
}

Why react loads images for many times? is it possible to save the first load?

to watch the problem you can vivsit the test site http://u100525.test-handyhost.ru/products
the problem appears if to click many times on category items, images of products start to bug becouse react loads image of one item over and over again, on every change of category - on every filter of products, so how to make one load and save somehow the loaded images?
so if i click on categories my code is filtering products array and update statement - visibleProducts then im doing visibleProducts.map((product)=>{});
and i`m getting bug problem, because every time when react renders my the component does request to the server for getting image by id and waits while the image will load, but if i click on an other category react(ProductItem) starts other request for new images then it is starting to bug they start blinking and changing ;c
im new in react and just stated to practice what i have to do guys?
is my code correct ?
here is my ProductItem component ->
import React, { useState, useEffect, memo, useCallback } from "react";
import { Link } from "react-router-dom";
import { connect } from "react-redux";
import { setModalShow, onQuickViewed, addedToCart } from "../../actions";
import Checked from "../checked";
import "./product-item.css";
import Spinner from "../spinner";
const ProductItem = ({
product,
wpApi,
addedToCart,
onQuickViewed,
setModalShow,
}) => {
const [prodImg, setProdImg] = useState("");
const [animated, setAnimated] = useState(false);
const [checked, setChecked] = useState(false);
const [itemLoading, setItemLoading] = useState(true);
const checkedFn = useCallback(() => {
setChecked(true);
setTimeout(() => {
setChecked(false);
}, 800);
},[product]);
const onModalOpen = useCallback((e, id) => {
onQuickViewed(e, id);
setModalShow(true);
}, product);
const addHandle = useCallback((e, id) => {
e.preventDefault();
addedToCart(id);
checkedFn();
},[product]);
useEffect(()=>{
setItemLoading(false);
}, [prodImg]);
useEffect(() => {
wpApi.getImageUrl(product.imageId).then((res) => {
setProdImg(res);
});
});
return (
<div className="product foo">
<div
className='product__inner'}
>
{!itemLoading? <div
className="pro__thumb"
style={{
backgroundImage:prodImg
? `url(${prodImg})`
: "assets/images/product/6.png",
}}
>
<Link
to={`/product-details/${product.id}`}
style={{ display: `block`, width: `100%`, paddingBottom: `100%` }}
>
</Link>
</div>: <Spinner/>}
<div className="product__hover__info">
<ul className="product__action">
<li>
<a
onClick={(e) => {
onModalOpen(e, product.id);
}}
title="Quick View"
className="quick-view modal-view detail-link"
href="#"
>
<span ><i class="zmdi zmdi-eye"></i></span>
</a>
</li>
<li>
<a
title="Add TO Cart"
href="#"
onClick={(e) => {
addHandle(e, product.id);
}}
>
{checked ? (
<Checked />
) : (
<span className="ti-shopping-cart"></span>
)}
</a>
</li>
</ul>
</div>
</div>
<div className="product__details">
<h2>
<Link to={`/product-details/${product.id}`}>{product.title}</Link>
</h2>
<ul className="product__price">
<li className="old__price">${product.price}</li>
</ul>
</div>
</div>
);
};
const mapStateToProps = ({ options, cart, total, showModal }) => {
return {};
};
const mapDispatchToProps = {
onQuickViewed,
setModalShow,
addedToCart,
};
export default connect(mapStateToProps, mapDispatchToProps)(memo(ProductItem));
here is my parent component Products ->
import React, { useEffect, useState } from "react";
import { connect } from "react-redux";
import ProductItem from "../product-item";
import { withWpApiService } from "../hoc";
import { onQuickViewed, addedToCart, categoriesLoaded } from "../../actions";
import CategoryFilter from "../category-filter";
import Spinner from "../spinner";
import "./products.css";
const Products = ({
maxProducts,
WpApiService,
categoriesLoaded,
addedToCart,
onQuickViewed,
products,
categories,
loading,
}) => {
const [activeIndex, setActiveIndex] = useState(0);
const [activeCategory, setActiveCategory] = useState(0);
const [visibleProducts, setVisibleProducts] = useState([]);
const wpApi = new WpApiService();
useEffect(() => {
updateVisibleProducts(activeCategory, products);
}, [products]);
useEffect(() => {
wpApi.getCategories().then((res) => {
categoriesLoaded(res);
});
}, []);
const getCatId = (cat) => {
setActiveCategory(cat);
updateVisibleProducts(cat, products);
setActiveIndex(cat);
};
const updateVisibleProducts = (category, products) => {
let updatedProducts = [];
switch (category) {
case 0:
updatedProducts = products;
setVisibleProducts(updatedProducts);
break;
default:
updatedProducts = products.filter(
(product) => product.categories.indexOf(category) >= 0
);
setVisibleProducts(updatedProducts);
}
};
let currentLocation = window.location.href.split("/");
if (!loading) {
return (
<section className="htc__product__area shop__page mb--60 mt--130 bg__white">
<div className={currentLocation[3] == "" ? `container` : ""}>
<div className="htc__product__container">
<CategoryFilter
activeIndex={activeIndex}
categories={categories}
getCatId={getCatId}
/>
<div
className="product__list another-product-style"
style={{ height: "auto" }}
>
{visibleProducts
.slice(0, maxProducts ? maxProducts : products.length)
.map((prod, id) => {
return (
<ProductItem
wpApi={wpApi}
key={id}
onQuickViewed={onQuickViewed}
addedToCart={addedToCart}
product={prod}
/>
);
})}
</div>
</div>
</div>
</section>
);
} else {
return <Spinner />;
}
};
const mapStateToProps = ({ products, loading, activeCategory, categories }) => {
return {
products,
activeCategory,
categories,
loading,
};
};
const mapDispatchToProps = {
addedToCart,
categoriesLoaded,
onQuickViewed,
};
export default withWpApiService()(
connect(mapStateToProps, mapDispatchToProps)(Products)
);
and if you need, here is my CategoryFilter component ->
import React from 'react'
const CategoryFilter = ({categories, getCatId, activeIndex}) => {
return (
<div className="row mb--60">
<div className="col-md-12">
<div className="filter__menu__container">
<div className="product__menu">
{categories.map((cat) => {
return (
<button key={cat.id}
className={activeIndex === cat.id? 'is-checked' : null}
onClick={() => getCatId(cat.id)}
data-filter=".cat--4"
>
{cat.name}
</button>
);
})}
</div>
</div>
</div>
</div>
)
}
export default CategoryFilter

Gatsby: How to close previously opened item if new one is selected?

I have a page where are list items with option to click on each of them and open info box. When I click on one, info box is being opened, but when I click on another, the previously one stays there instead of closing it. How can I make it work, so when I click on new, the previously opened closes? My code here?
import React, { useState } from "react";
import "../../styles/styles.scss";
import InfoIcon from "../../images/icons/info.svg";
import InfoBox from "../info/InfoBox";
const Step = ({ title, description }) => {
const [show, setShow] = useState(false);
const openInfo = () => {
setShow(true);
};
const closeInfo = () => {
setShow(false);
};
return (
<>
<li>
<div className="list-item" onClick={openInfo}>
<div className="list-item-content">
<h3>{title}</h3>
<InfoIcon className="info-icon" />
</div>
</div>
</li>
{show && (
<InfoBox
title={title}
description={description}
closeInfo={closeInfo}
/>
)}
</>
);
};
export default Step;
You could:
move const [show, setShow] = useState(false); to the parent component.
Instead of a boolean you could store the title of the infoBox, initialising it to null . [openedInfo, setOpenedInfo] = useState(null);
the Step component will have 2 other props: setOpenedInfo and openedInfo
openInfo will be const openInfo = () => setOpenedInfo(title)
closeInfo will be const closeInfo = () => setOpenedInfo(null)
You will show the infoBox if openedStep === title
This way you will always have only one infoBox open.
I'm assuming that the title is a string and is unique. You can substitute title with any other (unique) value related to the Step component.
import React, { useState } from "react";
import "../../styles/styles.scss";
import InfoIcon from "../../images/icons/info.svg";
import InfoBox from "../info/InfoBox";
const ParentComponent = () => {
[openedInfo, setOpenedInfo] = useState(null);
return data.map(({title,description})=><Step title={title} description={description} setOpenedInfo={setOpenedInfo} openedInfo={openedInfo}/>)
}
const Step = ({ title, description, setOpenedInfo, openedInfo }) => {
const openInfo = () => {
setOpenedInfo(title); //better use an id if available
};
const closeInfo = () => {
setOpenedInfo(null);
};
return (
<>
<li>
<div className="list-item" onClick={openInfo}>
<div className="list-item-content">
<h3>{title}</h3>
<InfoIcon className="info-icon" />
</div>
</div>
</li>
{openedInfo === title && (
<InfoBox
title={title}
description={description}
closeInfo={closeInfo}
/>
)}
</>
);
};
export default Step;

Categories

Resources