I'm trying to change the className of each component Card i have by clicking on them. If one is selected, the others one will not be and have their default className.
How my component Card is called :
const UserBookingData = ({ bookings }: any) => {
return (
<div className="col-span-6 py-2">
{bookings?.map((booking: any) => (
<Card key={`cardKey${nanoid()}`} booking={booking} />
))}
</div>
)
}
component Card :
const Card = ({ booking} : any) => {
const bookingDate = moment(booking?.startAt);
const [selected, setSelected] = useState(false);
const getBookingRef = (ref: string) => {
setSelected(false);
zusContext.setState({
bookingRef: ref,
bookingLoaded: true
})
setSelected(true);
};
return <div onClick={() => getBookingRef(booking?.ref)} className={`my-1 py-3 px-3 flex items-center rounded-lg ${selected ? 'cursor-default bg-white-dark' : 'bg-white group hover:bg-white-dark cursor-pointer'}`}>
<div><svg className="w-14 fill-current text-white-dark4" viewBox="0 0 150 150"><path d="M 75, 75 m -75, 0 a 75,75 0 1,0 150,0 a 75,75 0 1,0 -150,0"/></svg></div>
<div className="flex items-center justify-between w-full">
<div className={`ml-3 ${selected ? '' : 'flex flex-col'}`}>
<div className="flex">
<div className={`text-blue text-xs px-2 py-[3px] rounded-md ${selected ? 'bg-white' : 'bg-blue-light group-hover:bg-white' }`}>{bookingDate.utc().format('DD MMM YYYY')}</div>
</div>
<div className='truncate text-sm font-medium pt-1 max-w-[160px]'>{booking?.space?.title}</div>
<div className="text-sm text-dark text-[13px]">{booking?.space?.host?.firstname}</div>
</div>
<div><svg className="ml-3 mr-1 w-2 fill-current text-blue" viewBox="0 0 150 150"><path d="M 75, 75 m -75, 0 a 75,75 0 1,0 150,0 a 75,75 0 1,0 -150,0"/></svg></div>
</div>
</div>
}
I tried with the hook selected, but the problem is, if i clic on one, it doesn't go back to his default className when i select another one.
I think it might be because it's not re rendered ?
the best approach here is to manage the state at the parent component(UserBookingData) and use booking.ref to know which card is selected:
// UserBookingData
const UserBookingData = ({ bookings }: any) => {
const [selected, setSelected] = useState("");
return (
<div className="col-span-6 py-2">
{bookings?.map((booking: any) => (
<Card
key={`cardKey${nanoid()}`}
booking={booking}
selected={selected}
setSelected={setSelected}
/>
))}
</div>
)
}
// Card component from codesandbox
const Card = ({ booking, selected, setSelected }: any) => {
const color = selected === booking.ref ? "green" : "red";
const getBookingRef = (event: any) => {
setSelected(booking.ref);
console.log(event.currentTarget.className);
};
return (
<div
style={{ marginTop: "10px", backgroundColor: `${color}` }}
onClick={getBookingRef}
className={`default ${selected ? "green" : "red"}`}
>
{booking.space.host.firstname}
</div>
);
};
export default Card;
you can see a working example here
Related
I have a child and parent compoinents as follows,
When I click on the div I am not able to get the alert in parent component but when I keep button in child component I can able to get the aler. Not sure what I am doing, Can any one please help. Thanks.
Child Comp:
const PrimaryNavigation = ({ className, data, childToParent }: IPrimaryNavigation) => {
return (
<div className={`${className} w-full bg-black grid grid-cols-12 gap-4 py-12 pl-12`}>
<div className="relative col-span-2 md:col-span-1 w-5 h-5 md:w-6 md:h-6 cursor-pointer" onClick={() => { childToParent() }}>>
<Image src={searchSvg} layout="fill" objectFit="contain" alt="hamburgerIcon" />
</div>
<div className="relative col-span-2 md:col-span-1 md:w-6 md:h-6 w-5 h-5 cursor-pointer" >
<Image src={hamburgerSvg} layout="fill" objectFit="contain" alt="searchIcon" />
</div>
</div>
)
}
Parent Comp:
const Header = ({ data }: IHeader) => {
const childToParent = () => {
alert('hi')
}
return (
<div>
<header className="w-full md-0 md:md-6 bg-black grid grid-cols-12 gap-4">
<PrimaryNavigation className="col-span-11" data={data.primaryNavigationCollection.items} childToParent={childToParent} />
</header>
</div>
)
}
export default Header
I am loading data from firebase, so when I refresh, it takes time to load it. And added data are not present in the cart that,s why I am not getting any product in the cart. But I did follow the same process to store user data, and it,s working fine, which means I am getting a registered profile in console. Can anyone suggest me a solution for getting data in the cart?
sending data:
import { motion } from "framer-motion";
import React, { useEffect, useRef, useState } from "react";
import { MdShoppingBasket } from "react-icons/md";
import { actionType } from "../context/reducer";
import { useGlobalState } from "../context/stateProvider";
import NotFound from "../img/NotFound.svg";
const RowContainer = ({ flag, data, scrollValue }) => {
const [{ cartItems }, dispatch] = useGlobalState();
const [items, setItems] = useState([]);
const rowContainer = useRef();
const addToCart = () => {
dispatch({
type: actionType.SET_CARTITEMS,
cartItems: items,
});
localStorage.setItem("cartItems", JSON.stringify(items));
};
useEffect(() => {
addToCart();
}, [items]);
useEffect(() => {
rowContainer.current.scrollLeft += scrollValue;
}, [scrollValue]);
return (
<div
ref={rowContainer}
className={`w-full my-12 flex items-center ${
flag
? "overflow-x-scroll scrollbar-none scroll-smooth"
: "overflow-x-hidden flex-wrap justify-center"
}`}
>
{data && data.length > 0 ? (
data.map((item) => {
const { id, price, imageURL, calories, title } = item;
return (
<div
key={id}
className="w-350 min-w-[300px] md:min-w-[380px] md:my-10 backdrop-blur-lg mx-1 my-2 lg:mx-2 "
>
<div className="w-full flex flex-row items-center justify-between bg-white rounded-lg drop-shadow-lg py-2 px-4 hover:bg-whiteAlpha min-h-[150px]">
<motion.img
whileHover={{ scale: 1.2 }}
src={imageURL}
alt="img"
className="w-30 max-h-40 -mt-8 duration-100 drop-shadow-2xl"
/>
<div className="flex flex-col items-end justify-end gap-4">
<motion.div
whileTap={{ scale: 0.75 }}
className="w-8 h-8 rounded-md bg-orange-500 hover:bg-orange-600 "
onClick={() => setItems([...new Set(cartItems), item])}
>
<MdShoppingBasket className="text-white text-2xl m-1" />
</motion.div>
<div className=" w-full">
<p className="text-gray-800 md:text-lg text-base text-right">
{title}
</p>
<p className="text-sm text-gray-500 text-right">
<span className="text-orange-600">$</span> {price}
</p>
</div>
</div>
</div>
</div>
);
})
) : (
<div className="w-full flex flex-col items-center justify-center">
<img src={NotFound} className="h-340" alt="" />
<p className="text-center my-2 text-xl text-red-500">No data found</p>
</div>
)}
</div>
);
};
export default RowContainer;
fetching data
export const fetchCart = () => {
const cartInfo =
localStorage.getItem("cartItems") !== "undefined"
? JSON.parse(localStorage.getItem("cartItems"))
: localStorage.clear();
return cartInfo ? cartInfo : [];
};
You are clearing your data for no reason
localStorage.getItem("cartItems") !== "undefined"
should be
localStorage.getItem("cartItems") !== undefined
I have create a refinement component that sends data to its parent and it works correctly. after data goes to parent component, it will save to an array with useState. array value is ok when data from child component is incremental, but when the value from child component is decremented, the array value is not correct.
Here's the code:
Refinement.jsx
import { Fragment, useState } from "react";
const Refinement = (props) => {
let [fileTypeSelected, setFileTypeSelected] = useState([]);
let changingState = "";
const CheckBoxHandler = (event, content) => {
if (event) {
fileTypeSelected.push(content);
changingState = "Incremental"
}
else {
fileTypeSelected = fileTypeSelected.filter((c) => c !== content);
changingState = "Decremental"
}
}
return (
<Fragment>
<div className="w-full rounded-md overflow-hidden shadow-sm border">
<div className="flex justify-between items-center px-4 py-1 bg-biscay-700 text-white border-b">
<span className="font-semibold ">{props.RefineName}</span>
</div>
{
<div className={`px-4 py-2 w-full flex flex-col gap-2 transform transition-all duration-200 ${props.RefineValue.length <= 5 ? "" : "h-36 overflow-y-scroll scrollbar"} `}>
{
props.RefineValue.map(m => {
return (
<div className="text-sm flex justify-between items-center" key={m.id}>
<span>{m.displayName}</span>
<input className="accent-sky-500 w-4 h-4 rounded-md" onChange={(event) => {
CheckBoxHandler(event.target.checked, m.displayName);
props.onDataModified({
SearchCompnent: props.RefineName,
SearchItem: fileTypeSelected,
SearchState: changingState
});
}} type="checkbox" name="" id="" />
</div>
)
})
}
</div>
}
</div>
</Fragment>
)
}
export default Refinement;
App.jsx
import { Fragment, useState } from "react";
import Refinement from "../components/Refinement";
const App = () => {
const [fileFormatRefine, setfileFormatRefine] = useState([
{ id: 0, displayName: 'PDF' },
{ id: 1, displayName: 'DOCX' },
{ id: 2, displayName: 'PPTX' },
{ id: 3, displayName: 'XLSX' },
{ id: 4, displayName: 'TXT' },
{ id: 5, displayName: 'MP3' },
{ id: 6, displayName: 'MP4' },
{ id: 7, displayName: 'CS' },
]);
const [filterFormatModifiedDate, setFilterFormatModifiedDate] = useState([]);
let FileFormatQueryTemp = [];
const FileFormatModifiedEnvent = (enterdModifiedData) => {
const modifiedData = {
...enterdModifiedData
};
FileFormatQueryTemp = [];
for (let index = 0; index < modifiedData.SearchItem.length; index++) {
FileFormatQueryTemp.push([[modifiedData.SearchCompnent, modifiedData.SearchItem[index]]]);
}
setFilterFormatModifiedDate([]);
console.log(modifiedData.SearchItem.length);
setFilterFormatModifiedDate(FileFormatQueryTemp);
}
return (
<Fragment>
<div className="w-full lg:w-5/6 mx-auto flex gap-4 mt-16 px-4">
<div className="w-1/4 col-start-1 hidden lg:flex">
<form className="flex flex-col gap-4 w-full" id="Form" onSubmit={(event) => { event.preventDefault() }}>
<Refinement onDataModified={FileFormatModifiedEnvent} MinToShow={3} RefineValue={fileFormatRefine} RefineName="File Format"></Refinement>
<button className="h-8 text-white font-semibold text-sm rounded bg-red-600" onClick={() => { console.log(filterFormatModifiedDate.length) }}>Show Array Length</button>
</form>
</div>
<div className="lg:w-3/4 w-full flex flex-col gap-2">
<div className="w-full">
<h3 className="text-xs flex items-center font-semibold uppercase mb-2">Filters
<span className="h-4 w-4 ml-2 text-xs flex items-center justify-center rounded bg-gray-200 text-black">{filterFormatModifiedDate.length}</span>
</h3>
<div className="">
{
filterFormatModifiedDate.map(f => {
return (
<p key={f[0][1]} >{f[0]}</p>
)
})
}
</div>
</div>
</div>
</div>
</Fragment>
)
}
export default App;
if you wanna change fileTypeSelected state, you should do next:
setFileTypeSelected(prevArray => [...prevArray, content])
and in else scope:
setFileTypeSelected(prevValue => prevValue.filter((c) => c !== content));
You must always change state using his function, which comes inside array as second argument
let's consider this example
const [count,setCount] = useState(0)
setCount is the only function that can change count value
From firebase I fetch data and map this data to be shown in cards, every card has edit component and CardComponent(edit component parent) which use ref provided from useHandleOpen custom hook
Error message:
Function components cannot be given refs. Attempts to access this ref will fail. Did you mean to use React.forwardRef()?
Component where ref is in use
export default function EditCard(id) {
const { ref, isOpen, setIsOpen } = useHandleOpen(false);
return (
<div>
<GoKebabVertical
ref={ref}
className="cursor-pointer "
onClick={() => setIsOpen(true)}
/>
{isOpen && (
<div
ref={ref}
className="w-20 h-15 bg-white z-30 rounded-md absolute text-center
top-0 right-0
"
>
<p className="hover:bg-blue-300 hover:text-white cursor-pointer">
Edytuj
</p>
<p
className="bg-red-400 text-white font-semibold hover:bg-red-600 cursor-pointer"
onClick={() => {}}
>
UsuĊ
</p>
</div>
)}
</div>
);
}
export const Edit = memo(EditCard);
Card component which use ref tho and its Edit parent
const Card = ({ data, id }) => {
const editRef = useRef(null);
const { ref, isOpen, setIsOpen } = useHandleOpen(editRef);
return (
<div
className="bg-gradient-to-r from-blue-200 to-purple-500
w-64 h-28 rounded-md relative
"
>
<div className="relative top-0 left-0">
<p className="px-2 pt-1 font-bold text-gray-800 text-lg">
{data.subject_name}
</p>
<p className="px-2 text-xs text-gray-800">Sala 101</p>
</div>
<div
className="absolute top-0 right-0 rounded-r-md
rounded-b-none
bg-white w-6 h-7
grid place-items-center
"
>
<Edit id={id} />
</div>
<div className="absolute bottom-9 left-0 mb-2.5">
<p className="px-2 text-xs text-gray-800">{data.teacher}</p>
</div>
<div className=" flex direction-row mt-7 text-center w-full justify-end align-bottom pr-2 ">
<div className="rounded-lg w-14 h-7 mx-3 bg-purple-300">
<a
href={data.link_subject}
className="text-gray-800
"
target="_blank"
rel="noreferrer"
>
Lekcja
</a>
</div>
<div className="rounded-lg w-14 h-7 bg-gray-800 ">
<a
href={data.link_online_lesson}
target="_blank"
rel="noreferrer"
className="text-white"
>
Online
</a>
</div>
</div>
<div
className=" absolute bottom-0 left-0 rounded-l-md
bg-white w-7 h-6 grid place-items-center devide-solid"
>
{isOpen ? (
<AiOutlineUp
className="cursor-pointer"
ref={ref}
onClick={() => setIsOpen(true)}
/>
) : (
<AiOutlineDown
className="cursor-pointer"
ref={ref}
onClick={() => setIsOpen(true)}
/>
)}
</div>
{isOpen && (
<div
className="bg-gradient-to-r from-blue-200 to-purple-500 w-full text-left rounded-b-md p-4 "
ref={ref}
>
<p className="font-bold text-gray-800 text-sm ">Lekcje w:</p>
<p className="text-gray-800 text-sm">PN: 8-12</p>
</div>
)}
</div>
);
};
export const CardSubject = memo(Card);
Custom hook with ref:
export default function useHandleOpen() {
const [isOpen, setIsOpen] = useState(false);
const ref = useRef(null);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(!isOpen);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, !isOpen);
return () => {
document.removeEventListener("click", handleClickOutside, !isOpen);
};
});
return { ref, isOpen, setIsOpen };
}
Edit: Tried change it this way, but this displays warning too.
export default function useHandleOpen(ref) {
const [isOpen, setIsOpen] = useState(false);
const handleClickOutside = (event) => {
if (ref.current && !ref.current.contains(event.target)) {
setIsOpen(!isOpen);
}
};
useEffect(() => {
document.addEventListener("click", handleClickOutside, !isOpen);
return () => {
document.removeEventListener("click", handleClickOutside, !isOpen);
};
});
return { ref, isOpen, setIsOpen };
}
And use hook like this:
const editRef = useRef(null);
const { ref, isOpen, setIsOpen } = useHandleOpen(editRef);
Ok i fixed it by removing ref from places where i use onClick method for changing the state
I have a dashboard and I want to use React-Grid-Layout but I render the components only if they have been favorited. To use the gird layout each div needs a key="a" or key="b" depending on the layout defined each key needs to be different.
How can I go about giving each div an individual key? When it renders on-screen in its current form it renders two divs with the same cards in where I need it to render one div for each card.
const layout = [
{ i: "a", x: 0, y: 0, w: 1, h: 2 },
{ i: "b", x: 1, y: 0, w: 3, h: 2, minW: 2, maxW: 4 },
{ i: "c", x: 4, y: 0, w: 1, h: 2 },
];
render function
const userHasFavorite = (data, favIds, layout) => {
if (favIds && data) {
const filteredData = data.filter((idsArr) =>
favIds.split(",").includes(idsArr.id)
);
const keysMapped = layout.map((ids) => ids.i);
console.log(keysMapped);
return (
<div key={keysMapped}>
<PriceCard data={filteredData} />
</div>
);
} else {
return <p1>No Cards Have Been Faved</p1>;
}
};
Grid Layout
<GridLayout
className="layout"
layout={layout}
cols={12}
rowHeight={30}
width={1200}
>
{isLoading ? (
<LoadingCard />
) : (
userHasFavorite(data, favoritedIds, layout)
)}
</GridLayout>
PRICE CARD
return (
<dl className="mt-5 ml-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{data.map((item) => (
<div
key={item.id}
className="flex-row bg-white hover:bg-gray-100 dark:hover:bg-purple-700 dark:bg-secondaryDark h-24 pt-2 px-4 pb-12 sm:pt-6 sm:px-6 shadow-2xl rounded-lg overflow-hidden"
>
<div className="flex h-2 ">
{/* TODO: ADD TRANSISITON */}
{cardFaved.some((cardFaved) => cardFaved.id === item.id) ? (
<MinusSmIcon
onClick={() => removeCardFavClick(item.id)}
className="ml-auto h-5 w-5 cursor-pointer fill-current text-red-600 "
/>
) : (
<PlusSmIcon
onClick={() => handleCardFavClick(item.id)}
className="ml-auto h-5 w-5 cursor-pointer fill-current text-green-600 "
/>
)}
</div>
<dt>
<div className="absolute rounded-md p-3">
<img className="h-6 w-6 mr-3" src={item.image} />
</div>
<p className="ml-16 text-sm pb-0 font-medium text-gray-500 dark:text-white truncate">
{item.name.toUpperCase()}
</p>
</dt>
<dd className="ml-16 pb-7 flex items-baseline sm:pb-7">
<p className="text-2xl font-semibold text-gray-900 dark:text-white">
{formatDollar(item.current_price)}
</p>
</dd>
</div>
))}
</dl>
);
};
export default PriceCard;
Error I'm having is the cards render 2 in one div
Error 2
Ok, so from the React-Grid-Layout official usage, You need to map() directly within the <GridLayout/> component.
So here a good suggestion:
Within the GridLayout:
WITH map()
<GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
{isLoading
? <LoadingCard />
: layout.map(({ i }) =>
<div key={i}>
// change this from a typical function into a React component
<UserHasFavorite data={data} favIds={favoritedIds} />
</div>
)}
</GridLayout>
WITHOUT map()
<GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
{isLoading
? <LoadingCard />
: <React.Fragment>
<div key="a"><UserHasFavorite data={data} favIds={favoritedIds} /></div>
<div key="b"><UserHasFavorite data={data} favIds={favoritedIds} /></div>
<div key="c"><UserHasFavorite data={data} favIds={favoritedIds} /></div>
</React.Fragment>}
</GridLayout>
Within the child component
const UserHasFavorite = (data, favIds) => {
if (favIds && data) {
const filteredData = data.filter((idsArr) =>
favIds.split(",").includes(idsArr.id)
);
// <div> is moved up into the GridLayout
return <PriceCard data={filteredData} />;
} else {
return <p1>No Cards Have Been Faved</p1>;
}
};
UPDATE: To handle issue of rendering multiple "similar" cards
Within the PriceCard, there is this...
return (
<dl className="mt-5 ml-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{data.map((item) => (
<div
key={item.id}
className="flex-row bg-white hover:bg-gray-100 dark:hover:bg-purple-700 dark:bg-secondaryDark h-24 pt-2 px-4 pb-12 sm:pt-6 sm:px-6 shadow-2xl rounded-lg overflow-hidden"
>
</div>
)}
</dl>
)
You are mapping to display all the cards in a row. So if you updated the className="flex-column..." those two cards should render in a Column but in one <div>.
And then if you do that, you have two options:
Either you delete the <GridLayout/> and render directly like this...
// <GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
return (
isLoading
? <LoadingCard />
: <UserHasFavorite data={data} favIds={favoritedIds} />
)
// </GridLayout>
By removing the <GridLayout/> which is used for display, you will have just two cards rendered in a column (handled with PriceCard component).
2.Or render the <PriceCard/> directly to within the like this...
// Parent component -> GridLayout
export const ParentComponent = props => {
// I'm not sure where "data" and "favoritedIds" are coming from yet,
// but we check like "data &&..." to ensure they are defined
// before we attempt to filter
const filteredData = data && data.filter(
(idsArr) => favoritedIds && favoritedIds.split(",").includes(idsArr.id)
);
return (
<GridLayout className="layout" layout={layout} cols={12} rowHeight={30} width={1200}>
{isLoading
? <LoadingCard />
: (!!data && !!filteredData)
? <React.Fragment>
<div key="a"><PriceCard data={filteredData[0]} /></div>
<div key="b"><PriceCard data={filteredData[1]} /></div>
<div key="c"><PriceCard data={filteredData[2]} /></div>
</React.Fragment>}
: <p1>No Cards Have Been Faved</p1>
</GridLayout>
)
}
// Child component -> PriceCard
export const ChildComponent = props => {
return (
<dl className="mt-5 ml-5 grid grid-cols-1 gap-5 sm:grid-cols-2 lg:grid-cols-3">
{data && /* don't map here -> we moved it to parent */
<div className="flex-row bg-white hover:bg-gray-100 dark:hover:bg-purple-700 dark:bg-secondaryDark h-24 pt-2 px-4 pb-12 sm:pt-6 sm:px-6 shadow-2xl rounded-lg overflow-hidden">
/* make necessary changes */
</div>}
</dl>
)
}
From what I read I think that you need to iterate and assign it to the div. You can do something like this.
return (
{filteredData.map(data => (
<div key={data.i}>
<PriceCard data={filteredData} />
</div>
)
);