So I have an overlay flex container that when clicked adds nominations to a movie. How do I make it so that it doesn't appear and thus doesn't give the option to click it once a movie has already been nominated? The click event is located in the MovieList container where the handleNominationsClick function is being taken from props and adding it to the onClick property in the overlay.
Here is App:
const [nominations, setNominations] = useState([]);
const nominateMovie = (movie) => {
const newNominationList = [...nominations, movie];
setNominations(newNominationList);
};
return (
<div className='container-fluid movie-app'>
<div className='row d-flex align-items-center mt-4 mb-4'>
<MovieListHeading heading='Movies' />
<SearchBox searchValue={searchValue} setSearchValue={setSearchValue} />
</div>
<div className='row'>
<MovieList
movies={movies}
nominationComponent={AddNominations}
handleNominationsClick={nominateMovie}
/>
</div>
<div className='row d-flex align-items-center mt-4 mb-4'>
<MovieListHeading heading='Nominations' />
</div>
MovieList Component:
onst MovieList = (props) => {
const NominationComponent = props.nominationComponent;
return (
<>
{props.movies.map((movie, index) => (
<div className='image-container d-flex justify-content-start m-3 col-3 d-flex flex-column'>
<img src={movie.Poster} alt='movie'></img>
<div
onClick={() => props.handleNominationsClick(movie)}
className='overlay d-flex align-items-center justify-content-center'
>
<NominationComponent />
</div>
</div>
))}
</>
);
};
I suggest storing the nominated movies in an object that provides O(1) lookup and pass the nominations as a prop to MovieList so the overlay div can be conditionally rendered. I am assuming your movies data has an id property (or some uniquely identifying property) to use. If your data doesn't have a unique identifier then I highly recommend augmenting it to include one so you can use a proper React key when mapping.
App
const [nominations, setNominations] = useState({}); // <-- object
const nominateMovie = (movie) => {
setNominations(nominations => ({
...nominations,
[movie.id]: true // <-- set movie nomination by id
}));
};
return (
<div className='container-fluid movie-app'>
<div className='row d-flex align-items-center mt-4 mb-4'>
<MovieListHeading heading='Movies' />
<SearchBox searchValue={searchValue} setSearchValue={setSearchValue} />
</div>
<div className='row'>
<MovieList
movies={movies}
nominationComponent={AddNominations}
nominations={nominations} // <-- pass nominations
handleNominationsClick={nominateMovie}
/>
</div>
MovieList
const MovieList = (props) => {
const NominationComponent = props.nominationComponent;
return (
<>
{props.movies.map((movie, index) => (
<div className='image-container d-flex justify-content-start m-3 col-3 d-flex flex-column'>
<img src={movie.Poster} alt='movie'></img>
{!props.nominations[movie.id] && ( // <-- conditional render
<div
onClick={() => props.handleNominationsClick(movie)}
className='overlay d-flex align-items-center justify-content-center'
>
<NominationComponent />
</div>
)}
</div>
))}
</>
);
};
Related
const questions = (props) => {
const { question, options, id } = props.quiz;
const selected = (e) => {
e.target.children[0].checked = true;
console.log(e.target);
};
return (
<div className="bg-gray-200 p-6 m-10 rounded-md">
<div className="font-bold text-xl text-slate-600">{question}</div>
<div className="grid grid-cols-2 mt-4">
{options.map((option) => (
<div className="border-2 border-solid border-gray-400 rounded text-gray-500 m-1">
<div className="flex cursor-pointer p-3" id={id} onClick={selected}>
<input type="radio" className="mr-2" name="name" />
{option}
</div>
</div>
))}
</div>
</div>
);
};
export default questions;
if console.log(e.target) is done in the selected function return the Div. How can I access the text of the Div within the selected function?
You can access the text of the Div within the selected function by using the .textContent property on the e.target object. This will return the text content of the Div that was clicked.
const selected = (e) => {
e.target.children[0].checked = true;
console.log(e.target.textContent);
};
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 have the following components : (AddBookPanel contains the AddBookForm)
I need to have the ability to display the form on click of the 'AddBookButton', and also
have the ability to hide the form while clicking the 'x' button (img in AddBookForm component), however the component doesn't seem to load properly, what could be wrong here? What do you think is the best way to organize this?
import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm initialFormActive={addBookPressed} />
</div>
);
};
import { useState } from 'react';
interface AddBookFormProps {
initialFormActive: boolean;
}
export const AddBookForm: React.FC<AddBookFormProps> = ({
initialFormActive,
}) => {
const [isFormActive, setIsFormActive] = useState(initialFormActive);
return (
<>
{isFormActive && (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={() => setIsFormActive(!isFormActive)}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};
You're always rendering the AddBookForm. That means in the initial render the useState is called with false. Since you never call setIsFormActive it is now stuck as "do not render".
Just don't use the useState and use the property initialFormActive directly. And maybe rename it to isFormActive. Let the parent handle the state change.
import { AddBookButton } from './AddBookButton';
import { AddBookForm } from './AddBookForm';
import { useState } from 'react';
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm initialFormActive={addBookPressed} onClose={() => setAddBookPressed(false)} />
</div>
);
};
import { useState } from 'react';
interface AddBookFormProps {
initialFormActive: boolean;
onColse: () => void;
}
export const AddBookForm: React.FC<AddBookFormProps> = ({
initialFormActive,
onClose,
}) => {
// const [isFormActive, setIsFormActive] = useState(initialFormActive); don't use this frozen state, allow the parent to control visibility
return (
<>
{initialFormActive && (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={() => onClose()}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};
Problem with your code is in how you tied initialFormActive to inner state of AddBookForm component. When looking at useState inside AddBookForm it is clear that prop initialFormActive only affects your initial state, and when looking at your parent component I can clearly see that you passed false as value for initialFormActive prop, thus initializing state inside AddBookForm with false value(form will stay hidden). Then you expect when you click on button, and when prop value changes(becomes true), that your <AddBookForm /> will become visible, but it will not because you just used initial value(false) from prop and after that completely decoupled from prop value change.
Since you want to control visibility outside of component, then you should attach two props to AddBookForm, isVisible and toggleVisibiliy. And do something like this:
export const AddBookPanel = () => {
const [addBookPressed, setAddBookPressed] = useState(false);
const toggleAddBookPressed = () => setAddBookPressed(p => !p);
return (
<div>
<div onClick={() => setAddBookPressed(!addBookPressed)}>
<AddBookButton />
</div>
<AddBookForm isVisible={addBookPressed} toggleVisibility={toggleAddBookPressed } />
</div>
);
};
export const AddBookForm: React.FC<AddBookFormProps> = ({
isVisible, toggleVisibility
}) => {
return (
<>
{isVisible&& (
<div className="fixed box-border h-2/3 w-1/3 border-4 left-1/3 top-24 bg-white border-slate-600 shadow-xl">
<div className="flex justify-between bg-slate-400">
<div>
<p className="text-4xl my-5 mx-6">Add a new Book</p>
</div>
<div className="h-16 w-16 my-3 mx-3">
<button onClick={toggleVisibility}>
<img
src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/a0/OOjs_UI_icon_close.svg/1200px-OOjs_UI_icon_close.svg.png"
alt="close-button"
className="object-fit"
></img>
</button>
</div>
</div>
<div className="flex-col">
<div className="flex justify-between my-10 ml-5 text-xl">
<input type="text" placeholder="book name"></input>
</div>
</div>
</div>
)}
</>
);
};
This question already has answers here:
Warning: Each child in a list should have a unique "key" prop
(2 answers)
Closed 1 year ago.
i am having the same issue here in the below code. I have added a key where think it is appropriete but still getting key warning message.
Hi guys, i am having the same issue here in the below code. I have added a key where think it is appropriete but still getting key warning message.
Can you help, please?
Leo
import { useState } from "react";
import { FaTimes } from "react-icons/fa";
import EditSkillsForm from "./editSkillsForm";
const SkillsList = ({ inputs, handleDeleteClick, handleCancelClick, handleEditClick, setIsEditing, handleUpdateInput,currentInput}) => {
const [ isEditing, setISEditing ] = useState(false);
//const [ currentSkill, setCurrentSkill ] = useState({});
handleEditClick = () => {
setISEditing(true);
console.log("editing", true)
}
return (
<div className="col-lg-4 col-sm-6 mb-4">
<div className="card p-2">
<div className="card-Header text-center mb-4">
<h4 className="card-title mt-2">Mastered Skills List</h4>
</div>
{inputs.map((skill, idx) => {
return (
<>
<div className="card width: 10rem; mb-3 p-2" key={idx}>
<div className="card-header text-center mb-4">
<h5 className="card-title mt-2">{skill}</h5>
</div>
<img src="../../logo.svg" className="card-img-top" alt="..." key={idx} />
<div className="card-body">
<div className="card-title text-center mt-2 " key={idx}>
{
isEditing ? (
<div key={idx}>
<EditSkillsForm
inputs={inputs}
editing={setIsEditing}
cancelClick= {handleCancelClick}
updateInput = {handleUpdateInput}
currentInput={currentInput}
/>
</div>
) : null
}
{/* <EditSkillsForm inputs={inputs} /> */}
<ol key={idx}>
Skill name: {skill}
</ol>
</div>
</div>
<div className="card-footer" >
<div className="row mb-2" key={idx}>
<button
className="btn btn-primary w-100"
onClick={() => handleEditClick(inputs.idx)}>Edit
</button>
</div>
<div className="row">
<FaTimes className="mb-2" fill="#ffc800" onClick={() => handleDeleteClick(idx)} key={idx} />
</div>
</div>
</div>
</>
)
}
)}
</div>
</div>
);
}
export default SkillsList;
the key must be unique for all the elements in your component. as I can see you using the same key idx in multiple div elements so it isnt unique anymore. try to make for each div a special name in addition of that idx value to make it unique for each one.
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>
)
);