I am building a restaurant website template. In the project, I do have a modal box with two arrow buttons to change pages. I keep track of the pageNumber variable via useState. Unfortunately, the value never updates, which prevents pages from changing.
Here is the Menu.js component:
import { BrowserRouter as Router, Link } from 'react-router-dom';
import { useState } from 'react';
const Menu = () => {
let [pageNumber, switchPage] = useState(1);
let dish = {
categories: [
'proin blandit',
'suspendisse non',
'fermentum ultrices',
'volutpat vulputat'
],
names: [
['Sed fermentum', 'Sapien nec lobortis', 'Tristique in suspendisse', 'Pellentesque mattis elit', 'Vel interdum velit'],
['Nam nisi quam', 'Donec non viverra', 'Morbi suscipit mattis', 'Sed dignissim nisi', 'Aliquam vitae'],
['Curabitur ante mi', 'Nulla a felis', 'Donec porta convallis ', 'Vestibulum viverra ', 'Nullam viverra '],
['Vestibulum odio', 'Mauris neque ligula', 'Praesent iaculis nunc', 'Cras sollicitudin est', 'Class aptent taciti ']
],
prices: [
[13.99, 11.99, 15.99, 17.99, 21.99],
[22.99, 20.99, 18.99, 16.99, 24.99],
[11.99, 21.99, 31.99, 25.99, 15.99],
[6.99, 8.99, 10.99, 4.99, 8.99]
]
}
let iterator = Math.abs(pageNumber % 4);
let lineBreak = window.innerWidth <= 750 ? <br/> : undefined;
return (
<Router>
<section className='menu'>
<i className='fas fa-chevron-circle-left' id='menu-arrow-left' onClick={() => switchPage(pageNumber - 1)}></i>
<h1 className='dish-category'>You are viewing {dish.categories[iterator]}</h1>
<Link to='/home'><i className='fas fa-times'></i></Link>
<ul className='dish-list'>
<li className='dish'>{dish.names[iterator][0]} {lineBreak} <span className='price'>{dish.prices[iterator][0]} PLN</span></li>
<li className='dish'>{dish.names[iterator][1]} {lineBreak} <span className='price'>{dish.prices[iterator][1]} PLN</span></li>
<li className='dish'>{dish.names[iterator][2]} {lineBreak} <span className='price'>{dish.prices[iterator][2]} PLN</span></li>
<li className='dish'>{dish.names[iterator][3]} {lineBreak} <span className='price'>{dish.prices[iterator][3]} PLN</span></li>
<li className='dish'>{dish.names[iterator][4]} {lineBreak} <span className='price'>{dish.prices[iterator][4]} PLN</span></li>
</ul>
<i className='fas fa-chevron-circle-right' id='menu-arrow-right' onClick={() => switchPage(pageNumber + 1)}></i>
</section>
</Router>
)
}
export default Menu;
This is how I toggle the modal's visibility:
const hideModal = (e) => {
e.preventDefault();
let placeholder = document.getElementById('placeholder');
placeholder.innerText = '';
placeholder.style.display = 'none';
let container = document.getElementById('container');
container.style.filter = '';
container.style.pointerEvents = 'auto';
}
const showModal = () => {
let placeholder = document.getElementById('placeholder');
placeholder.innerHTML = ReactDOMServer.renderToString(<Menu />);
placeholder.style.display = 'block';
let container = document.getElementById('container');
container.style.filter = 'blur(8px)';
container.style.pointerEvents = 'none';
}
And this is the corresponding JSX code in App.js:
<div id='placeholder'><Menu /> <Contact /></div>
Additionally, I have written a test function that detects the clicked place. When I run it on the example above I get a message that I am clicking outside the modal box, even though it's the opposite. The function is run from a different file. It looks like this:
let childrenNodes = [
document.getElementsByClassName('menu')[0],
document.getElementsByClassName('dish-category')[0],
document.getElementsByClassName('dish-list')[0],
document.getElementsByClassName('dish')[0],
document.getElementsByClassName('dish')[1],
document.getElementsByClassName('dish')[2],
document.getElementsByClassName('dish')[3],
document.getElementsByClassName('dish')[4],
document.getElementsByClassName('price')[0],
document.getElementsByClassName('price')[1],
document.getElementsByClassName('price')[2],
document.getElementsByClassName('price')[3],
document.getElementsByClassName('price')[4]
];
const detectPlace = () => {
window.addEventListener('click', (e) => {
childrenNodes.some((node) => e.target === node) ? console.log('Clicked inside!') : console.log('Clicked outside!');
console.log(e.target);
});
}
Could you tell me why is it working this way?
You are rendering the contents of #placholder with:
let placeholder = document.getElementById('placeholder');
placeholder.innerHTML = ReactDOMServer.renderToString(<Menu />);
ReactDOMServer.renderToString(...) produces a string containing HTML. This string doesn't include any JavaScript, thus making the result non-interactive.
renderToString()
ReactDOMServer.renderToString(element)
Render a React element to its initial HTML. React will return an HTML
string. You can use this method to generate HTML on the server and
send the markup down on the initial request for faster page loads and
to allow search engines to crawl your pages for SEO purposes.
If you call ReactDOM.hydrate() on a node that already has this
server-rendered markup, React will preserve it and only attach event
handlers, allowing you to have a very performant first-load
experience.
If you want an interactive DOM structure you should use the normal ReactDOM.render(...) method to render your component.
let placeholder = document.getElementById('placeholder');
ReactDOM.render(<Menu />, placeholder);
// import { BrowserRouter as Router, Link } from 'react-router-dom';
// import { useState } from 'react';
const { BrowserRouter: Router, Link } = ReactRouterDOM;
const { useState } = React;
const Menu = () => {
let [pageNumber, switchPage] = useState(1);
let dish = {
categories: [
'proin blandit',
'suspendisse non',
'fermentum ultrices',
'volutpat vulputat'
],
names: [
['Sed fermentum', 'Sapien nec lobortis', 'Tristique in suspendisse', 'Pellentesque mattis elit', 'Vel interdum velit'],
['Nam nisi quam', 'Donec non viverra', 'Morbi suscipit mattis', 'Sed dignissim nisi', 'Aliquam vitae'],
['Curabitur ante mi', 'Nulla a felis', 'Donec porta convallis ', 'Vestibulum viverra ', 'Nullam viverra '],
['Vestibulum odio', 'Mauris neque ligula', 'Praesent iaculis nunc', 'Cras sollicitudin est', 'Class aptent taciti ']
],
prices: [
[13.99, 11.99, 15.99, 17.99, 21.99],
[22.99, 20.99, 18.99, 16.99, 24.99],
[11.99, 21.99, 31.99, 25.99, 15.99],
[6.99, 8.99, 10.99, 4.99, 8.99]
]
}
let iterator = Math.abs(pageNumber % 4);
let lineBreak = window.innerWidth <= 750 ? <br/> : undefined;
return (
<Router>
<section className='menu'>
<i className='fas fa-chevron-circle-left' id='menu-arrow-left' onClick={() => switchPage(pageNumber - 1)}></i>
<h1 className='dish-category'>You are viewing {dish.categories[iterator]}</h1>
<Link to='/home'><i className='fas fa-times'></i></Link>
<ul className='dish-list'>
<li className='dish'>{dish.names[iterator][0]} {lineBreak} <span className='price'>{dish.prices[iterator][0]} PLN</span></li>
<li className='dish'>{dish.names[iterator][1]} {lineBreak} <span className='price'>{dish.prices[iterator][1]} PLN</span></li>
<li className='dish'>{dish.names[iterator][2]} {lineBreak} <span className='price'>{dish.prices[iterator][2]} PLN</span></li>
<li className='dish'>{dish.names[iterator][3]} {lineBreak} <span className='price'>{dish.prices[iterator][3]} PLN</span></li>
<li className='dish'>{dish.names[iterator][4]} {lineBreak} <span className='price'>{dish.prices[iterator][4]} PLN</span></li>
</ul>
<i className='fas fa-chevron-circle-right' id='menu-arrow-right' onClick={() => switchPage(pageNumber + 1)}></i>
</section>
</Router>
)
}
let placeholder = document.getElementById('placeholder');
ReactDOM.render(<Menu />, placeholder);
<div id="placeholder"></div>
<link crossorigin rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" />
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<script crossorigin src="https://unpkg.com/react-router-dom#5/umd/react-router-dom.js"></script>
Related
i am building a restaurant website in which i have an array of all the items in their menu, looks like this -
const CardData = [
{
index: 1,
imagesrc: "require(./assets/menu/nonveg)",
title: "Tandoori Chicken",
group: "non-veg",
discprice: 400,
price: 450,
},
{
index: 2,
imagesrc: "require(./assets/menu/nonveg)",
title: "Momos Steamed",
group: "veg",
discprice: 80,
price: 100,
},
{
index: 3,
imagesrc: "require(./assets/menu/nonveg)",
title: "Non-Veg Momos Fried",
group: "non-veg",
discprice: 100,
price: 150,
}]
In a parent component i have different child "menu" components. In each menu component i want to render a different category of data.
The parent component looks like-
import React, { useState } from "react";
import CardMenu from "./CardMenu";
const Menu = () => {
return (
<div className="group">
<h1>Our Collection</h1>
<CardMenu/>
<CardMenu/>
<CardMenu/>
</div>
);
};
export default Menu;
and the child component looks like -
'''
import Tag from "./Tag";
import { useState } from "react";
const CardMenu = ({ menudata }) => {
return (
<>
<div className="menu">
<Tag />
<div className="scroll-menu">
{
menudata.map((curElem) => {
const { index, title, group, imagesrc, price, disprice } = curElem;
return (
<>
<div className="card" key={index}>
<div className="card-upper">
<div className="card-image">
<img
src={require("../assets/Duggarhutz/nonveg/nonvegfriedmomos.jpeg")}
alt=""
/>
</div>
<div className="card-info">{index}</div>
</div>
<div className="card-mid">
<div className="card-title">{title}</div>
<div className="card-icons"></div>
<div className="card-desc">
Lorem, ipsum dolor sit amet consectetur adipisicing elit.
Aliquam, vero. Provident nesciunt magni aliquid omnis eveniet
architecto optio reprehenderit at itaque aut?
</div>
</div>
<div className="card-bottom">
<div className="card-price">${price}</div>
<div className="card-descprice">${disprice}</div>
<div className="card-button">
<button>Order</button>
</div>
</div>
</div>
</>
)
})
}
</div>
</div>
</>
)
};
export default CardMenu;
---for each group i want to render a new Cardmenu with cards related to it.---
Pass in the cardData to your cardMenu like this <CardMenu cardData />
Or <CardMenu cardData=cardData />
As you are destructuring the props in CardMenu with a property menudata you need to pass a property with the same name as this:
const Menu = () => {
return (
<div className="group">
<h1>Our Collection</h1>
<CardMenu menudata={CardData}/>
</div>
);
};
i write your solution and give a suggestion to this situation:
solution- filter data from js methods:
const data = [
{
categoryName: "Banking",
categoryId: "B1",
description: "Financial sector"
},
{
categoryName: "Retail",
categoryId: "R1",
description: "Retail and customer experience"
}
];
function groupByProperty(arrayOfObjects, property) {
return arrayOfObjects.reduce((acc, curr) => {
const key = curr[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(curr);
return acc;
}, {});
}
const dataByCategoryName = groupByProperty(data, "categoryName");
console.log(dataByCategoryName);
/* Output
{
Banking: [{
categoryId: "B1",
categoryName: "Banking",
description: "Financial sector"
}],
Retail: [{
categoryId: "R1",
categoryName: "Retail",
description: "Retail and customer experience"
}]
}
*/
suggestion- you can write a search bar and filter cards by client search (realtime filtering).
you can use my sample:
enter link description here
Since you are already mapping the data inside the CardMenu component, you should:
Import CardData from the corresponding file (I suppose it is in the same directory, so probably:
import CardData from "./CardData";
Remove the extra calls to CardMenu:
<div className="group">
<h1>Our Collection</h1>
<CardMenu />
</div>
Pass CardData as a prop menudata to CardMenu:
<CardMenu menudata={CardData} />
The final code of Menu would look something like this:
import React, { useState } from "react";
import CardMenu from "./CardMenu";
import CardData from "./CardData";
const Menu = () => {
return (
<div className="group">
<h1>Our Collection</h1>
<CardMenu menudata={CardData} />
</div>
);
};
export default Menu;
I am working on a Vue 3 and Bootstrap 5 app. I needed a date-picker and I choose Vue 3 Datepicker.
In components\Ui\Datepicker.vue I have:
<template>
<datepicker
#selected="handleSelect"
v-model="dateSelected"
:upper-limit="picked_to"
:lower-limit="picked_from"
class="datepicker text-center" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const dateSelected = ref(new Date());
return {dateSelected}
},
methods: {
handleSelect() {
this.$emit('setDate')
}
}
}
</script>
In components\Ui\Navigation.vue I have:
import Datepicker from './Datepicker'
export default {
inject: ['$apiBaseUrl'],
name: 'Navigation',
components: {
Datepicker,
},
data() {
return {
// more code
}
},
methods: {
setDate() {
this.$emit('setDate');
}
},
}
In components\Content.vue I have:
<template>
<div class="main">
<div class="d-sm-flex>
<h1>{{ title }}</h1>
<Navigation
#setDate='setDate'
/>
</div>
<div class="content">
<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam,
quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo
consequat.</p>
</div>
</div>
</template>
<script>
import Navigation from './Ui/Navigation'
export default {
inject: ['$apiBaseUrl'],
name: 'Content',
components: {
Navigation,
},
props: {
title: String,
},
emits: ['setDate'],
data() {
return {
headers: {
'content-type': 'application/json',
'Accept': 'application/json'
},
from: '',
to: '',
}
},
methods: {
sendData() {
this.axios.post(`${this.$apiBaseUrl}/submit`, this.fields, {options: this.headers}).then((response) => {
if (response.data.code == 200) {
this.isReport = true;
}
}).catch((errors) => {
this.errors = errors.response.data.errors;
});
}
},
setDate() {
console.log('Date');
},
}
}
</script>
The problem
Although I select a date from the datepicker, the setDate() method is not executed. The Chrome console shows instead:
Maximum call stack size exceeded
Where is my mistake?
As comments have mentioned previously, the error usually occurs when having an infinite loop.
As Kissu pointed out this can easily happen if you have an event that is emitted to a parent component which then changes data that is passed as props to a child component which then triggers an event to the parent and so on.
In the code you showed I can't really find any loops, they might be in the parts you omitted.
Edit:
The infinite loop could actually be caused by the naming of your component.
You're using <datepicker ...> inside Datepicker.vue without registering the Datepicker of vue3-datepicker there explicitly.
That probably causes vue to recursively try to mount the component in itself causing the maximum stack trace error, but that's just a guess.
But there's a couple issues in your code still.
First off:
<template>
<datepicker
#selected="handleSelect" // <-- according to the docs there are no events that the datepicker emits
v-model="dateSelected"
:upper-limit="picked_to" // <-- where does the picked_to value come from?
:lower-limit="picked_from" // <-- where does the picked_from value come from?
class="datepicker text-center" />
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const dateSelected = ref(new Date());
return {dateSelected}
},
// do not mix options & composition api
methods: {
handleSelect() {
this.$emit('setDate')
}
}
}
</script>
You're mixing options and composition API, which is never a good idea.
There's also a couple values that seemingly come from nowhere and you're listening to a #selected event from the datepicker, which according to the docs doesn't exist, so you'll have to watch for changes in the selected date yourself.
The same component in the composition API would look like this:
<template>
<Datepicker v-model="initialDate" class="datepicker text-center" />
</template>
<script>
import Datepicker from "vue3-datepicker";
import { ref, watch } from "vue";
export default {
components: { Datepicker },
setup(props, { emit }) {
const initialDate = ref(new Date());
watch(initialDate, (newDate) => {
emit("setDate", newDate);
});
return { initialDate };
},
};
</script>
I tried to recreate the example you gave in a sandbox and I do not encounter any infinite loop issues.
You might wanna check it out and compare with your code and possibly fix all the other issues first and see if that helps your situation :)
I want to build a dropdown menu showing teachers who teach certain instruments (see image below)
The dropdown component is this:
import * as React from 'react'
import { useState, useEffect } from "react"
import { useStaticQuery, graphql } from 'gatsby'
import { BiChevronDown } from "react-icons/bi";
import StaffList from "./StaffList"
const rows = [
{
id: 1,
title: "Verantwortliche",
},
{
id: 2,
title: "Lehrende der Streichinstrumente",
instrument: "streichinstrumente"
},
{
id: 3,
title: "Lehrende der Zupfinstrumente",
},
{
id: 4,
title: "Lehrende des Tasteninstruments",
},
{
id: 5,
title: "Lehrende des Gesangs",
},
{
id: 6,
title: "Lehrende des Schlagzeugs",
},
{
id: 7,
title: "Lehrende des Akkordeons",
},
{
id: 8,
title: "Lehrende der Musiktheorie",
},
{
id: 9,
title: "Lehrende der Früherziehung",
}
]
class DropDownRows extends React.Component {
constructor(props) {
super(props);
this.state = {isToggleOn: false};
// This binding is necessary to make `this` work in the callback
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState(prevState => ({
isToggleOn: !prevState.isToggleOn
}));
}
render() {
return (
<div className="dropdown-rows">
{rows.map(row => (
<div key={row.id}>
<div className="row">
<div className="col">{row.title}</div>
<div className="col">
<BiChevronDown
onClick={this.handleClick}
style={{float: "right"}}/>
</div>
<div>
</div>
</div>
{this.state.isToggleOn ? <StaffList /> : ''}
</div>
))}
</div>
)
}
}
export default DropDownRows
it uses this StaffList component:
import * as React from 'react'
import { StaticQuery, graphql } from 'gatsby'
import { GatsbyImage, getImage } from "gatsby-plugin-image"
import { MDXProvider } from "#mdx-js/react"
function StaffList({ data }) {
return(
<StaticQuery
query={graphql`
query staffQuery {
allMdx {
edges {
node {
excerpt(pruneLength: 900)
id
body
frontmatter {
title
description
featuredImage {
childImageSharp {
gatsbyImageData(
placeholder: BLURRED
)
}
}
}
}
}
}
}
`}
render={data => (
<div className="staff-container">
{data.allMdx.edges.map(edge => (
<article>
<div className="staff-image-container">
<GatsbyImage key={edge.node.id} alt='some alt text' image={getImage(edge.node.frontmatter.featuredImage)} style={{margin: "0 auto", padding: "0"}} />
</div>
<div style={{margin: "0 2em"}}>
<div>
<h4 key={edge.node.id} style={{margin: "0"}}>{edge.node.frontmatter.title}</h4>
<h5>{edge.node.frontmatter.description}</h5>
</div>
<p><MDXProvider>{edge.node.excerpt}</MDXProvider></p>
</div>
</article>
))}
</div>
)}
/>
)
}
export default StaffList
this is one of the .mdx files i source my data from:
---
title: Diana Abouem à Tchoyi
featuredImage: Foto_07.jpg
description: Violine, Streicherklassen
---
Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
Right now the component looks like this:
How would I conditionally render the content for each bar. so not all bars opening with the same content but just the one clicked showing only those teachers who fall in the selected category. My idea is to have some sort of conditional statement, comparing the selected bar title with maybe a field in the teachers markdown file? Like
title: Hanna
category: Streichinstrumente
and then if (allMdx.edges.node.frontmatter.category === rows.title) {...}
That's as far as I can wrap my head around this. Maybe someone can help? Thank you in advance.
I would have one array of objects storing teachers' datas. Example below.
const teacherData = [
{ id: 'piano',
name: "Alex",
otherData: ...
},
... (similarly for the rest)
]
Then another array of objects/records for instrument-teachers. Such as:
const records = [
{
id: 'piano',
teacher: []
}
]
After this I would loop through each element in the teacherData array to get the finalized record Array with id being the type of instrument and teachers being all the teachers that play that type of instruments.
Now, getting into the display part. I would write my component as below:
const Dashboard = () => {
const [show, setShow] = React.useState([])
function handleClick(id) {
if show.includes(id) {
const newShow = show.filter(a => a !== id)
setShow(newShow)
} else {
const newShow = show.push(id)
setShow(newShow)
}
}
return (
<div className="dashboard">
{records.map((record) =>
(
<div className="instrument-wrapper">
<p onClick={() => handleClick(record.id)} >{record.id}</p> //The name of the instrument
{show.includes(record.id) ? (
<div>
... all the teachers here by mapping through the record.teachers
</div>
) : (null)}
<div>
)
)}
</div>
)
}
I am creating a user interface for a watch. In this UI if I select a watch of particular color it must be shown in the product preview. But when I select Color of the watch it is giving me error as
TypeError: Cannot read property 'imageUrl' of undefined.
Can anyone tell me why it is happening. I am giving you code for reference.
//App.js
import logo from './logo.svg';
import React, { Component } from 'react';
import classes from './App.module.css';
import ProductPreview from './ProductPreview/ProductPreview';
import ProductDetails from './ProductDetails/ProductDetails';
import TopBar from './TopBar/TopBar';
import ProductData from './utils/ProductData';
class App extends Component {
state = {
productData : ProductData,
currentPreviewImage : "https://imgur.com/Mplj1YR.png",
showHeartBeatSection: true,
}
onColorOptionClick = (pos) => {
const updatedPreviewImage = this.state.productData.colorOptions[pos].imageUrl;
console.log(updatedPreviewImage);
this.setState({currentPreviewImage : updatedPreviewImage});
}
render() {
return (
<div className="App">
<header className="App-header">
<TopBar />
</header>
<div className={classes.MainContainer}>
<div className={classes.ProductPreview}>
<ProductPreview currentPreviewImage={this.state.currentPreviewImage} showHeartBeatSection={this.state.showHeartBeatSection} />
</div>
<div className={classes.ProductData}>
<ProductDetails data={this.state.productData} onColorOptionClick={this.onColorOptionClick} />
</div>
</div>
</div>
);
}
}
export default App;
//ProductData.js
const ProductData = {
title: 'FitBit 19 - The Smartest Watch',
description: 'Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor Lorem ipsum dolor.',
colorOptions: [
{
styleName: 'Black Strap',
imageUrl: 'https://imgur.com/iOeUBV7.png'
},
{
styleName: 'Red Strap',
imageUrl: 'https://imgur.com/PTgQlim.png'
},
{
styleName: 'Blue Strap',
imageUrl: 'https://imgur.com/Mplj1YR.png'
},
{
styleName: 'Purple Strap',
imageUrl: 'https://imgur.com/xSIK4M8.png'
},
],
featureList: [
"Time", "Heart Rate"
]
}
Object.freeze(ProductData); //This line of code just makes your object as a constant. No values can be updated.
export default ProductData;
//ProductDetails.js
import React from 'react';
import classes from './ProductDetails.module.css';
const ProductDetails = (props) => {
const colorOptions = props.data.colorOptions.map((item, pos) => {
const classArr = [classes.ProductImage];
if(pos === 0) {
classArr.push(classes.selectedProductImage);
}
return(
<img key={pos} className={classArr.join(' ')} src={item.imageUrl} alt={item.styleName} onClick={() => props.onColorOptionClick()} />
);
})
const featureList = props.data.featureList.map((item, pos) => {
const classArr = [classes.FeatureItem];
if(pos === 0){
classArr.push(classes.SelectedFeatureItem);
}
return(
<button key={pos} className={classArr.join(' ')}>{item}</button>
)
})
return(
<div className = {classes.ProductData}>
<h1 className={classes.ProductTitle}>{props.data.title}</h1>
<p className={classes.ProductDescription}>{props.data.description}</p>
<h3 className={classes.SectionHeading}>Select Color</h3>
<div>
{colorOptions}
</div>
<h3 className={classes.SectionHeading}>Features</h3>
<div> {featureList}</div>
<button className={classes.PrimaryButton}>Buy Now</button>
</div>
);
}
export default ProductDetails;
Image of the UI :
After selecting watch of the color:
You forgot pass pos when call props.onColorOptionClick. Just update like this:
onClick={() => props.onColorOptionClick(pos)} />
You are not passing position value on the click function to props.onColorOptionClick in ProductDetails.js .
<img key={pos} className={classArr.join(' ')} src={item.imageUrl} alt={item.styleName} onClick={() => props.onColorOptionClick(pos)} />
This should work for you.
I recently watch a tutorial on YouTube about making Tabs using React and I got these codes.
App.js
import './App.css';
import { Tab } from './components/Tab/Tab';
const tabContent = [
{
title: "Chennai",
content: "Chennai is the capital of the Indian state." },
{
title: "Lorem Ipsum",
content: "Lorem ipsum dolor sit amet, consectetur adipisicing elit. Exercitationem."
},
{
title: "Dolor sit amet",
content: "Lorem ipsum dolor sit amet consectetur adipisicing elit. Eius, veniam!"
}];
function App() {
return (
<>
<div className="row">
<div className="col">
<div className="row text-left">
<Tab active={1}>
{ tabContent.map((tab, idx) =>
<Tab.TabPanel key={`Tab-${idx}`} tab={tab.title}>
{tab.content}
</Tab.TabPanel>) }
</Tab>
</div>
</div>
</div>
</>
);
}
export default App;
Tab.js
import React, { useState, useEffect } from 'react'
import './Tab.css'
export const Tab = ({children, active=0}) => {
const [activeTab, setActiveTab] = useState(active);
const [tabsData, setTabsData] = useState([]);
useEffect(() => {
let data = [];
React.Children.forEach(children, element => {
if(!React.isValidElement(element)) return; //exit the function
const {props: {tab, children}} = element;
data.push({tab, children})
})
setTabsData(data);
}, [children])
return (
<div className="w-100 custom-tab">
<ul className="nav nav-tabs">
{
tabsData.map(({tab}, idx) => (
<li className="nav-item">
<a
className = {`nav-link ${idx === activeTab ? "active" : ""}`}
href = "#"
onClick={() => setActiveTab(idx)}
>
{tab}
</a>
</li>
))
}
</ul>
<div className="tab-content p-3">
{tabsData[activeTab] && tabsData[activeTab].children}
</div>
</div>
)
}
const TabPanel = ({children}) => {
return {children}
}
Tab.TabPanel = TabPanel;
When I look to App.js I see there's a custom element named Tab.TabPanel. I think that is the children of the Tab element. And then I look to Tab.js and I see that there's TabPanel const that runs a function to return it's children.
I wonder, what is the purpose of Tab.TabPanel here? What will he render? And should I make codes like this in the future?
It's not the children, it's more of a namespace. The library author didn't want to export both Tab and TabPanel and decided to attach TabPanel to the function that is exported (for whatever reason). It's an opinionated choice that makes it harder to tree shake but just understand that Tab.TabPanel is just another React component.
The author could have just as easily done
export Tab = () => {...};
export TabPanel = () => {...};
and you could have consumed it as
import {Tab, TabPanel} from "./Tab.jsx";
const Foo = () => (
<div>
<TabPanel>
<Tab/>
<Tab/>
</TabPanel>
</div>
);
or however you wanted to use it.