I have data sets for recipes that I want to map onto cards that are on a carousel that I made.
I am trying to do this the most efficient way with least amount of code, I am already achieving it by just creating multiple sliders for each set of recipes. However I want to make it so I only need the one slider component, which already has the card component in it – in which I can then map my data into as I need. Rather than just having several of the same components where I have already mapped each dataset into previously.
Code below will show what I am trying to do.
Also here is a code sandbox if you go to the menu section and click on pasta option then the seafood button at top it will show the issue I have currently of my method of mapping is not working.
- for reference this has been designed mobile first so UI will only look normal when in mobile dimensions.
recipeCard.js
import React from 'react' import { MenuCard } from './menuCard' import styled from 'styled-components' import salad from '../assets/homePage/salad.jpg' export const RecipeCard = ({image, title}) => { return ( <div> <div style={{height: "200px"}}> <RecipeCardDiv> <ImageHolder> <img style={{height: "100%", width: "100%", borderRadius: "25px 25px 0px 0px",}} src={image}/> </ImageHolder> <RecipeTitleDiv> {title} </RecipeTitleDiv> <RecipeButton> Recipe </RecipeButton> </RecipeCardDiv> </div> </div> ) } export const RecipeCardDiv = styled.div` position: absolute; display: flex; justify-content: center; align-items: center; width: 132px; height: 180px; /* left: 47px; top: 128px; */ z-index: 0; background: #F6F6F6; box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); border-radius: 25px; ` const ImageHolder = styled.div` position: absolute; height: 95px; width: 132px; border-radius: 25px; /* background-color: red; */ top: 0; ` const RecipeTitleDiv = styled.span` position: absolute; height: 25px; width: 100%; top: 58%; left: 5%; font-family: 'Kaisei Opti', serif; font-style: bold; font-weight: 900; font-size: 14px; color: #000000; ` const RecipeButton = styled.button` position: absolute; height: 22.5px; width: 75px; border-radius: 25px; font-family: 'Kaisei Opti', serif; font-style: bold; border: none; outline: none; bottom: 6%; /* right: 10%; */ color: white; background: #30E3CA; box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); `
menuCarousel.js
import React, { Component } from "react"; import Slider from "react-slick"; import { meatPastaRecipes, seafoodPastaRecipes, veggiePastaRecipes } from "../data/pastaRecipes"; import { veggieSaladsRecipeCards } from "../data/saladRecipes"; import { PastaCard, NoodlesCard } from "./menuCard"; import { RecipeCard } from "./recipeCard"; //below is how I have been mapping my data into the various sliders and then rendering these sliders where I need them in application //However I want to be able to have the one slider component and map the data into the cards in the carousel at the point in the application where the slider is being rendered export class VeggieSaladSlider extends Component { render() { const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 2, slidesToScroll: 1, // variableWidth: true, variableHeight: true, centerMode: true, centerPadding: "6px", draggable: true }; return ( <div style={{height: "220px", width: "300px", padding: "2rem", marginTop: "3.5rem", overflow: "hidden"}}> <Slider {...settings}> {veggieSaladsRecipeCards.map(salad => <div> <RecipeCard key={salad.id} title={salad.title} image={salad.image} /> </div> )} </Slider> </div> ); } } export class VeggiePastaSlider extends Component { render() { const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 2, slidesToScroll: 1, // variableWidth: true, variableHeight: true, centerMode: true, centerPadding: "6px", draggable: true }; return ( <div style={{height: "220px", width: "300px", padding: "2rem", marginTop: "3.5rem", overflow: "hidden"}}> <Slider {...settings}> {veggiePastaRecipes.map(pasta => <div> <RecipeCard key={pasta.id} title={pasta.title} image={pasta.image} /> </div> )} </Slider> </div> ); } } export class MeatPastaSlider extends Component { render() { const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 2, slidesToScroll: 1, // variableWidth: true, variableHeight: true, centerMode: true, centerPadding: "6px", draggable: true }; return ( <div style={{height: "220px", width: "300px", padding: "2rem", marginTop: "3.5rem", overflow: "hidden"}}> <Slider {...settings}> {meatPastaRecipes.map(pasta => <div> <RecipeCard key={pasta.id} title={pasta.title} image={pasta.image} /> </div> )} </Slider> </div> ); } } //This is the template for the slider that I am trying to be able to use multiple times with different data sets mapped into it //see code file below (pastaMenuPage.js) where the <SeadfoodPastaSlider/> is being rendered and having data mapped into it export class SeafoodPastaSlider extends Component { render() { const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 2, slidesToScroll: 1, // variableWidth: true, variableHeight: true, centerMode: true, centerPadding: "6px", draggable: true }; return ( <div style={{height: "220px", width: "300px", padding: "2rem", marginTop: "3.5rem", overflow: "hidden"}}> <Slider {...settings}> <div> <RecipeCard/> </div> </Slider> </div> ); } }
pastaMenuPage.js
import React, {useState} from 'react' import { HeaderNav } from '../../components/header' import styled from 'styled-components' import { PopularCardo } from '../../components/popularCard' import LazyLoad, { MeatPastaSlider, SeafoodPastaSlider, VeggiePastaSlider } from '../../components/menuCarousel' import { MenuSection, OptionsButtonMeat, OptionsButtonSeafood, OptionsButtonVeggie, OptionsDiv, PopularCardDiv, PopularSection, TitleDiv } from './saladMenuPage' import { veggiePastaRecipes, seafoodPastaRecipes } from '../../data/pastaRecipes' export const PastaMenuPage = () => { const [active, setActive] = useState("VeggieMenu"); return ( <div style={{height: "100vh", overflow: "hidden", display: "flex", justifyContent: "center", alignItems: "center"}}> <HeaderNav/> <TitleDiv> Pasta </TitleDiv> <OptionsDiv> <OptionsButtonMeat onClick={() => setActive("MeatMenu")}> Meat </OptionsButtonMeat> <OptionsButtonSeafood style={{marginLeft: "52.5%"}} onClick={() => setActive("SeafoodMenu")}> Seafood </OptionsButtonSeafood> <OptionsButtonVeggie style={{marginRight: "52.5%"}} onClick={() => setActive("VeggieMenu")}> Veggie </OptionsButtonVeggie> </OptionsDiv> <MenuSection> {active === "VeggieMenu" && <VeggiePastaSlider/>} {active === "MeatMenu" && <MeatPastaSlider/>} {active === "SeafoodMenu" && seafoodPastaRecipes.map(pasta => <SeafoodPastaSlider key={pasta.id} title={pasta.title} image={pasta.image} /> )} </MenuSection> <TitleDiv style={{top: "67.5%"}}> Popular </TitleDiv> <PopularSection> <PopularCardDiv> {veggiePastaRecipes.slice(2,3).map(pasta => <PopularCardo key={pasta.id} title={pasta.title} image={pasta.image} /> )} </PopularCardDiv> </PopularSection> </div> ) }
and the data file incase pastaRecipes.js
export const veggiePastaRecipes = [ { id: 1, title: "Mushroom & Leek", image: "https://cmx.weightwatchers.co.uk/assets-proxy/weight-watchers/image/upload/t_WINE_EXTRALARGE/ak6clrxuzruvrv3wweqj.jpg", recipe: "" }, { id: 2, title: "Cacio e Pepe", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/cacio-e-pepe-with-runner-beans-e523207.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, { id: 3, title: "Spaghetti Primavera", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/healthy-pasta-primaver-35cbc26.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, { id: 4, title: "Caponata", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/caponata-pasta-a0027c4.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, { id: 5, title: "Tomato & Avocado", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/mexican-penne-5cd4efb.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, { id: 6, title: "Mac n Cheese", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/macaroni-cheese-251d55c.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, ] export const meatPastaRecipes = [ { id: 1, title: "Spaghetti Bolognese", image: "https://www.slimmingeats.com/blog/wp-content/uploads/2010/04/spaghetti-bolognese-36-720x720.jpg", recipe: "" }, { id: 2, title: "Tuna Pasta Bake", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/recipe-image-legacy-id-51616_12-796faab.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, { id: 3, title: "Classic Lasagne", image: "https://cdn.bosh.tv/uploads/images/recipes/_full/Lasagne-Website.jpg?v=1601992601", recipe: "" }, { id: 4, title: "Broccoli & Salmon Bake", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/recipe-image-legacy-id-227467_12-0d8623c.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, { id: 5, title: "Beef Stroganoff", image: "https://images.immediate.co.uk/production/volatile/sites/30/2020/08/beefstroganoff-d53f55e.jpg?quality=90&webp=true&resize=300,272", recipe: "" }, { id: 6, title: "Spaghetti Carbonara", image: "https://easyweeknight.com/wp-content/uploads/2019/02/spaghetti-carbonara3.jpg", recipe: "" }, ] export const seafoodPastaRecipes = [ { id: 1, title: "Crab Ravioli", image: "https://media-cdn.greatbritishchefs.com/media/oqobaojp/img27462.jpg?mode=crop&width=768&height=512", recipe: "" }, { id: 2, title: "Crab Linguine", image: "https://media-cdn.greatbritishchefs.com/media/sxipor0k/img11530.jpg?mode=crop&width=768&height=512", recipe: "" }, { id: 3, title: "Conchiglie Frutti Di Mare", image: "https://media-cdn.greatbritishchefs.com/media/eqypizew/img62005.jpg?mode=crop&width=768&height=512", recipe: "" }, { id: 4, title: "Fishghetti", image: "https://media-cdn.greatbritishchefs.com/media/tv4foh5e/img26026.jpg?mode=crop&width=768&height=512", recipe: "" }, { id: 5, title: "Prawn Linguine", image: "https://media-cdn.greatbritishchefs.com/media/al4icv5v/img52113.jpg?mode=crop&width=768&height=512", recipe: "" }, ]
To summarise, I am looking for a more advanced and efficient way to map data into a component so I do not need several of the same components in one file.
Advertisement
Answer
The Problem you have here is , you are telling the Slider upfront that you are going to render a certain list of items. Due to this we are repeating the Slider logic in all the places where ever we want to achieve the carousel behaviour.
But what we need is for the slider to render it contents dynamically because slider doesn’t care what it needs to render. All it needs to do is provide the carousel behaviour. This in react can be achieved using the children
prop.
Create a new component for the Slider,
Solution 1
SliderContainer.js
import React from "react"; import Slider from "react-slick"; const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 2, slidesToScroll: 1, // variableWidth: true, variableHeight: true, centerMode: true, centerPadding: "6px", draggable: true }; const SliderContainer = ({ children }) => ( <div style={{ height: "220px", width: "300px", padding: "2rem", marginTop: "3.5rem", overflow: "hidden" }} > <Slider {...settings}>{children}</Slider> </div> ); export default SliderContainer;
Now use this component in all the places where you want to achieve the carousel behaviour.
export class VeggieSaladSlider extends Component { render() { return ( <SliderContainer> {veggieSaladsRecipeCards.map((salad) => ( <div> <RecipeCard key={salad.id} title={salad.title} image={salad.image} /> </div> ))} </SliderContainer> ); } }
Solution 2
If it is guaranteed that all the carousel items will have the below shape
{ id: ..., title: ..., image: ... }
we can further enhance the SliderContainer
component to take a prop which is a list of items instead of the children prop.
import React from "react"; import Slider from "react-slick"; import { RecipeCard } from "./recipeCard"; const settings = { dots: true, infinite: true, speed: 500, slidesToShow: 2, slidesToScroll: 1, // variableWidth: true, variableHeight: true, centerMode: true, centerPadding: "6px", draggable: true }; const SliderContainer = ({ items }) => ( <div style={{ height: "220px", width: "300px", padding: "2rem", marginTop: "3.5rem", overflow: "hidden" }} > <Slider {...settings}> {items.map(({ id, title, image }) => ( <div key={id}> <RecipeCard key={id} title={title} image={image} /> </div> ))} </Slider> </div> ); export default SliderContainer;
Now with this change , we can just render the different Sliders as
export class VeggieSaladSlider extends Component { render() { return <SliderContainer items={veggieSaladsRecipeCards} />; } }