I need to pass a prop (it's a unique id) from one child component to another, but I can't do it.
The operation is simple: Homepage is the parent and has two direct children: Gallery and Detail. Gallery using a map prints its children Cards, which have a unique id. I need that when clicking on a card button, that unique id can be received by Detail.
I tried to manage it through handleClick but since I already manage a state there, I don't know if it is possible to manage it from there or are other methods.
This is the code:
interface DetailProps {}
const Detail: React.FC<DetailProps> = (props) => {
return (
<div>
<span className="tituloJuego">DENTRO DE DETAIL</span>
</div>
);
};
interface CardProps {
id: number;
image: string;
title: string;
price: string;
onClick: (result: string) => void;
}
const Card: React.FC<CardProps> = (props) => {
return (
<div className="card">
<img className="image" src={props.image} />
<div className="card-info">
<div className="containerTitlePrice">
<span className="tituloJuego">{props.title}</span>
<br></br>
<span className="-USD">{props.price}</span>
</div>
<div className="containerButton">
<button className="Rectangle" onClick={(event) => props.onClick("detail")}>
<span className="Install-Game">BUY NOW</span>
</button>
</div>
</div>
</div>
);
};
interface GalleryProps {
onClick: (result: string) => void;
}
const Gallery: React.FC<GalleryProps> = (props) => {
return (
// Se recorren los datos de json con map y se imprime cada card
<div className="container">
{data.map((games, key) => {
return (
<Card
id={games.id}
image={games.image}
title={games.title}
price={games.price}
onClick={(event) => props.onClick("detail")}
/>
);
})}
</div>
);
};
const Homepage: React.FC = () => {
// Por defecto el estado muestra homepage
const [currentView, setCurrentView] = React.useState<string>("homepage");
const handleClick = () => setCurrentView("detail");
const [currentClickedGame, setCurrentClickedGame] = React.useState<number>(0);
return (
<div className="Simplified-homepage">
{currentView === "homepage" ? (
<div className="store-gallery">
<Gallery onClick={handleClick} />
</div>
) : (
<div className="">
<Detail />
</div>
)}
</div>
);
};
// ========================================
ReactDOM.render(<Homepage />, document.getElementById("root"));
Here is the json file. The data from this file is the data that im showing in the Cards. The "ID" is the number that I want to pass from Card to Homepage:
JSON file
You can pass the state currentClickedGame in both the child components for handling the ID.
You can't pass props on components in the same level or from child to parent as react follows unidirectional data flow .Also, instead of passing the handleClick from Homepage you can manage it inside Card component itself and set setCurrentView from there.
I've drilled props to 2 levels, consider using context in a more complex case
In HomePage:
return (
<div className="Simplified-homepage">
{currentView === "homepage" ? (
<div className="store-gallery">
<Gallery setCurrentClickedGame ={setCurrentClickedGame}/>
</div>
) : (
<div className="">
<Detail currentClickedGame={currentClickedGame}/>
</div>
)}
</div>
);
And in Gallery component:
return (
<div key={games.id}>
<Card
id={games.id}
image={games.image}
title={games.title}
price={games.price}
setCurrentView={props.setCurrentView}
setCurrentClickedGame={props.setCurrentClickedGame}
/>
</div>
);
View demo on Codesandbox
Related
I've got a set of dashboards that display in bootstrap cards on a front page and I would like to wrap them in a div with the class row for every 3rd entry. I was thinking about marking my dashboard component with the DB id from props and use a modulus function, but that will cause problems if an ID is deleted
Dashboard component:
export type DashboardProps = {
id: number
title: string
description: string
}
const Dashboard: React.FC<{ dashboard: DashboardProps }> = ({ dashboard }) => {
return (
<>
<div className="col-sm-12 col-lg-4">
<div className="card bg-light h-100">
<div className="card-header">
{dashboard.title}
</div>
<div className="card-body d-flex flex-column">
<p className="card-text">
{dashboard.description}
</p>
<a className="btn btn-info text-center mt-auto"
onClick={() =>
Router.push("/dashboard/[id]", `/dashboard/${dashboard.id}`)
}
>Go to dashboard</a>
</div>
</div>
</div>
</>
)
}
export default Dashboard
Index page:
type Props = {
dashboards: DashboardProps[]
}
export const getServerSideProps: GetServerSideProps = async () => {
const dashboards = await prisma.dashboard.findMany({
orderBy: { id: "asc", },
})
return {
props: JSON.parse(JSON.stringify({ dashboards })),
}
}
const Index: React.FC<Props> = (props) => {
const { data: session, status } = useSession()
if (status === "loading") {
return (
<Spinner />
)
}
if (!session) {
return (
<Layout>
<AccessDenied />
</Layout>
)
}
return (
<Layout>
<h1>Dashboards</h1>
{props.dashboards.map((dashboard) => (
<Dashboard dashboard={dashboard} />
))}
</Layout>
)
}
export default Index
I could also potentially wrap them in a single div with class row, but I would need to enforce a top/bottom margin so the cards don't stack right on top of each other. Any tips to get me rolling on this would be greatly appreciated!
.map provides index, you this to find every 3rd element.
//...
{
props.dashboards.map((dashboard, index) =>
(index + 1) % 3 === 0 ? (
<div>
<Dashboard key={dashboard.id} dashboard={dashboard} />
</div>
) : (
<Dashboard key={dashboard.id} dashboard={dashboard} />
)
)
}
I am trying to implement onClick event handle to get the details of the card. However, when clicking on it I am getting the details of some other card, not the card which I am trying to click. The Card component is recursive as I am creating a tree. Attaching the image for the reference.
const Card = (props: any) => {
const handleClick = (item: any) => {
console.log("This is value: ", item)
}
const [selectedOption, setSelectedOption] = useState(null);
return (
<ul>
{props.data.map((item: any, index: any) => (
<React.Fragment key={item.name}>
<li>
<div className="card">
<div className="card-body">
<p>{item.name}</p>
</div>
<div onClick={() => handleClick(item)}>
<Select
defaultValue={selectedOption}
onChange={handleChange}
className="select"
options={props.users}
/>
</div>
<div></div>
</div>
{item.children?.length && <Card data={item.children} users={[]} />}
</li>
</React.Fragment>
))}
</ul>
);
};
const AccountabilityChartComponent = () => {
return (
<div className="grid">
<div className="org-tree">
<Card
users={users}
data={hierarchy}
/>
</div>
</div>
);
};
export default AccountabilityChartComponent;
Currying the onClick handler is a useful technique. It's particularly convenient because the item and the click event are colocated within the same function -
function App({ items = [] }) {
const onClick = item => event => { // <--
console.log(event.target, item)
}
return <div>
{items.map((item, key) =>
<button key={key} onClick={onClick(item)} children={item.name} />
)}
</div>
}
const items = [
{ name: "apple", price: 3 },
{ name: "pear", price: 4 },
{ name: "kiwi", price: 5 }
]
ReactDOM.render(<App items={items} />, document.querySelector("#app"))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I am working on a development site and am having an issue. The issue is that I am looping over the data file in order to create some project cards. Each project card has a show more/show less button to display/hide card descriptions.
My problem is that the current setup is mapping over the data and causing it so that whenever one gets clicked, all three either open or close simultaneously. Please help me to fix this issue. Relevant code is shown below:
Data example:
{
name: "Hot in the Biscuit",
id: "3a34",
image: "/images/bonnie.jpg",
description: "A multi-page front-end business website for a local restaurant in Koh Samui, Thailand. Custom design built with vanilla JavaScript, HTML and CSS.",
link: "https://www.xxxxxxxxxxxxx.com",
date: "2021",
github: "https://github.com/xxxxxxxxxxxxxxxxxxxxxx"
},
Hero file where Showcase Component is rendered:
<h1>Featured Projects</h1>
<div>
<Showcase/>
</div>
Showcase where cards are created (UNNECCESSARY CODE REMOVED - classes and image):
const Showcase = () => {
const {readMore, setReadMore} = useContext(HeroContext)
const {toggleMenu} = useContext(NavbarContext)
return(
<>
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image/>
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{ readMore[id] ? <-- THIS IS WHERE YOU NEED AN ID
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore[id] ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub/>
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
})}
</>
)
}
export default Showcase
So I just need some help on figuring out how to set it up so that each button knows which card is being clicked and only that button open. Thank you very much for helping me. I appreciate your time and help immensely.
Bodi
It will be easier if you split showcase item to a new component.
const ShowCaseItem = ({ data }) => {
const { toggleMenu } = useContext(NavbarContext)
const [readMore, setReadMore] = useState(false)
const { id, name, image, link, github, description, date } = data;
return (
<div key={id}>
<div>
{!toggleMenu &&
<div>
<Image />
</div>
}
</div>
<div>
<div>
<h2>{name} | {date}</h2>
</div>
<div>
<h4>{readMore ?
description :
`${description.substring(0, 100)}...`
} <button key={id} onClick={() => setReadMore(!readMore)}>{readMore ? "Show Less" : "Show More"}</button>
</h4>
</div>
<div>
<a href={github}>
<FiGithub />
</a>
<a href={link}>
<h4 >See For Yourself! →</h4>
</a>
</div>
</div>
</div>
)
}
const Showcase = () => {
const { readMore, setReadMore } = useContext(HeroContext)
return (
<>
{showcase.map((item) => <ShowCaseItem data={item} />)}
</>
)
}
export default Showcase
You should update the HeroContext state to hold a reference to the ids that are shown/hidden.
Example:
const [readMore, setReadMore] = useState({});
const readMoreToggler = (id) => setReadMore(state => ({
...state,
[id]: !state[id], // <-- toggle boolean value
}));
// context value
{
readmore,
setReadMore: readMoreToggler, // pass readMoreToggler as setReadMore
}
...
const { readMore, setReadMore } = useContext(HeroContext);
...
{showcase.map((item) => {
const {id, name, image, link, github, description, date} = item;
return (
<div key={id}>
<div>
...
</div>
<div>
...
<div>
<h4>
{readMore[id] // <-- check by id if toggled true|false
? description
: `${description.substring(0, 100)}...`
}
<button
onClick={() => setReadMore(id)} // <-- pass id to toggle
>
{readMore[id] ? "Show Less" : "Show More"} // <-- check by id if toggled true|false
</button>
</h4>
</div>
<div>
...
</div>
)
})}
When I click on a card, the loadAboutInfo function works through which I transfer data to another component and display it there. But if I click again on the same card, then it is duplicated. How can I fix it?I have check which take card id and then if it the same it render but I click again it render one more card, but i need if it already exist than new card mustn't render
loadAboutInfo=(pokemonValue,pockemonImg,pokemonId)=>{
this.setState(prevState => ({
pokemonValue:[...prevState.pokemonValue, pokemonValue],
pockemonImg,
pokemonId
}))
}
render() {
return (
<div className="wrapper">
<div className="pokemonlist__inner__cards">
<div className="pokemonlist__cards">
{this.state.pokemonList.map((value,index)=>{
let pokemonImgTemplate = this.state.pokemonImgTemplate;
let pokemonId = value.id;
let pockemonImg = pokemonImgTemplate.replace('{id}',pokemonId);
return(
<div className="pokemonListCard" key={index} onClick={()=>this.loadAboutInfo(value,pockemonImg,pokemonId)}>
<PokemonCard
pockemonImg={pockemonImg}
pokemonName={value.name}
pokemonTypes={value.types}
/>
</div>
)
})}
</div>
<PokemonLoadMore
loadMore={this.loadMore}
currentPage={this.state.currentPage}
/>
</div>
</div>
);
}
}
component where i map get data
render() {
return (
<div className="pokemon__about">
{this.props.pokemonValue.map((value,index)=>{
let totalMoves = value.moves.length;
return(
<div className="pokemon__about__wrapper" key={index}>
{this.props.pokemonId == value.id ?
<div className="pokemon__about__inner" key={index}>
<AboutImage
pockemonImg={this.props.pockemonImg}
/>
<AboutName
pockemonName={value.name}
/>
<div className="pokemon__about__table">
<AboutPokemonTypes
pokemonTypes={value.types}
/>
<table>
<AboutPokemonWeight
pockemonWeight={value.weight}
/>
<AboutPokemonMoves
totalMoves={totalMoves}
/>
</table>
</div>
</div>
:
null
}
</div>
)
})}
</div>
);
On the loadAboutInfo you can check if there is already a pokemon with the same id on pokemonValue array, something like this:
loadAboutInfo = (pokemonValue,pockemonImg,pokemonId) => {
// this will get the first element that matches the id
const exists = this.state.pokemonValue.find(pokemon => pokemon.id === pokemonId)
if (!exists) {
this.setState(prevState => ({
pokemonValue:[...prevState.pokemonValue, pokemonValue],
pockemonImg,
pokemonId
}))
}
}
So it will update the state only if the clicked pokemon isn't in the pokemonValue array
I have this app that uses mobx, in it there is a component called "Listings" that uses some state from mobx to render a list of items.
The way it is right now, is that the Listings component gets the data it needs(store.restaurantResults[store.selectedFood]) from inside of it by using the mobx store like so:
const Listings = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="pa2">
{store.restaurantResults[store.selectedFood] &&
store.restaurantResults[store.selectedFood].map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<p>{rest.name}</p>
</div>
);
})}
</div>
));
};
But i think this is wrong, as it couples the component with the data, I want instead to pass that data via props so it can be reusable.
What is the correct way to do this? Right now my App looks like this, where it's being wrapped around a storeProvider:
function App() {
return (
<StoreProvider>
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way"/>
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings />
</div>
</StoreProvider>
);
}
My idea is to extract everrything inside the StoreProvider into another component that has a store and returns the jsx via useObserver so that I can acces the store and then pass what i need as props to the other components. like this:
const Wapper = () => {
const store = React.useContext(StoreContext);
return useObserver(() => (
<div className="mw8 center">
<Header title="EasyLunch" subTitle="Find Pizza, Burgers or Sushi in Berlin the easy way" />
<FixedMenu menuItem1={"Pizza"} menuItem2={"Burger"} menuItem3={"Sushi"} />
<p className="b tc pt3">or...</p>
<Search />
<Listings listings={store.restaurantResults[store.selectedFood]} />
</div>
))
}
And then on the listings component change the hard coded store.restaurantResults[store.selectedFood] inside to use the props that is being passes now, that is called listigs like so:
const Listings = ({listings}) => {
const store = React.useContext(StoreContext);
return useObserver(() => (
store.loading
? <Loading />
: <div className="pa2">
<div className="flex flex-wrap">
{listings &&
listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
));
};
And this works, but is this the right way to go about this?
As <Listings/> can be provided with listing and loading you can:
const Listings = ({listings, loading}) => {
if(loading) return <Loading />
return (
<div className="pa2">
<div className="flex flex-wrap">
{listings && listings.map((rest, i) => {
return (
<div key={i} className="pa2 listing">
<img className='object-fit' src={rest.image_url} alt="restuarant" />
<p>{rest.name}</p>
<p>{rest.location.address1}</p>
</div>
);
})}
</div>
</div>
);
}
No observables used, no useObservable required.
You want to useObservables on store for listings then no reason to wrap all components with useObservable. You should wrap <Listings/> only.
I usually define my store as a global, so every component has visibility of it:
class Store {
#observable myVar
}
global.store = new Store()
And in my components i just use it:
#observer
export default class MyComponent extends React.Component {
constructor () {
super()
store.myVar = 0
}
setMyVar (a) {
store.myVar += 1
}
render () {
return <button onClick={this.setMyVar}>
Clicked {store.myVar} times
</button>
}
}