Calculate total from array in localStorage - javascript

I am using react and globalContext to add items to a cart page and this adds the product to the array stored in localStorage and looks like this.
basket,…]
0: {id: "9", title: "Apple Watch SE 40mm (GPS) - Space Grey Aluminium Case with Midnight Sport Band",…}
id: "9"
price: 249.99
quantity: 1
src: "/static/media/se.0ca02982636517aed223.png"
title: "Apple Watch SE 40mm (GPS) - Space Grey Aluminium Case with Midnight Sport Band"
The key is basket and i am able to map the data in the basket page but how would I calculate the total of the items within that array?
import { GlobalContext } from '../context/GlobalState'
export const Basket = () => {
const { basket } = useContext(GlobalContext)
return (
<div className="h-screen bg-zinc-100 p-4">
<div className="py-5">
<div className="max-w-md mx-auto bg-white shadow-lg rounded-lg md:max-w-7xl">
<div className="md:flex">
<div className="w-full p-4">
<div className="md:grid md:grid-cols-3 gap-2 ">
<div className="col-span-2 p2 md:p-5">
<h1 className="text-xl font-bold ">Shopping Basket</h1>
<div className="flex justify-between flex-col items-center mt-6 pt-6 gap-12">
{basket.length > 0 ? (
<>
{basket.map(item => (
<>
<div className='flex flex-col w-full justify-between md:gap-44 items-start'>
<div className="flex items-center">
<img src={item.src} className="rounded w-20 " />
<div className="flex flex-col ml-3 gap-1 ">
<span className="md:text-lg text-sm font-bold w-full md:t-width ">{item.title}</span>
<span className="text-xl font-bold">£ {item.price}</span>
</div>
</div>
</div>
</>
))}
</>
) : (
<div>No Items</div>
)}
</div>
<div className="flex justify-between items-center mt-6 pt-6 border-t">
<div className="flex items-center"> <i className="fa fa-arrow-left text-sm pr-2">
</i> <span className="text-md font-medium text-amz hover:text-orange-500 cursor-pointer "> <FontAwesomeIcon icon={faChevronLeft}></FontAwesomeIcon> Continue Shopping</span>
</div>
<div className="flex justify-center items-end">
<span className="text-sm font-medium text-gray-400 mr-1">Total:</span>
<span className="text-lg font-bold text-gray-800 ">Total}</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}

You'd simply loop over the array from the localstorage and calculate the total.
let cartArray = localStorage.getItem("basket");
/* since cartArray returns a stringified array you need to parse it */
cartArray = JSON.parse(cartArray);
/* you now have an array of objects. You'd now want to use the reduce function on the object */
const total = cartArray.reduce((prev, cur) => (cur.price * cur.quantity) + prev, 0);
total would be your cart total

Related

How to access and display nested object property when click on an user image and goes to detail page

I am fetching a list of items from a local Data.js file and displaying it using map method in Header.js. But when I click on user image then it should go to user detail page and should display all the properties of that object. But when I click it gives me error saying "Cannot read properties of undefined (reading 'name')". I am using react-router-domv6.
Users.js
import React from 'react';
import { Link } from 'react-router-dom';
import { list } from './Data';
function Header() {
console.log(list);
return (
<div>
<div className="bg-gradient-to-b from-white-300 to-[#6337c8] relative flex justify-center items-center h-screen w-screen">
<div className="absolute top-0 w-full z-[-1]">
<div className="h-[20vh] bg-[#6337c8]"></div>
<svg className="drop-shadow-[0px_17px_0px_#dcd6f3] z-[-1]" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1440 320">
<path
fill="#6337c8"
fillOpacity={1}
d="M 0 224 L 80 197.3 C 160 171 320 117 480 128 C 640 139 800 213 960 245.3 C 1120 277 1280 267 1360 261.3 L 1440 256 L 1440 0 L 1360 0 C 1280 0 1120 0 960 0 C 800 0 640 0 480 0 C 320 0 160 0 80 0 L 0 0 Z"
></path>
</svg>
</div>
<div className="h-[85vh] shadow-2xl w-2/5 rounded-[25px] bg-white">
<div className="w-full rounded-t-[25px] flex justify-center items-center h-[16vh] text-gray-600 bg-gray-100 text-xl">
{' '}
Select an account{' '}
</div>
<div className="h-[65vh] pb-4 px7 overflow-y-auto scrollbar scrollbar-thumb-gray-200">
{list &&
list.length > 0 &&
list.map((value) => (
<Link key={value.id} to={`/profile/${value.id}`}>
<div className="cursor-pointer flex gap-3 items-center border-b py-2">
<img className="rounded-full w-[7%]" src={value.profilepicture} alt="" />
<p>City: {value.address.city}</p>
<p>Company Name: {value.company.name}</p>
<div className="text-md text-gray-700 text-center font-[400] ">
<button>{value.name}</button>
</div>
</div>
</Link>
))}
</div>
</div>
</div>
</div>
);
}
export default Header;
UserDetail.js
import React from 'react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { list } from './Data';
function Profile() {
const [item, setItem] = useState({});
const [show, setShow] = useState(false);
const { id } = useParams();
const navigate = useNavigate();
useEffect(() => {
getItem();
}, []);
const getItem = () => {
const newItem = list.find((value) => value.id === parseInt(id));
setItem(newItem);
};
const Logout = () => {
navigate('/');
};
return (
<div>
<div className="w-screen h-screen flex gap-10 px-8 pt-3 overflow-x-hidden">
<div className="flex flex-col w-full">
<div className="h-[13vh] w-[100%] border-b border-gray-300">
<div className="flex h-full items-center justify-between">
<div className="capitalize text-[20px] text-gray-500">
<p>profile</p>
</div>
<div className="relative inline-block text-left mt-5 ml-40" data-headlessui-state>
{
<div>
<button
className="inline-flex w-full justify-center rounded-md bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm focus:outline-none "
id="headlessui-menu-button-:r0:"
type="button"
aria-haspopup="menu"
aria-expanded="false"
data-headlessui-state="open"
aria-controls="headlessui-menu-items-:r1:"
onClick={() => setShow(!show)}
>
<div className="cursor-pointer flex justify-end gap-3 items-center">
<img className="rounded-full w-[7%]" src={item.profilepicture} alt={item.id} />
<p>{item.name}</p>
</div>
</button>
</div>
}
{show && (
<div
className="absolute right-0 z-10 mt-2 w-[20vw] origin-top-right rounded-md bg-white shadow-lg focus:outline-none transform opacity-100 scale-100"
aria-labelledby="headlessui-menu-button-:r0:"
id="headlessui-menu-items-:r7:"
role="menu"
tabIndex={0}
data-headlessui-state="open"
>
<div className=" py-1" role="none">
<div className="flex flex-col justify-center items-center gap-1 pb-2 " role="none">
<img className="rounded-full" src={item.profilepicture} alt="" width="50%" role="none"></img>
<p role="none">{item.name}</p>
<p className="text-[14px] text-gray-400" role="none">
{item.email}
</p>
<div className="border-t border-gray-300 ">
<button className="mt-2 rounded-[20px] px-2 py-1 text-white bg-[#e15b22]" role="none" onClick={Logout}>
Sign out
</button>
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
<div className="block">
<div className="w-full h-full flex mt-5">
<div className="border-r border-gray-300 h-full w-[40%]">
{
<div className="flex flex-col w-full items-center h-full justify-center">
<img className="rounded-full w-[48%]" src={item.profilepicture} alt="" />
<p className="text-[16px] font-[400] text-gray-800">{item.name}</p>
<div className="flex flex-col gap-2 border-b border-gray-300 py-3">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">Username:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.username}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">email:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.email}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">phoneNumber:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.phone}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">website:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.website}</p>
</div>
</div>
<div className="flex flex-col gap-2 py-3">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">company:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.company.name}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">bs:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.company.bs}</p>
</div>
</div>
</div>
}
</div>
<div className="h-full w-[60%]">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">address:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.address.city}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Profile;
Data.js
export const list = [
{
id: 1,
name: ' Leanne Graham',
username: 'Bret',
email: 'Sincere#april.biz',
profilepicture: 'https://panorbit.in/wp-content/uploads/2019/hotlink-ok/1001.jpeg',
address: {
street: 'Kulas Light',
suite: 'Apt. 556',
city: 'Gwenborough',
zipcode: '92998-3874',
geo: {
lat: '-37.3159',
lng: '81.1496',
},
},
phone: '1-770-736-8031 x56442',
website: 'hildegard.org',
company: {
name: 'Romaguera-Crona',
catchPhrase: 'Multi-layered client-server neural-net',
bs: 'harness real-time e-markets',
},
},
{
id: 2,
name: ' Ervin Howell',
username: 'Antonette',
email: 'Shanna#melissa.tv',
profilepicture: 'https://panorbit.in/wp-content/uploads/2019/hotlink-ok/1002.jpeg',
address: {
street: 'Victor Plains',
suite: 'Suite 879',
city: 'Wisokyburgh',
zipcode: '90566-7771',
geo: {
lat: '-43.9509',
lng: '-34.4618',
},
},
phone: '010-692-6593 x09hotlink-ok5',
website: 'anastasia.net',
company: {
name: 'Deckow-Crist',
catchPhrase: 'Proactive didactic contingency',
bs: 'synergize scalable supply-chains',
},
}
]
The issue is that in the Profile component the initial item state is an empty object {} which is fine when only accessing root-level properties, i.e. item.name, item.company, etc, but the issue is that during the initial render cycle and any subsequent render cycle until the item state is populated all the root-level properties will be undefined, so an error is thrown when attempting to access a second-level property like item.company.name. item.company is undefined and attempting to access item.company.name will throw the exception.
The item "state" is completely unnecessary as it's what we would call derived state, in that it is derived from the current id route path parameter and the imported list array. Derived state shouldn't be stored in local state unless absolutely necessary.
To resolve the issue simply compute the item value from the list and id values. I recommend also converting the data's id property to a String instead of parsing the route path parameter to an integer as it's generally easier to compare number-like strings.
Example:
import React from 'react';
import { useEffect, useState } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { list } from './Data';
function Profile() {
const navigate = useNavigate();
const { id } = useParams();
const item = list.find((value) => String(value.id) === id);
const [show, setShow] = useState(false);
const Logout = () => {
navigate('/');
};
if (!item) {
// No matching item found, render nothing, error message, etc, it's up to you
return <div>No matching item found.</div>;
}
return (
<div>
....
</div>
);
}
If the "cost" of computing the item value is "expensive" you can memoize the value with the useMemo hook.
const item = useMemo(() => {
return list.find((value) => String(value.id) === id)
}, [id]);
In fact, just about any time you see you've implemented the pattern useState + useEffect you should really be using useMemo.
You just need to change the initial state value to const [item, getItem] = useState(null); and then check if the item exists, render the data otherwise not.
Please replace you UserDetail.js file that file which I updated in my answer.
import React from 'react';
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import { list } from './Data';
function Profile() {
const [item, setItem] = useState(null);
const [show, setShow] = useState(false);
const { id } = useParams();
const navigate = useNavigate();
useEffect(() => {
getItem();
}, []);
const getItem = () => {
const newItem = list.find((value) => value.id === parseInt(id));
setItem(newItem);
};
const Logout = () => {
navigate('/');
};
return (
<div>
<div className="w-screen h-screen flex gap-10 px-8 pt-3 overflow-x-hidden">
<div className="flex flex-col w-full">
<div className="h-[13vh] w-[100%] border-b border-gray-300">
<div className="flex h-full items-center justify-between">
<div className="capitalize text-[20px] text-gray-500">
<p>profile</p>
</div>
<div className="relative inline-block text-left mt-5 ml-40" data-headlessui-state>
{item &&
<div>
<button
className="inline-flex w-full justify-center rounded-md bg-white px-4 py-2 text-sm font-medium text-gray-700 shadow-sm focus:outline-none "
id="headlessui-menu-button-:r0:"
type="button"
aria-haspopup="menu"
aria-expanded="false"
data-headlessui-state="open"
aria-controls="headlessui-menu-items-:r1:"
onClick={() => setShow(!show)}
>
<div className="cursor-pointer flex justify-end gap-3 items-center">
<img className="rounded-full w-[7%]" src={item.profilepicture} alt={item.id} />
<p>{item.name}</p>
</div>
</button>
</div>
}
{show && (
<div
className="absolute right-0 z-10 mt-2 w-[20vw] origin-top-right rounded-md bg-white shadow-lg focus:outline-none transform opacity-100 scale-100"
aria-labelledby="headlessui-menu-button-:r0:"
id="headlessui-menu-items-:r7:"
role="menu"
tabIndex={0}
data-headlessui-state="open"
>
<div className=" py-1" role="none">
<div className="flex flex-col justify-center items-center gap-1 pb-2 " role="none">
<img className="rounded-full" src={item.profilepicture} alt="" width="50%" role="none"></img>
<p role="none">{item.name}</p>
<p className="text-[14px] text-gray-400" role="none">
{item.email}
</p>
<div className="border-t border-gray-300 ">
<button className="mt-2 rounded-[20px] px-2 py-1 text-white bg-[#e15b22]" role="none" onClick={Logout}>
Sign out
</button>
</div>
</div>
</div>
</div>
)}
</div>
</div>
</div>
<div className="block">
<div className="w-full h-full flex mt-5">
<div className="border-r border-gray-300 h-full w-[40%]">
{item &&
<div className="flex flex-col w-full items-center h-full justify-center">
<img className="rounded-full w-[48%]" src={item.profilepicture} alt="" />
<p className="text-[16px] font-[400] text-gray-800">{item.name}</p>
<div className="flex flex-col gap-2 border-b border-gray-300 py-3">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">Username:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.username}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">email:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.email}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">phoneNumber:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.phone}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">website:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.website}</p>
</div>
</div>
<div className="flex flex-col gap-2 py-3">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">company:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.company.name}</p>
</div>
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">bs:</p>
<p className="text-[16px] font-[400] text-gray-800">{item.company.bs}</p>
</div>
</div>
</div>
}
</div>
<div className="h-full w-[60%]">
<div className="flex gap-3 items-center justify-center">
<p className="text-[16px] font-[400] text-gray-400">address:</p>
<p className="text-[16px] font-[400] text-gray-800">{item?.address?.city}</p>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
export default Profile;
Your initial state is a empty object {} which means the property name is not defined yet.
To resolve this you can use optional chaining.
<p>{item?.name}</p>
Same for all the other expression which use item.
Now for the useEffect, this will currently only ran the very first render. But will not update once you revisit to check another user. To fix this you can pass the id as a dependency.
useEffect(() => {
getItem();
}, [id]);

React - Modal inside map function receives wrong ._id

I've a map that creates edit and delete buttons.
The delete button needs to be confirmed.
So I created a modal.
console.log(additive._id) at the first delete button gives me the correct ._id
the confirm button inside the modal gives me always the last ._id of the array i map through, so it'll always delete the last item.
How can i get the correct ._id?
{additives.sort(byId).map((additive) => (
<div key={additive._id}>
<div>
<div </div>
<div>{additive.additive[0]?.label}</div>
</div>
<div className='flex justify-between items-center'>
<button
onClick={() =>
editAdditive(
additive._id,
additive.additive[0]?.value,
additive.additive[0]?.label
)
}>
Edit
</button>
<button
onClick={() => {
setShowModal(!showModal);
console.log(additive._id);
}}
>
Delete
</button>
{showModal && (
<>
<div
className='backdrop'
onClick={(e) => {
if (e.target.className === 'backdrop') {
setShowModal(false);
}
}}
>
<div className='relative w-auto my-6 mx-auto max-w-3xl'>
<div className=' rounded-lg shadow-2xl relative flex flex-col w-full bg-white outline-none focus:outline-none'>
<div className='flex items-start justify-between p-5 border-b border-solid border-slate-200 rounded-t bg-gray-600'>
<h3 className='text-3xl font-semibold text-red-500'>
Delete Additive
</h3>
</div>
<div className='relative p-6 flex-auto bg-gray-600'>
<p className='my-4 text-white text-lg leading-relaxed'>
Are you sure?
</p>
</div>
<div className='flex items-center justify-end p-6 border-t border-solid border-slate-200 rounded-b bg-gray-600'>
<button
className='text-red-500 background-transparent font-bold uppercase px-6 py-2 text-sm outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150'
type='button'
onClick={() => setShowModal(!showModal)}
>
Cancel
</button>
<button
className='bg-red-500 text-white active:bg-red-600 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150'
type='button'
onClick={() => {
deleteAdditive(additive._id);
setShowModal(!showModal);
console.log(additive._id);
}}
>
Confirm
</button>
</div>
</div>
</div>
</div>
</>
)}
</div>
</div>
))}
store the additive id in a state and use it in the modal
const [additiveId, setAdditiveId] = useState()
<button
onClick={() => {
setShowModal(!showModal);
setAdditiveId(additive._id)
console.log(additive._id);
}}
>
Delete
</button>
<button
className='bg-red-500 text-white active:bg-red-600 font-bold uppercase text-sm px-6 py-3 rounded shadow hover:shadow-lg outline-none focus:outline-none mr-1 mb-1 ease-linear transition-all duration-150'
type='button'
onClick={() => {
deleteAdditive(additiveId);
setShowModal(!showModal);
console.log(additiveId);
}}
>
Confirm
</button>

Tailwindcss show/hide transition

I'm making a react app with tailwindcss, and I want to make a hidden mobile navbar and when the user click on the icon it appears.
So I want to make a transition while the menu appears.
I use:
React
Tailwindcss
Headlessui
My Code:
MobileMenu.js:
function MobileMenu() {
return (
<div className="block md:hidden px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700">
<div className="flex items-center mb-3 pb-3 border-b border-slate-700">
<img
src="https://africaprime.com/wp-content/uploads/2020/04/ElonMusk.jpg"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="mobile-nav-icon">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="mobile-nav-icon">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="mobile-nav-icon">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
How I show it in Navbar.js:
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
{mobileMenuOpen && <MobileMenu />}
</>
);
}
export default Navbar;
Thanks in advance!
You can use #framer/motion package that allows you easily animate elements.
const menuVariants = {
open: {
opacity: 1,
x: 0,
},
closed: {
opacity: 0,
x: '-100%',
},
}
Animations can be changed however you want according to #framer/motion docs.
And attach variants to your <MobileMenu /> component.
function MobileMenu({isMenuOpen}) {
return (
<motion.div animate={isMenuOpen ? 'open' : 'closed'}
variants={menuVariants}> className="block md:hidden px-4 py-3 text-white w-full bg-gray-800 border-t border-opacity-70 border-slate-700">
<div className="flex items-center mb-3 pb-3 border-b border-slate-700">
<img
src="https://africaprime.com/wp-content/uploads/2020/04/ElonMusk.jpg"
className="rounded-full w-8 h-8 cursor-pointer"
/>
<h6 className="ml-5 cursor-pointer">Elon Musk</h6>
</div>
<div className="mobile-nav-icon">
<ImHome size={20} />
<h4 className="ml-5">Home</h4>
</div>
<div className="mobile-nav-icon">
<HiUsers size={20} />
<h4 className="ml-5">Friends</h4>
</div>
<div className="mobile-nav-icon">
<CgProfile size={20} />
<h4 className="ml-5">My Profile</h4>
</div>
</div>
);
}
export default MobileMenu;
And you can pass isMenuOpen variable as a prop.
function Navbar() {
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
return (
<>
<nav className="flex justify-between items-center px-4 lg:px-8 py-3 bg-gray-900 text-white">
{/* Mobile Menu Icon */}
<div
className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
<FiMenu size={20} />
</div>
</nav>
{/* Mobile Menu */}
{mobileMenuOpen && <MobileMenu isMenuOpen={mobileMenuOpen}/>}
</>
);
}
export default Navbar;
One potential issue with the provided answer is that state is not being set correctly. See the React Docs about updating state based on prior state value.
<div className="block md:hidden p-2 cursor-pointer rounded-full hover:bg-gray-700 transition-2" onClick={() => setMobileMenuOpen(prevState => !prevState)}>

Why is my Next.js router reloading the page, even with shallow routing

I am creating a search page. You can have a search query, e.g. "Nike" and then you can click different categories or conditions.
My file structure in pages is just:
search > index.tsx
When a user changes a category or condition, the search works correctly and finds the correct results. The search query is also correct. Here is a sample query:
/search?searchQuery=nike&category=clothes
However, the page reloads, which causes UI elements to move around, expanding menu's close etc.
I need the page to research without reloading, I read that using shallow: true should avoid this behaviour but it isn't working at the moment. I am only updating the query params.
Here is my (search) index.tsx:
export default function Search() {
const [listings, setListings] = useState<null | Array<ListingObject>>(null);
const [noResultsListings, setNoResultsListings] = useState<
Array<ListingObject>
>([]);
const [loading, setLoading] = useState("idle");
const [mobileFiltersOpen, setMobileFiltersOpen] = useState(false);
const [sortBy, setSortBy] = useState("listings");
const [currentPage, setCurrentPage] = useState(0);
const [paginationData, setPaginationData] = useState<PaginationData>({
totalHits: 0,
totalPages: 0,
});
const router = useRouter();
const { query, isReady } = useRouter();
let searchClient;
if (process.env.NODE_ENV === "development") {
searchClient = algoliasearch(
process.env.NEXT_PUBLIC_ALGOLIA_TEST_APP!,
process.env.NEXT_PUBLIC_ALGOLIA_TEST_API!
);
}
useEffect(() => {
if (query && sortBy && loading === "idle" && isReady) {
console.log("Search use effect ran");
search(query, 0);
}
}, [isReady, query]);
//============================================
//When a search filter checkbox is checked/unchecked, this is called
//============================================
function handleCheck(checked: Boolean, id: string, option) {
let tempQuery = query;
if (checked) {
if (tempQuery[id]) {
tempQuery[id] += `,${option.value}`;
} else {
tempQuery[id] = `${option.value}`;
}
}
if (!checked) {
if (tempQuery[id] && tempQuery[id].toString().split(",").length > 1) {
let param = tempQuery[id].toString();
let array = param.split(",");
tempQuery[id] = array.filter((param) => param !== option.value);
} else {
delete query[id];
}
}
router.push(
{
pathname: `/search`,
query: tempQuery,
},
undefined,
{ shallow: true }
);
}
//============================================
//Where the "search" actually happens.
//============================================
async function search(query, page: number) {
if (loading === "idle") {
setListings(null);
setLoading("loading");
let listingIndex = searchClient.initIndex(sortBy);
const readyQuery = await handleSearchQuery(query);
const response = await listingIndex.search(readyQuery.searchQuery, {
numericFilters: readyQuery.filters.numericFilters,
facetFilters: readyQuery.filters.facetFilters,
page: page,
});
if (response) {
const hits = response.hits;
setPaginationData({
totalPages: response.nbPages,
totalHits: response.nbHits,
});
console.log(hits);
setListings(hits);
setLoading("idle");
}
}
}
//=========================================================
//When a user changes page, uses npm package react-paginate
//=========================================================
const handlePageClick = (data) => {
let selected = data.selected;
setCurrentPage(data.selected);
search(query, selected);
window.scrollTo(0, 0);
};
return (
<div className="flex flex-col items-center bg-white min-h-screen">
<Navbar page={"Search"} />
<div className="container">
{/* Mobile filter dialog */}
<Transition.Root show={mobileFiltersOpen} as={Fragment}>
<Dialog
as="div"
className="fixed inset-0 flex z-40 lg:hidden"
onClose={setMobileFiltersOpen}
>
<Transition.Child
as={Fragment}
enter="transition-opacity ease-linear duration-300"
enterFrom="opacity-0"
enterTo="opacity-100"
leave="transition-opacity ease-linear duration-300"
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="fixed inset-0 bg-black bg-opacity-25" />
</Transition.Child>
<Transition.Child
as={Fragment}
enter="transition ease-in-out duration-300 transform"
enterFrom="translate-x-full"
enterTo="translate-x-0"
leave="transition ease-in-out duration-300 transform"
leaveFrom="translate-x-0"
leaveTo="translate-x-full"
>
<div className="ml-auto relative max-w-xs w-full h-full bg-white shadow-xl py-4 pb-12 flex flex-col overflow-y-auto">
<div className="px-4 flex items-center justify-between">
<h2 className="text-lg font-medium text-gray-900">Filters</h2>
<button
type="button"
className="-mr-2 w-10 h-10 bg-white p-2 rounded-md flex items-center justify-center text-gray-400"
onClick={() => setMobileFiltersOpen(false)}
>
<span className="sr-only">Close menu</span>
<XIcon className="h-6 w-6" aria-hidden="true" />
</button>
</div>
{/* Filters */}
<form className="mt-4 border-t border-gray-200">
{filters.map((section) => (
<Disclosure
as="div"
key={section.id}
className="border-t border-gray-200 px-4 py-6"
>
{({ open }) => (
<>
<h3 className="-my-3 flow-root">
<Disclosure.Button className="px-2 py-3 bg-white w-full flex items-center justify-between text-gray-400 hover:text-gray-500">
<span className="font-medium text-gray-900">
{section.name}
</span>
<span className="ml-6 flex items-center">
{open ? (
<MinusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
) : (
<PlusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
)}
</span>
</Disclosure.Button>
</h3>
<Disclosure.Panel className="pt-6">
<div className="space-y-6">
{section.options.map((option, optionIdx) => (
<div
key={option.value}
className="flex items-center"
>
<input
checked={router?.query[
section.id
]?.includes(option.value)}
id={`filter-mobile-${section.id}-${optionIdx}`}
name={`${section.id}[]`}
defaultValue={option.value}
type="checkbox"
className="h-4 w-4 border-gray-300 rounded text-indigo-600 focus:ring-indigo-500"
/>
<label
htmlFor={`filter-mobile-${section.id}-${optionIdx}`}
className="ml-3 min-w-0 flex-1 text-gray-500"
>
{option.label}
</label>
</div>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
))}
</form>
</div>
</Transition.Child>
</Dialog>
</Transition.Root>
<main className="w-full mx-auto px-4 md:px-0">
{listings?.length > 0 && (
<div className="flex flex-col relative z-30 md:items-center justify-between pb-6 md:flex-row">
<div className="flex items-center mt-8">
<Menu as="div" className="relative inline-block text-left">
<Menu.Button className="group inline-flex justify-center text-sm font-medium text-gray-700 hover:text-gray-900">
{" "}
{sortOptions.find((item) => item.value === sortBy).label}
<ChevronDownIcon
className="flex-shrink-0 -mr-1 ml-1 h-5 w-5 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
</Menu.Button>
<Transition
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95"
>
<Menu.Items className="origin-top-right absolute left-0 md:right-0 mt-2 w-48 p-0 shadow-2xl bg-white ring-1 ring-black ring-opacity-5 focus:outline-none z-30">
<div>
{sortOptions.map((option) => (
<Menu.Item key={option.value}>
{({ active }) => (
<button
className={classNames(
sortBy === option.value
? "font-medium text-gray-900"
: "text-gray-500",
active ? "bg-gray-100" : "",
"block px-4 py-2 text-sm w-full text-left"
)}
onClick={() => {
setSortBy(option.value);
}}
>
{option.label}
</button>
)}
</Menu.Item>
))}
</div>
</Menu.Items>
</Transition>
</Menu>
<button
type="button"
className="p-2 -m-2 ml-4 sm:ml-6 text-gray-400 hover:text-gray-500 lg:hidden"
onClick={() => setMobileFiltersOpen(true)}
>
<span className="sr-only">Filters</span>
<FilterIcon className="w-5 h-5" aria-hidden="true" />
</button>
</div>
</div>
)}
<section className="flex w-full">
<div className="hidden md:grid grid-cols-1 lg:grid-cols-1 min-h-screen w-1/5 lg:pr-4">
{/* Filters */}
<form className="hidden h-full overflow-y-auto md:inline-block">
{listings?.length > 0 &&
filters.map((section) => (
<Disclosure
as="div"
key={section.id}
className="border-gray-200 py-6"
>
{({ open }) => (
<>
<h3 className="-my-3 flow-root">
<Disclosure.Button className="py-3 bg-white w-full flex items-center justify-between text-sm text-gray-400 hover:text-gray-500">
<span className="font-medium text-gray-900">
{section.name}
</span>
<span className="ml-6 flex items-center">
{open ? (
<MinusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
) : (
<PlusSmIcon
className="h-5 w-5"
aria-hidden="true"
/>
)}
</span>
</Disclosure.Button>
</h3>
<Disclosure.Panel className="pt-6">
<div className="space-y-4 px-1">
{section.options.map((option, optionIdx) => (
<div
key={option.value}
className="flex items-center"
>
<input
checked={
query[section.id] &&
query[section.id]
?.toString()
.split(",")
.includes(option.value)
? true
: false
}
id={`filter-${section.id}-${optionIdx}`}
name={`${section.id}[]`}
type="checkbox"
className="checkbox"
onChange={(e) => {
handleCheck(
e.target.checked,
section.id,
option
);
}}
/>
<label
htmlFor={`filter-${section.id}-${optionIdx}`}
className="ml-3 text-sm text-gray-600"
>
{option.label}
</label>
</div>
))}
</div>
</Disclosure.Panel>
</>
)}
</Disclosure>
))}
</form>
</div>
{/* Product grid */}
<ul
role="list"
className="h-full w-full grid grid-cols-1 py-4 gap-8 md:grid-cols-4 md:w-4/5 md:p-4 xl:gap-x-8"
>
{!listings && <SearchGridSkeleton />}
{listings?.length > 0 &&
listings.map((listing) => (
<ListingCard
key={listing.docID}
listing={listing}
type={"normal"}
/>
))}
{listings?.length === 0 && (
<div className="w-full h-full flex flex-col items-center justify-center col-span-4 relative py-8">
<h2 className="text-lg font-semibold text-center">
Oops, we found no results for "
<span className="underline text-blue-600">
{query.searchQuery}
</span>
"
</h2>
<div className="mt-16 flex flex-col justify-center items-center w-full md:w-3/4">
<h3 className="text-base font-semibold">
You may also like:
</h3>
<div className="w-full grid gap-8 mt-8 border-t pt-8 grid-cols-1 md:grid-cols-3">
{noResultsListings?.map((listing: ListingObject) => (
<ListingCard
className="col-span-1"
key={listing.docID}
type="normal"
listing={listing}
/>
))}
</div>
</div>
</div>
)}
</ul>
</section>
{listings?.length > 0 && (
<ReactPaginate
previousLabel={
<span className="flex items-center">
<ArrowNarrowLeftIcon
className="mr-3 h-5 w-5 text-gray-400"
aria-hidden="true"
/>{" "}
Previous
</span>
}
nextLabel={
<span className="flex items-center">
Next{" "}
<ArrowNarrowRightIcon
className="ml-3 h-5 w-5 text-gray-400"
aria-hidden="true"
/>
</span>
}
breakLabel={"..."}
breakClassName={"break-me"}
pageCount={paginationData.totalPages}
marginPagesDisplayed={2}
pageRangeDisplayed={10}
onPageChange={handlePageClick}
disabledClassName={"opacity-20"}
pageClassName={
"border-transparent text-gray-500 hover:text-gray-700 hover:border-gray-300 border-t-2 pt-4 px-4 inline-flex items-center text-sm font-medium"
}
containerClassName={
"border-t border-gray-200 px-4 flex items-center justify-center sm:px-0 pb-8"
}
activeClassName={"border-indigo-500 text-indigo-600"}
nextClassName={
"border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 ml-auto"
}
previousClassName={
"border-t-2 border-transparent pt-4 pr-1 inline-flex items-center text-sm font-medium text-gray-500 hover:text-gray-700 hover:border-gray-300 mr-auto"
}
initialPage={0}
/>
)}
</main>
</div>
<Footer />
</div>
);
}

sort data by filters, react js

I have created a card where it shows me data that has been passed to me, but I have to sort them with several different filters using buttons, the order would be from highest to lowest and from lowest to highest
const menu = (
<Menu>
<Menu.Item key="1" icon={<UserOutlined />}>
24 hours
</Menu.Item>
<Menu.Item key="2" icon={<UserOutlined />}>
48 hours
</Menu.Item>
<Menu.Item key="3" icon={<UserOutlined />}>
72 hours
</Menu.Item>
</Menu>
);
function Menutop(props) {
const [percent, setPercen] = useState(0);
useEffect(() => {
if (percent === 100) {
setTimeout(() => {
props.showTiendas(true);
}, 1000);
} else {
props.showTiendas(false);
}
}, [percent, props]);
return (
<>
<Progress value={percent} onUpdate={setPercen} duration={16} />
<div className="flex items-center bg-dark-04 px-4 py-3">
<div className="flex w-3/12">
Name
<BsArrowUpDown className="text-base ml-2" />
</div>
<div className="flex w-1/6 justify-center items-center">
capacity
<BsArrowUpDown className="text-base ml-2" />
</div>
<div className="card flex w-1/12 justify-center items-center">
<Dropdown overlay={menu}>
<Button>
Capacity alert <DownOutlined />
</Button>
</Dropdown>
</div>
<div className="flex w-1/6 justify-center items-center">
visitor
<BsArrowUpDown className="text-base ml-2" />
</div>
<div className="flex w-1/6 justify-center items-center">
Distancing alert
<BsArrowUpDown className="text-base ml-2" />
</div>
<div className="flex w-1/6 justify-center items-center ">
Capacity alert
<BsArrowUpDown className="text-base ml-2" />
</div>
<div className="flex h-3 w-1/6"></div>
<div
onClick={() => {
setPercen(0);
}}
className="w-1/12 flex justify-center items-center cursor-pointer bg-yellow"
>
<BsArrowRepeat className=" font-bold text-2xl" />
</div>
</div>
</>
);
}
export default Menutop;
The filters would be the ones I have put, name, capacity, Capacity alert, visitor, Distancing alert and Capacity alert, the only one that would change from smallest to largest would be name that would be in alphabetical order, I still have a button that would be ordered by hour that neither I know how to do it
this is where i show my data:
function Card(props) {
const [open, setOpen] = useState(false);
const trigger = useRef(null);
useEffect(() => {
if (trigger.current) {
trigger.current.style.transition = "0.35s";
}
}, [trigger]);
function toggleOpen() {
if (trigger.current) {
setOpen((open) => !open);
if (open) {
trigger.current.style.webkitTransform = "rotate(0deg)";
} else {
trigger.current.style.webkitTransform = "rotate(180deg)";
}
}
}
return (
<div>
<div className="w-full flex justify-center px-4 py-3">
<div className="flex w-3/12 px-2 items-center">
<div className="w-24 p-2 bg-dark-04 mr-4 rounded">
<img src={props.store_logo} className="" alt="" />
</div>
<div className="flex flex-col justify-center ">
<p className="font-semibold my-0">{props.store_name} </p>
<p className="text-xs my-0">{props.store_address} </p>
</div>
</div>
<div className="flex w-1/6 justify-center items-center px-2">
<div className="w-4/5 h-10 bg-green bg-green-700 rounded text-white font-bold flex justify-center items-center">
<p className="my-0 text-lg">{props.occupancy.occupancy}/{props.occupancy.capacity}</p>
<p className="ml-2 my-0">{props.occupancy.percentage}%</p>
</div>
</div>
<div className="flex w-1/12 justify-center">
<Chart />
</div>
<div className="flex w-1/6 justify-center items-center font-semibold">
<p className="my-0 text-lg">{props.visits}</p>
</div>
<div className="flex justify-center items-center font-semibold w-1/6">
<p className="my-0 text-lg">{props.alerts.conglomerations} Alertas</p>
</div>
<div className="flex justify-center items-center font-semibold w-1/6 ">
<p className="my-0 text-lg">{props.alerts.occupancy} Alertas</p>
</div>
<div className="card flex justify-center items-center w-1/6">
<Button className="mr-3">Analytics</Button>
<Button>Alerts</Button>
</div>
<div className="w-1/12 flex justify-center items-center">
<span
ref={trigger}
onClick={toggleOpen}
className=" w-4/12 flex justify-center items-center"
>
<BsChevronUp className=" text-2xl" />
</span>
</div>
</div>
<Info open={open} {...props} />
</div>
);
}
export default Card;
where I show my data is a component where I send it to call
this would be the data:
[
{
"name": "Samantha White",
"id": "484567489sda",
"address": "4116 Barton Creek Boulevard Austin, TX 78735 USA",
"logo": "https://sssssss.s3.amazonaws.com/ssss/express.png",
"occupancy": {
"capacity": 150,
"occupancy": 0,
"percentage": 0
},
"alerts": {
"conglomerations": 0,
"occupancy": 0
},
"visits": 2721
},
{
"name": "Jacqueline Wells",
"id": "sdasdx45616a4dsa5",
"address": "s15035 Highview Road Grand Junction, CO 81504 USA",
"store_logo": "ssssss.s3.amazonaws.com/ssss/lider.png",
"occupancy": {
"capacity": 150,
"occupancy": 0,
"percentage": 0
},
"alerts": {
"conglomerations": 0,
"occupancy": 0
},
"visits": 2069
}
]
you need to include your data as an array in the state.
After you've done that, you'll have to make a function where that array is reassigned to one sorted depending on which button the user has pressed.
In this last step the sort function will come in handy.

Categories