React: hide and show specific elements from array displayed using map() - javascript

The goal is to display a series of recipes. The user first gets a general overview of the available recipes containing the name of the recipe, a picture, and some basic information (time to cook, etc). When the user clicks a button (show full recipe), the full recipe should be shown. When the user clicks a new button (hide full recipe) the full recipe is hidden again and the user only sees a general overview of the recipes.
For now, I have created an array of (two) recipes. By using a map() I return the different recipes. However, when the show or hide full recipe button is clicked, all the recipes are shown. How can I make sure that when the user clicks 'show full recipe', he/she only gets to see the specific recipe?
import React, { useState } from 'react';
var recipes = [{
_id: 1,
title: "Spaghetti",
type: "Dinner or Lunch",
category: "vegan",
image: "http://via.placeholder.com/350x250",
cookingTime: 35,
ingredients: [[1, "onion"], [3, "tomato"], [1, "aubergine"], [1, "beans"], [1, "tomatoesauce"], [1, "tomatoconcentrate"], [1, "pepper"], [1, "salt"], [1, "pasta"]],
cookingSteps: [["Boil a pot full of watter. Once the watter is boiling, add the pasta. The cooking time depends on the pasta."], ["Cut the onion and the garlic. Add them to a poth with olive oil. Cook for 2 to 3 minutes"], ["Cut the other vegetables. After the onion and the garlic are marrinated, add the different vegtables to the poth. Let them cook with the lid on for about 10 to 15 minutes or untill the vegetables are ready."], ["Once the vegetables are ready, add the tomato sauce and the tomato concentrate. Add the beans. Let this cook for another five minutes"], ["Remove the lid from the pot. Add pepper and salt and other additional spcices, like italian spcies or special peppers"]]
},
{
_id: 2,
title: "Eggs",
type: "Breakfast",
category: "vegan",
image: "http://via.placeholder.com/350x250",
cookingTime: 10,
ingredients: [[4, "egg"], [1, "tomato"], [1, "pepper"], [1, "salt"]],
cookingSteps: [["cut the tomato. Heat olive oil in a pan, add the tomato and bake it till the outer peal of the tomato gets soft. If you like pepper, it is adviced to first bake the pepper a bit in the pan"], ["Meanwhile, scrambel the eggs. Once the tomatoes are ready (see step 1), add the eggs"]]
}
];
//TODO: make sure only one specific recipe shows when clicked
function DisplayRecipes(){
//declare useState and onClick for button to show full recipe
const[isNotDisplayed, isDisplayed] = React.useState(false)
const onClickShow = () => {isDisplayed(true)};
const onClickHIde = () => isDisplayed(false);
//return statement to render recipe
return(
<div className="card-list">
<div className="container">
<h1 className="page-title">Our vegan recipes...</h1>
<div className="row">
{
recipes.map((recipe)=>{
return(
<div key={recipe._id} className="col-md-3">
<div className="card bwm-card">
<div className="card-title">
<h2>{recipe.title}</h2>
</div>
<img className="card-img-top" src={recipe.image} alt={recipe.title} />
<div className="card-subtitle">
<h3><b>Type: {recipe.type} </b></h3>
<h3><b>Category: {recipe.category}</b></h3>
<h3><b>Cooking time: {recipe.cookingTime} minutes</b></h3>
</div>
<div>
{ isNotDisplayed ?
<div className="card-body">
<div className="card-body-ingredient">
{
recipe.ingredients.map((ingredient, i) =>{
return(
<table>
<tr>
<th>Quantity</th>
<th>Ingredient</th>
</tr>
<tr>
<th>{ingredient[0]}</th>
<th>{ingredient[1]}</th>
</tr>
</table>
)
})
}
</div>
<div className="card-body-cookingsteps">
{
recipe.cookingSteps.map((cookingstep, i) =>{
return(
<p>{cookingstep}</p>
)
})
}
</div>
<div>
<button type="submit" value="Hide full recipe" onClick= {onClickHIde}>Hide full recipe</button>
</div>
</div>
:
<button type="submit" value="Show full recipe" onClick= {onClickShow}>Show full recipe</button>
}
</div>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default DisplayRecipes;
Thank you in advance!

You need a separate piece of state for each recipe. Currently all the recipes share the same state, so when one is open, they're all open.
The best way to separate out the state is to make each recipe its own component with its own state.
import React, {useState} from 'react';
const recipes = [
{
_id: 1,
title: 'Spaghetti',
type: 'Dinner or Lunch',
category: 'vegan',
image: 'http://via.placeholder.com/350x250',
cookingTime: 35,
ingredients: [
[1, 'onion'],
[3, 'tomato'],
[1, 'aubergine'],
[1, 'beans'],
[1, 'tomatoesauce'],
[1, 'tomatoconcentrate'],
[1, 'pepper'],
[1, 'salt'],
[1, 'pasta'],
],
cookingSteps: [
[
'Boil a pot full of watter. Once the watter is boiling, add the pasta. The cooking time depends on the pasta.',
],
[
'Cut the onion and the garlic. Add them to a poth with olive oil. Cook for 2 to 3 minutes',
],
[
'Cut the other vegetables. After the onion and the garlic are marrinated, add the different vegtables to the poth. Let them cook with the lid on for about 10 to 15 minutes or untill the vegetables are ready.',
],
[
'Once the vegetables are ready, add the tomato sauce and the tomato concentrate. Add the beans. Let this cook for another five minutes',
],
[
'Remove the lid from the pot. Add pepper and salt and other additional spcices, like italian spcies or special peppers',
],
],
},
{
_id: 2,
title: 'Eggs',
type: 'Breakfast',
category: 'vegan',
image: 'http://via.placeholder.com/350x250',
cookingTime: 10,
ingredients: [
[4, 'egg'],
[1, 'tomato'],
[1, 'pepper'],
[1, 'salt'],
],
cookingSteps: [
[
'cut the tomato. Heat olive oil in a pan, add the tomato and bake it till the outer peal of the tomato gets soft. If you like pepper, it is adviced to first bake the pepper a bit in the pan',
],
[
'Meanwhile, scrambel the eggs. Once the tomatoes are ready (see step 1), add the eggs',
],
],
},
];
const Recipe = ({recipe}) => {
const [isOpen, setIsOpen] = React.useState(false);
return (
<div className="col-md-3">
<div className="card bwm-card">
<div className="card-title">
<h2>{recipe.title}</h2>
</div>
<img className="card-img-top" src={recipe.image} alt={recipe.title} />
<div className="card-subtitle">
<h3>
<b>Type: {recipe.type} </b>
</h3>
<h3>
<b>Category: {recipe.category}</b>
</h3>
<h3>
<b>Cooking time: {recipe.cookingTime} minutes</b>
</h3>
</div>
<div>
{isOpen ? (
<div className="card-body">
<div className="card-body-ingredient">
{recipe.ingredients.map((ingredient, i) => {
return (
<table>
<tr>
<th>Quantity</th>
<th>Ingredient</th>
</tr>
<tr>
<th>{ingredient[0]}</th>
<th>{ingredient[1]}</th>
</tr>
</table>
);
})}
</div>
<div className="card-body-cookingsteps">
{recipe.cookingSteps.map((cookingstep, i) => {
return <p>{cookingstep}</p>;
})}
</div>
<div>
<button
type="submit"
value="Hide full recipe"
onClick={() => setIsOpen(false)}>
Hide full recipe
</button>
</div>
</div>
) : (
<button
type="submit"
value="Show full recipe"
onClick={() => setIsOpen(true)}>
Show full recipe
</button>
)}
</div>
</div>
</div>
);
};
function DisplayRecipes() {
return (
<div className="card-list">
<div className="container">
<h1 className="page-title">Our vegan recipes...</h1>
<div className="row">
{recipes.map((recipe) => (
<Recipe key={recipe._id} recipe={recipe} />
))}
</div>
</div>
</div>
);
}
export default DisplayRecipes;

You can consider using refs here:
Pass the ref to your onClickShow and show the div with that particular ref.
You can also use CSS, in this case, get the div using the ref and mark display of that div as none and vice-versa.

Related

React components not conditionally rendering?

I am trying to build a simple website that will display a random lyric after the user selects a musical artist. I am struggling with making components appear on react after the button is clicked. It is likely that this is a really simple issue given I just started working with React yesterday and learned JS a few days ago. My first screen renders with the two artists and a button to chose between them, but I can't get anything past that to pop up. I also seem to be having trouble with the states not updating.
import axios from 'axios';
import React, {Component} from 'react'
import taylorSwift from './taylorswift.jpg';
import kanyeWest from './kanyewest.jpeg';
const TAYLOR_ID = "259675";
const KANYE_ID = "33091467";
class Game extends Component {
constructor(props) {
super(props);
this.state = {
artist_id: "",
lyric: "",
actualTrack: "",
userTrack: "",
artistSelected: false
};
}
async getRandomLyric(artist_id) {
const response = await axios.get(`http://localhost:3000/randomlyric/:${artist_id}`);
this.setState({
lyric :response.lyric,
actualTrack: response.actualTrack});
}
choseArtist(artist_id) {
this.setState({
artist_id: artist_id,
artistSelected: true
})
return (
<div>
<p> Great! Now you a random lyric from the artist you chose will be presented and</p>
<p> you will have to guess which song this lyric is from. </p>
<p> Good luck! </p>
<div>
{this.getRandomLyric(artist_id)}
<p>What track is this lyric from: {this.state.lyric} </p>
<input type = "text" onChange ={ (e) => {
this.setState({userTrack: e.target.value})
}}/>
</div>
</div>
)
}
showLyrics (artist_id) {
}
render() {
return (
<div>
<h1>Welcome to the Song Guessing Game!</h1>
<p> These are the rules of the game: </p>
<p> First, you must pick whether you are Team Taylor or Team Kanye.</p>
<p> After you pick your team, you will be presented with 5 random lyrics from your chosen artist.</p>
<p> Your job is to guess which song each lyric is from. </p>
<p> Good luck!</p>
<div className = "team_taylor">
<div>
<img className = "artist_picture"
src={taylorSwift}
alt="Taylor Swift" />
</div>
<div className = "artist_button">
<button onClick={()=>{this.choseArtist(TAYLOR_ID); this.forceUpdate()}}> Team Taylor</button>
</div>
</div>
<div className = "team_kanye">
<div>
<img className = "artist_picture"
src={kanyeWest}
alt="Kanye West"/>
</div>
<div className = "artist_button">
<button onClick={()=>{this.choseArtist(KANYE_ID); this.forceUpdate()}}> Team Kanye</button>
</div>
</div>
</div>
);
}
}
export default Game;
If you just started learning react I recommend you to learn React functional components
and React hooks.
Here is an example of how your component would look like following the react functional components and hooks structure.
import { useEffect, useState } from "react";
const Game = () => {
const [artist, setArtist] = useState({
artist_id: "",
lyric: false,
actualTrack: "",
userTrack: "",
artistSelected: false
});
useEffect(() => {
artist.artistSelected && axios
.get(`http://localhost:3000/randomlyric/:${artist_id}`)
.then((response) =>
setArtist((prev) => ({
...prev,
lyric: response.lyric,
actualTrack: response.actualTrack
}))
)
.catch(error => console.log(error));
}, [artist.artistSelected]);
return artist.lyric ? (
<div>
<p>
{" "}
Great! Now you a random lyric from the artist you chose will be
presented and
</p>
<p> you will have to guess which song this lyric is from. </p>
<p> Good luck! </p>
<div>
<p>What track is this lyric from: {artist.lyric} </p>
<input
type="text"
onChange={(e) => {
setArtist(prev => ({...prev, userTrack:e.target.value}))
}}
/>
</div>
</div>
) : (
<div>
<h1>Welcome to the Song Guessing Game!</h1>
<p> These are the rules of the game: </p>
<p> First, you must pick whether you are Team Taylor or Team Kanye.</p>
<p>
{" "}
After you pick your team, you will be presented with 5 random lyrics
from your chosen artist.
</p>
<p> Your job is to guess which song each lyric is from. </p>
<p> Good luck!</p>
<div className="team_taylor">
<div>
<img
className="artist_picture"
src={taylorSwift}
alt="Taylor Swift"
/>
</div>
<div className="artist_button">
<button
onClick={() =>
setArtist((prev) => ({
...prev,
artist_id: TAYLOR_ID,
artistSelected: true
}))
}
>
{" "}
Team Taylor
</button>
</div>
</div>
<div className="team_kanye">
<div>
<img className="artist_picture" src={kanyeWest} alt="Kanye West" />
</div>
<div className="artist_button">
<button
onClick={() =>
setArtist((prev) => ({
...prev,
artist_id: KANYE_ID,
artistSelected: true
}))
}
>
{" "}
Team Kanye
</button>
</div>
</div>
</div>
);
};
export default Game;

How to enable/add anchor tag to the string in ReactJS?

The follwing is the code I've written and when I rendered it, item.about which has <a> tag, cannot render it and [object Object] is being displayed instead of that JSX. JSON.stringify does not even render it.
export default function Team() {
const teamList = [
{
id: 1,
name: "User",
image: user,
designation: "User",
about:
"He created Zoom Church LK </a>}. Apart from shipping apps, He Writes/Produces/Performs Music.",
},
{
id: 2,
name: "User 2",
image: user,
designation: "User",
about:
`He created ${ Zoom Church LK }. Apart from shipping apps, He Writes/Produces/Performs Music.`,
},
];
return (
<section className="leading-relaxed max-w-screen-xl mt-12 mx-auto px-4 lg:px-8 mb-20">
<div className="space-y-3 text-center">
<h1 className="text-5xl text-gray-400 font-black">Meet Our Team</h1>
<p className="text-gray-500 max-w-lg mx-auto text-lg">
Focused. Improving. Data-driven.
</p>
</div>
<div className="mt-14 gap-4 sm:grid sm:grid-cols-2 lg:grid-cols-3">
{teamList.map((item) => (
<div className="space-y-3 mt-5" key={item.id}>
<div className="object-center lg:w-full rounded-lg card-zoom">
<img
src={item.image}
alt={item.name}
/>
</div>
<h4 className="text-xl text-gray-400 font-bold">{item.name}</h4>
<h5 className="text-lg text-gray-400 font-medium">
{item.designation}
</h5>
<p className="text-gray-500">{(JSON.stringify(item.about))}</p>
</div>
))}
</div>
</section>
);
}
What would be the best way to modify my component to be able to add links like this?
To render links change your teamList like this:-
const teamList = [
{
id: 1,
name: "User",
image: user,
designation: "User",
about: <>He created Zoom Church LK . Apart from shipping apps, He Writes/Produces/Performs Music.</>,
},
{
id: 2,
name: "User 2",
image: user,
designation: "User",
about: <>He created Zoom Church LK . Apart from shipping apps, He Writes/Produces/Performs Music.</>,
},
];
and then render it like this
<p className="text-gray-500">{item.about}</p>
// you have to use tags without ${} when
// you want to use them in strings
const teamList = [
{
id: 1,
name: "User",
image: user,
designation: "User",
about:
"He created <a href='https://hanancs.github.io/ZoomChurchLK'> Zoom Church LK </a>. Apart from shipping apps, He Writes/Produces/Performs Music.",
},
{
id: 2,
name: "User 2",
image: user,
designation: "User",
about:
`He created <a href='https://hanancs.github.io/ZoomChurchLK'> Zoom Church LK </a>. Apart from shipping apps, He Writes/Produces/Performs Music.`,
},
];
// you can use dangerouslySetInnerHTML to show
//html tag inside of strings.
return (
{teamList.map((team) => (
<div dangerouslySetInnerHTML={{ __html: team.about }} key={team.id} />
))}
)

how to pass values from an array into a prop

so im trying to pass the value price from this array
const [products, setProducts] = useState(
[{
name: "14K bracelet",
id: "1",
description: "Beautfull 14K gold Bracelet",
price: 100.00,
img: braceletImg,
}]
)
into here
<h1 className="price">{products.price}</h1>{/*this is a prop*/}
I call the prop here in cart
function Cart({ products })
full code of the cart component
function Cart({ products }) {
return(
<div className="Body">
{/* {products.map(pr => <h1 className="price">{pr.price}</h1>)} */}
<div className="Cart-wrapper" >
<div className="cart-header">
<h5 className="Product-name cart-text">Product</h5>
<h5 className="quantity-name cart-text">Quantity</h5>
<h5 className="price-name cart-text">Price</h5>
<Button className="btn btn-plus">+</Button>
<Button className="btn btn-minus">-</Button>
<div className="card-cart">
<img className="braceletimg" src={braceletImg} />
<h1 className="card-body-title">Bracelet title</h1>
<h1 className="card-body-title seemore"><Link className="Link" to="/Bracelets">Learn More</Link></h1>
<hr className="cart-hr"></hr>
</div>
<div className="div-price">
{products.map(pr => <h1 key={pr.id} className="price">{pr.price}</h1>)}
<small className="shippingprice">$5.00 + shipping</small>
</div>
<Button className="btn btn-cart btn-primary"><Link className="Link" to="/Cart/shipping-buynow">Review Cart</Link></Button>
</div>
</div>
</div>
)
}
export default Cart;
hopefully, this gives you a better context of the component
Because products is an array, you either need to use indexing, or you can process them all using .map().
Using indexing:
<h1 className="price">{products[0].price}</h1>
Using .map():
{products.map(pr => <h1 key={pr.id} className="price">{pr.price}</h1>)}
The addition of key={...} is needed so React can optimize the rendering. If you don't add it then React will show warnings in the console output. If the items already have a unique id or key value, then it's best to use that.
Update:
Your Cart component may be getting activated already before products has been initialized, this would explain the undefined errors.
This is quite normal, and to prevent it from being a problem you can check if products is empty:
function Cart({ products }) {
if (!products)
return "Loading...";
return (
// ...
// ...
// ...
);
}

How to display result of map in two different columns

I have a task, but i couldnt get the solution for it in reactjs. I would like to show the result in two columns like: So every next element should be added in the second column, but the first column should be limited to 2 rows.
item 1 | item 4
item 2 | item 5
| item 6
When i loop through the array the columns are getting divided into equal order, but as you see in the diagram i need to split them in 2:3 ratio.
I am giving the sandbox link here on what i have tried so far.
// Example class component
class Thingy extends React.Component {
render() {
const secondaryNav = [
{
title: "Games",
name: ["Roadrash", "Prince of Persia", "Counter Strike"]
},
{
title: "Resources",
name: ["Images", "Videos", "E-books"]
},
{
title: "Shop",
name: ["Shop now"]
},
{
title: "Account",
name: ["Log In", "Register"]
},
{
title: "Support",
name: ["Email", "Call Us", "Get a callback"]
}
];
return (
<div className="App">
<div className="container">
<div className="row">
{secondaryNav.map((item, i) => {
return (
<div className="col-lg-6 col-md-6">
<h3>{ item.title }</h3>
<ul>
{item.name.map((items, i) => {
return <li>{items}</li>
})}
</ul>
</div>)
})}
</div>
</div>
</div>
)
}
}
// Render it
ReactDOM.render(
<Thingy title="I'm the thingy" />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="react"></div>
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.0/css/bootstrap.min.css"
integrity="sha384-9gVQ4dYFwwWSjIDZnLEWnxCjeSWFphJiwGPXr1jddIhOegiu1FwO5qRGvFXOdJZ4"
crossorigin="anonymous">
SANDBOX
You need to add the css on it and also you could slice your array as you like.
this is the piece of the code
<div className="row">
{secondaryNavData.slice(0, 2).map((item, i) => {
return (
<div className="col-lg-6 col-md-6">
<h3>{item.title}</h3>
<ul>
{item.name.map((items, i) => {
return <li>{items}</li>;
})}
</ul>
</div>
);
})}
</div>
<div className="row-2">
{secondaryNavData.slice(2, 5).map((item, i) => {
return (
<div className="col-lg-6 col-md-6">
<h3>{item.title}</h3>
<ul>
{item.name.map((items, i) => {
return <li>{items}</li>;
})}
</ul>
</div>
);
})}
</div>
Try this codesandbox based on yours for the full code with the css
https://codesandbox.io/s/elegant-grass-nmeux
slice can help us slice the array between (0,2) and (2,5) indices and accordingly we can map over the divs. We don't need row class coming from bootstrap for this use-case. Can simply replace it with col as well.
Updated question sandbox -
https://codesandbox.io/s/goofy-black-4m4ii?file=/src/styles.css

Saving the position of items in the Sortable component

I'm using UIKit (GetUIKit) within Vue.js. I have 3 lists of objects (id, picture) that I drag across. I would like to save the position of the pictures in each list but the arrays don't seem to have changed and I don't know how to find which array it has be dropped into.
<template>
<div class="uk-child-width-1-4#s" uk-grid>
<div>
<h4>Selected products</h4>
<div
uk-sortable="group: sortable-group"
v-for="product in selectedProducts"
v-bind:key="product.id"
v-on:stop="greet(product.id, selectedProducts, outfit1, outfit2)"
>
<div class="uk-card uk-card-small uk-card-default uk-card-body">
<img v-bind:src="product.image">
</div>
</div>
</div>
<div>
<h4>Outfit 1</h4>
<div
uk-sortable="group: sortable-group"
v-for="product in outfit1"
v-bind:key="product.id"
v-on:stop="greet(product.id, selectedProducts, outfit1, outfit2)
>
<div class="uk-card uk-card-small uk-card-default uk-card-body">
<img v-bind:src="product.image">
</div>
</div>
</div>
<div>
<h4>Outfit 2</h4>
<div
uk-sortable="group: sortable-group"
v-for="product in outfit2"
v-bind:key="product.id"
v-on:stop="greet(product.id, selectedProducts, outfit1, outfit2)"
>
<div class="uk-card uk-card-small uk-card-default uk-card-body">
<img v-bind:src="product.image">
</div>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
selectedProducts: [
{
id: 1,
name: "Cool top",
image:
"https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcTfpwfhIA_q8BvD5Hj6TKHfTd0fhfaNQcYKPlBcww6aZXWUOE3R"
},
{
id: 2,
name: "Fun jeans",
image:
"https://wwws.dior.com/couture/ecommerce/media/catalog/product/cache/1/grid_image_8/460x497/17f82f742ffe127f42dca9de82fb58b1/U/d/1550571393_922P02A3932_X5846_E08_GH.jpg"
}
],
outfit1: [
{
id: 3,
name: "shoes",
image: "https://images.vans.com/is/image/Vans/D3HY28-HERO?$583x583$"
}
],
outfit2: [
{
id: 4,
name: "scarf",
image:
"https://www.brontebymoon.co.uk/wp-content/uploads/2017/04/TAR80-A01-Lambswool-Tartan-Scarf-Antique-Dress-Stewart.jpg"
}
]
};
},
methods: {
greet: function(position, ) {
// This is where I want to save the position, for ex: {outfit1: [id: 1,2], outfit2: [id: 3], selectedProduct: [id: 4]}
console.log(position);
}
}
};
</script>
I want to save the position, for ex: {outfit1: [id: 1,2], outfit2: [id: 3], selectedProduct: [id: 4]} but I'm stuck. Any ideas?
Thank you!

Categories

Resources