I want to add a filter in arrays of filters, but all filters are added in the same array in filters. There are some blocks for filters and every filter must be added in its array. Now, every filter is added in its array, but all other filters are updated in that array too.
export const DropDownBlock = () => {
const [filters, setFilters] = useState({
type: [],
license: [],
tag: [],
format: [],
});
const filterKey = Object.keys(item.filters);
const [checked, setChecked] = useState([]);
return (
<section className="filterSection">
{filterKey.map((f, index) => {
const filterArray = [];
const photoItems = photos.map((p) => {
return p.filters[filterKey[index]];
});
photoItems.map((p) => {
if (filterArray.indexOf(p) < 0) {
filterArray.push(p);
}
});
const handleFilters = (filters, category) => {
const newFilters = { ...filters };
newFilters[category] = filters;
setFilters(newFilters);
};
return (
<div className="" key={f}>
<div
className="dropDownTitleBlock"
onClick={() => (isOpen ? setIsOpen(false) : setIsOpen(true))}
>
{isOpen ? <MdKeyboardArrowDown /> : <MdKeyboardArrowRight />}
<h5 className="dropDownTitle">{f}</h5>
</div>
{isOpen && (
<div className="dropDownCategoryBlock">
{filterArray.map((filter) => {
switch (f) {
case filterKey[index]:
return (
<Checkbox
filter={filter}
handleFilters={(filters) =>
handleFilters(filters, filterKey[index])
}
checked={checked}
setChecked={setChecked}
/>
);
}
})}
</div>
)}
</div>
);
})}
</section>
);
};
Related
enter image description here
enter image description here
enter image description here
API data
How to remove item in array have 2 id, id product and id product detail. When I click on the product to delete, redux selects the id in array 1 to delete and delete all product details. I want to remove product detail instead of product in array 1
This is my cartSlice.js in redux
import { createSlice } from '#reduxjs/toolkit';
const cartSlice = createSlice({
name: 'cart',
initialState: {
cart: [],
},
reducers: {
addToCart: (state, action) => {
const itemInCart = state.cart.find((item) => item.id === action.payload.id && item.idProduct === action.payload.idProduct);
if (itemInCart) {
itemInCart.quantity++;
} else {
state.cart.push({ ...action.payload, quantity: 1 });
}
},
incrementQuantity: (state, action) => {
const item = state.cart.find((item) => item.id === action.payload);
item.quantity++;
},
decrementQuantity: (state, action) => {
const item = state.cart.find((item) => item.id === action.payload);
if (item.quantity === 1) {
item.quantity = 1
} else {
item.quantity--;
}
},
removeItem: (state, action) => {
const removeItem = state.cart.filter((item) =>
item.id !== action.payload
);
state.cart = removeItem;
},
},
});
export const cartReducer = cartSlice.reducer;
export const {
addToCart,
incrementQuantity,
decrementQuantity,
removeItem,
} = cartSlice.actions;
SingleProduct.js
import { useDispatch } from 'react-redux';
import { useSelector } from 'react-redux';
import { addToCart } from './redux/cartSlice';
function SingleProduct() {
const [index, setIndex] = useState(1);
const [product, setProduct] = useState([]);
const [productDetail, setProductDetail] = useState()
// Set option product
const [id, setId] = useState(1)
const [idProduct, setIdProduct] = useState(1)
const [name, setName] = useState('')
const [price, setPrice] = useState(0);
const [image, setImage] = useState(undefined)
const [colorValue, setColorValue] = useState(undefined)
const [ramValue, setRamValue] = useState(undefined)
const [storageValue, setStorageValue] = useState(undefined)
const cart = useSelector((state) => state.cart)
console.log(cart)
const dispatch = useDispatch()
// const getTotalQuantity = () => {
// let total = 0
// cart.forEach(item => {
// total += item.quantity
// })
// return total
// }
// get path
const location = useLocation()
const productId = location.pathname.split('/')[2]
const path = location.pathname
console.log(productId, path)
//fetch api product
useEffect(() => {
fetch(`https://api-uit.herokuapp.com/api/iphone/${productId}`)
.then(res => res.json())
.then(data => {
setProduct(data);
setName(data.name)
setId(data.id)
setProductDetail(data.product_details)
var dataFirst = data.product_details[0]
var checkRam = dataFirst.ram !== undefined ? `${dataFirst.ram}` : undefined
setRamValue(checkRam)
setPrice(dataFirst.price)
setStorageValue(dataFirst.storage)
setImage(dataFirst.image)
setColorValue(dataFirst.color)
})
}, [productId])
const product_current = {
id: id,
idProduct: idProduct,
name: name,
image: image,
price: price,
color: colorValue,
ram: ramValue,
storage: storageValue
};
console.log(product_current)
//handle option
const findColor = (color) => {
setColorValue(color);
}
const findRam = (ram) => {
setRamValue(ram);
}
const findStorage = (storage) => {
setStorageValue(storage);
}
// console.log(productDetail)
// handle render when choose option
useLayoutEffect(() => {
var target = productDetail && productDetail.find(item => {
var ram = ramValue === undefined ? ramValue : `${ramValue}`;
return (
item.ram === ram &&
item.color === `${colorValue}` &&
item.storage === `${storageValue}`
)
})
// console.log(target)
var changeName = target !== undefined ? (target.name) : name;
var changeId = target !== undefined ? (target.id) : idProduct;
var changeImage = target !== undefined ? (target.image) : image;
var changePrice = target !== undefined ? (target.price) : price;
setIdProduct(changeId)
setName(changeName)
setImage(changeImage)
setPrice(changePrice)
// console.log(result);
}, [productDetail, ramValue, colorValue, storageValue, price, image, name, idProduct])
const getText = (html) => {
const doc = new DOMParser().parseFromString(html, "text/html")
return doc.body.textContent
}
const priceString = getText(price.toLocaleString().concat('đ'))
return (
<div className={cx('wrap')}>
<div className="grid wide">
<div className="wide row">
<div className="wide l-6 m-6 c-12" >
<img className={cx('img-product')} src={(image)} alt={product.name} />
</div>
<div className="wide l-6 m-6 c-12">
<div className={cx("wrap-heading")}>
<div className={cx('heading')}>{name}</div>
<div className={cx('separate')}></div>
<div className={cx('price')}>{priceString}</div>
{/* Check product have storage */}
{product.option && product.option.map((option, idx) =>
<div key={idx}>
{(option.key === "storage" ?
<div className={cx('wrap-storage')}>
<div className={cx('storage-heading')}>Chọn {option.key}</div>
<div className={cx('wrap-option')}>
{option.value.map((value, idx) =>
<div className={cx('option', `${value === storageValue ? "active" : ""}`)}
key={idx}
value={value}
onClick={() => findStorage(value)}
>
{value}
</div>
)}
</div>
</div> : <></>)}
</div>
)}
{/* Check product have ram */}
{product.option && product.option.map((option, idx) =>
<div key={idx}>
{(option.key === "ram" ?
<div className={cx('wrap-storage')}>
<div className={cx('storage-heading')}>Chọn {option.key}</div>
<div className={cx('wrap-option')}>
{option.value.map((value, idx) =>
<div className={cx('option', `${value === ramValue ? "active" : ""}`)}
key={idx}
value={value}
onClick={() => findRam(value)}
>
{value}
</div>
)}
</div>
</div> : <></>)}
</div>
)}
{/* Check product have color */}
{product.option && product.option.map((option, idx) =>
<div key={idx}>
{(option.key === "color" ?
<div className={cx('wrap-color')}>
<div className={cx('heading-color')}>Chọn {option.key}</div>
<div className={cx('option-color')}>
{option.value.map((value, idx) =>
<div className={cx('space', `${value === colorValue ? "active" : ""}`)}
key={idx}
value={value}
onClick={() => findColor(value)}
>
<div className={cx('radio-color')} style={{ backgroundColor: value }}></div>
</div>
)}
</div>
</div> : <></>)}
</div>
)}
<div className={cx('wrap-buy')}>
<div className={cx('box-buy')}>
<div className={cx('buy-cash')}>MUA NGAY</div>
</div>
<div className={cx('box-buy')}>
<div className={cx('add-to-list')}
onClick={() => dispatch(addToCart(product_current))}
>THÊM VÀO GIỎ HÀNG</div>
</div>
</div>
</div>
</div>
)
}
export default SingleProduct
This is my Cart.js
import React, { useState } from 'react'
import classNames from "classnames/bind";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import { useSelector } from 'react-redux';
import { useDispatch } from 'react-redux'
import { faXmark, faBagShopping } from "#fortawesome/free-solid-svg-icons";
import styles from './Cart.module.scss'
import { removeItem } from 'src/pages/SingleProduct/redux/cartSlice'
const cx = classNames.bind(styles)
function Cart() {
const dispatch = useDispatch()
const [showCart, setShowCart] = useState(false)
// toggle cart
const ToggleCart = () => {
setShowCart(!showCart)
}
const show = showCart ? 'toggle-cart' : ''
const cart = useSelector((state) => state.cart)
// console.log(cart)
// Tính tổng
const getTotal = () => {
let totalQuantity = 0
let totalPrice = 0
cart.forEach(item => {
totalQuantity += item.quantity
totalPrice += item.price * item.quantity
})
return { totalPrice, totalQuantity }
}
const getText = (html) => {
const doc = new DOMParser().parseFromString(html, "text/html")
return doc.body.textContent
}
const totalPrice = getTotal().totalPrice
const priceTotalString = getText(totalPrice.toLocaleString().concat('đ'))
return (
<>
<div className={cx('wrap-cart')}>
<FontAwesomeIcon className={cx('icon')} icon={faBagShopping} onClick={ToggleCart} />
<p>{getTotal().totalQuantity || 0}</p>
</div>
{show && <div className={cx('wrap-modal')} onClick={ToggleCart}>
</div>}
<div className={cx('cart', show)}>
<FontAwesomeIcon icon={faXmark} className={cx('icon-mark-cart')} onClick={ToggleCart} />
<div className={cx('height-cart')}>
{cart.map((product, idx) =>
<div className={cx('product-cart')} key={idx}>
<img className={cx('cart-img')} src={product.image} alt="" />
<div className={cx('wrapper-cart')}>
<div className={cx('name-cart')}>{product.name}</div>
<p className={cx('price-cart')}>{product.quantity} x {getText(product.price.toLocaleString().concat('đ'))}</p>
</div>
<div
className={cx('cartItem__removeButton')}
onClick={() => dispatch(removeItem(product.id && product.idProduc))}>
x
</div>
</div>
)}
</div>
<div className={cx('footer-cart')}>
<div className={cx('wrap-total')}>
<p>Tạm tính: </p>
<p>{priceTotalString}</p>
</div>
<div className={cx('wrap-pay')}>
<div className={cx('cart-buy')}>MUA NGAY</div>
</div>
</div>
</div>
</>
)
}
export default Cart
you should pass both the id (productId & productDetailId) to removeItem reducer function.
removeItem: (state, action) => {
const {productId, productDetailId} = action.payload;
const selectedProduct = state.cart.find((item) => item.id === productId);
if(Array.isArray(selectedProduct?.product_details)) {
selectedProduct.product_details = selectedProduct.product_details.filter(item => item.id !== productDetailId);
}
},
Basically, what we are doing is:
find product with the given product id
In the selected(found) product, remove the product detail id.
To call call this:
dispatch(removeItem({
productId: "yyy",
productDetailId: "zzz"
}))
Hope, that'll solve the issue.
Thanks
** As you can see i am taking input from the user and display and wanted data display on screen according to the year which you select( filter data according to year) and if there is no item i wanted to display found no expense **
this is my Expenses item code
const ExpenseAll = (props) => {
const [filteredYear, setFilteredYear] = useState("2020");
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
const filteredExpenses = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
return (
<div>
<Card className="expenses">
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
<ExpensesList items={filteredExpenses} />
</Card>
</div>
);
};
this is my condition filter code which is not working showing empty screen
if (props.items.length === 0) {
return <h2 className="expenses-list__fallback"> Found no Expense</h2>;
}
return (
<ul className="expenses-list">
{props.items.map((expense) => (
<ExpenseItem
key={expense.id}
title={expense.title}
amount={expense.amount}
date={expense.date}
/>
))}{" "}
;
</ul>
);
}
Try to store filteredExpenses in a state with default value:
const ExpenseAll = (props) => {
const [filteredExpenses, setFilteredExpenses] = useState([]);
const [filteredYear, setFilteredYear] = useState('2020');
const filterChangeHandler = (selectedYear) => {
setFilteredYear(selectedYear);
};
useEffect(() => {
const filtered = props.items.filter((expense) => {
return expense.date.getFullYear().toString() === filteredYear;
});
setFilteredExpenses(filtered)
}, []);
return (
<div>
<Card className='expenses'>
<ExpensesFilter
selected={filteredYear}
onChangeFilter={filterChangeHandler}
/>
<ExpensesList items={filteredExpenses} />
</Card>
</div>
);
};
I wanna push the object "task" on my array todoList.
For the moment only the value task is recorded on my todoList. It's weird because on my const Addtask, the value task is my object.
When i click on my button i also want him to change is value "etat".If i want to sort it, do i need to use .map.sort ?
Did i forget something ?
function Task() {
const [task, setTask] = useState({ task: "", etat: "en cours" });
const [todoList, setTodoList] = useState([]);
const switchEnCours = () => {
setTask({etat:"terminé"});
};
const deleteTask = () => {
setTask({etat:"supprimée"});
};
const handleInput = (e) => {
setTask(e.target.value);
};
const AddTask = (e) => {
setTodoList([...todoList, task]);
console.log(todoList);
};
useEffect(() => console.log(todoList), [todoList]);
return (
<div>
<input onChange={handleInput}></input>
<button onClick={AddTask}>Valider</button>
<div className="DivColonne">
<div className="Colonne">
<h1>Tâche à faire</h1>
{todoList.map((insertTask) => {
return (
<div>
<p>{insertTask.task}</p>
<button onClick={switchEnCours}>{insertTask.etat}</button>
</div>
);
})}
</div>
<div className="Colonne">
<h1>Tâche en cours</h1>
{encours === "terminé" ? (
<div>
{todoList.map((insert) => {
return (
<div>
<p>{insert.task}</p>
<button onClick={deleteTask}>{insert.etat}</button>
</div>
);
})}
</div>
) : (
<div></div>
)}
</div>
<div>
<h1>Tâches terminées</h1>
{encours === "supprimée" ? (
<div>
<p>{todoList}</p>
</div>
) : (
<div></div>
)}
</div>
</div>
</div>
);
}
when you want to update an object state you need to pass in the old properties of that object that are not going to change, so you dont lose those properties. This is done using a spread operator
for example your
const switchEnCours = () => {
setTask({ etat: "terminé" });
};
should be
const switchEnCours = () => {
setTask({...task, etat: "terminé"});
};
you also have to do the same with the handleInput function
const handleInput = (e) => {
setTask({...task, task: e.target.value});
};
I am doing the implementation of list pagination through a custom hook. The handleSetCurrentPage() function gets the correct number, it uses setCurrentPage(number). Consolelog setCurrentPage(number) showed undefined.
if you do all the same code only within one file (put everything in ListOfItems) it works fine.
Hook:
export const usePagination = (users = [], defaultPage = 1, amountPerPage = 10) => {
const [currentPage, setCurrentPage] = useState(defaultPage);
const [currentUsers, setCurrentUsers] = useState([]);
const [amountOfPages, setAmountOfPages] = useState(0);
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, []);
const updateUsers = () => {
const indexOfLastPost = currentPage * amountPerPage;
const indexOfFirstPost = indexOfLastPost - amountPerPage;
const updatedUsers = users.slice(indexOfFirstPost, indexOfLastPost);
setCurrentUsers(updatedUsers);
};
const updateAmountOfPages = () => {
const updatedAmount = Math.ceil(users.length / amountPerPage);
setAmountOfPages(updatedAmount);
};
return {
setCurrentPage,
amountOfPages,
currentUsers,
};
};
list of items:
export function ListOfItems() {
const users = useSelector(state => state);
const { setCurrentPage, currentUsers, amountOfPages } = usePagination(users);
let {url} = useRouteMatch();
let items = currentUsers.map(function (value, index) {
return (
<form key={index}>
<div className="input-group">
<div className="input-group-prepend">
<Link className="input-group-text" to={`${url}/${index}`}>
{value.name}, {index}
</Link>
</div>
</div>
</form>
)
});
return (
<div>
{/*<form className="card">*/}
{/* <Search setSearch={setSearch} />*/}
{/*</form>*/}
<div>{items}</div>
<div>
<Pagination amountOfPages={amountOfPages} setCurrentPage={setCurrentPage}/>
</div>
</div>
)
}
pagination component:
const Pagination = ({amountOfPages, setCurrentPage}) => {
const [pageNumbers, setPageNumbers] = useState([]);
useEffect(() => {
calculatePageNumbers();
}, [amountOfPages]);
function calculatePageNumbers() {
const updatedPageNumbers = [];
for (let i = 1; i <= amountOfPages; i++) {
updatedPageNumbers.push(i);
}
setPageNumbers(updatedPageNumbers);
}
function handleSetCurrentPage(number) {
console.log(number);
return console.log(setCurrentPage(number));
}
return (
<nav>
<ul className="pagination">
{pageNumbers.map(number => (
<li key={number} className="page-item">
<button
onClick={() => handleSetCurrentPage(number)}
type="button"
className="page-link"
>
{number}
</button>
</li>
))}
</ul>
</nav>
);
};
export default Pagination;
useEffect(() => {
updateUsers();
updateAmountOfPages();
}, [currentPage]);
I want to show/hide a part of JSX depending on isCommentShown state property. But as this part is inside a map loop isCommentShown acts for all mapped items not only the current one. So when I toggleComment every comment inside a loop is shown/hidden. I imagine this can be solved by moving everything into a separate component because every component has its own state. But I wonder if I can can solve this without that.
const SearchResults = () => {
const [isCommentShown, setIsCommentShown] = useState(false);
const toggleComment = () => {
setIsCommentShown(!isCommentShown);
};
return (
<>
{props.search_results.map(obj =>
<div key={obj.id}>
{ obj.comment ? <img onClick={toggleComment}/> : null }
<div>{obj.text}</div>
{ isCommentShown ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
};
You could use the useState hook to create an object that will keep all the search result ids as keys and a boolean value indicating if the comment should be shown or not.
Example
const { useState, Fragment } = React;
const SearchResults = props => {
const [shownComments, setShownComments] = useState({});
const toggleComment = id => {
setShownComments(prevShownComments => ({
...prevShownComments,
[id]: !prevShownComments[id]
}));
};
return (
<Fragment>
{props.search_results.map(obj => (
<div key={obj.id}>
{obj.comment ? (
<button onClick={() => toggleComment(obj.id)}>Toggle</button>
) : null}
<div>{obj.text}</div>
{shownComments[obj.id] ? <p>{obj.comment}</p> : null}
</div>
))}
</Fragment>
);
};
ReactDOM.render(
<SearchResults
search_results={[
{ id: 0, text: "Foo bar", comment: "This is rad" },
{ id: 1, text: "Baz qux", comment: "This is nice" }
]}
/>,
document.getElementById("root")
);
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Instead of storing true or false, you must store the comment id to show provided you only want to show one comment at a time. Its important to uniquely identify the item to be expanded
const SearchResults = () => {
const [commentShown, setCommentShown] = useState({});
const toggleComment = (id) => {
setCommentShown(prev => Boolean(!prev[id]) ? {...prev, [id]: true} : {...prev, [id]: false});
};
return (
<>
{props.search_results.map(obj =>
<div key={obj.id}>
{ obj.comment ? <img onClick={() => toggleComment(obj.id)}/> : null }
<div>{obj.text}</div>
{ commentShown[id] ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
};
If at all you need to open multiple comments simultaneously you can maintain a map of open ids
const SearchResults = () => {
const [commentShown, setCommentShown] = useState('');
const toggleComment = (id) => {
setCommentShown(prev => prev.commentShown !== id? id: '');
};
return (
<>
{props.search_results.map(obj =>
<div key={obj.id}>
{ obj.comment ? <img onClick={() => toggleComment(obj.id)}/> : null }
<div>{obj.text}</div>
{ commentShown === obj.id ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
};
Use the id to target the toggle on the comment you want.
More precisely, use the state to store the show/hide values, and pass the id to the onclick event to precise which comment to toggle. This should do the job:
class SearchResults extends React.Component {
constructor(props) {
super(props);
this.state = {};
for (let result of props.search_results) {
this.state[`${result.id}IsShown`] = true;
}
}
toggleComment(id) {
let key = `${result.id}IsShown`;
this.setState({[key]: !this.state[key]});
}
render() {
return (
<>
{this.props.search_results.map(result =>
<div key={result.id}>
{
result.comment
? <img onClick={() => toggleComment(result.id)}/>
: null
}
<div>{result.text}</div>
{ isCommentShown ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
}
}