How to build a generic menu component? - javascript

I have to build a menu screen component that can be used later on easily. The component only has options in a list way.
const options = [
{
text: 'Option 1',
},
{
text: 'Option 2',
},
{
text: 'Option 3',
},
];
and I just map it inside the component.
const Menu = () => {
return (
<div>
{options.map((option, index) => (
<MenuOption text={option.text} index={index} />
))}
</div>
);
};
const MenuOption = ({ text, index }) => {
return (
<div>
<p>{text}</p>
</div>
);
};
So anyone can use this menu component by passing in the options data. What i want is for this component to also run functions corresponding to the option clicked. So we can easily pass the function that needs to be run as a prop to the component. The main question is that how do i relate/map the options with the corresponding function?
NOTE: I can't send the functions with the options data object in the props.

I mean you can just send one callback to the Menu component:
const Menu = ({ onOptionClick }) => {
return (
<div>
{options.map((option, index) => (
<MenuOption text={option.text} index={index} onClick={onOptionClick} />
))}
</div>
);
};
const MenuOption = ({ text, index, onClick }) => {
return (
<div onClick={() => { onClick(index, text, whatever) }}>
<p>{text}</p>
</div>
);
};

Related

Pass render props to children in a React complement

I have a component, called Pagination and I want to pass multiple props down to the children of the component, similar to active on the Menu item of Headless UI’s menu:
<Menu.Item>
{({ active }) => (
<a className={`${active && 'bg-blue-500'}`} href="/account-settings">
Account settings
</a>
)}
</Menu.Item>
I want to pass an activeItem prop and itemsCount prop, and allow users to render it in whatever way they want to. An example implementation could be:
<Pagination>
{({ activeItem, itemsCount }) => (
<span>{ activeItem }</span>
<span>{ itemCount }</span>
)}
</Pagination>
And the baseline component for Pagination is:
interface PaginationProps extends React.HTMLAttributes<HTMLDivElement> {
children: React.ReactElement;
}
const Pagination = ({ children, ...rest }: PaginationProps) => {
const { state } = useLightboxContext();
return (
<div {...rest}>
</div>
);
};
How can I do this?
As I mentioned in a comment, here is the way to do it:
function Comp({ children }) {
return children({ activeItem: 1, itemsCount: 2 });
}
function App() {
return (
<Comp>
{({ activeItem, itemsCount }) => (
<p>
{activeItem}, {itemsCount}
</p>
)}
</Comp>
);
}
This will render 1, 2.

Why do Material UI tabs stop working when I use a .map to populate the content dynamically instead of hard coding?

I have implemented Material UI's tabs successfully by hard-coding the content, but when I tried to make a my hard coded tabs with a .map function to populate the content from a data source (simple json), it no longer works. Can anyone see why? The only change I made was to the MyTabs component below where there are now two .map functions instead of hard coded tabs.
Many thanks for your help!
Here is my data:
export const TabsData = [
{
tabTitle: 'Tab 1',
tabContent: 'Hello 1',
},
{
tabTitle: 'Tab 2',
tabContent: 'Hello 2',
},
{
tabTitle: 'Tab 3',
tabContent: 'Hello 3',
},
];
Here is my MyTabs component:
import React, { useState } from 'react';
// Material UI
import Tabs from '#material-ui/core/Tabs';
import Tab from '#material-ui/core/Tab';
// Data
import { TabsData } from '../../page-templates/full-page-with-tabs/FullPageWithTabsData';
//Components
import TabContentPanel from '../tabs/tab-content-panel/TabContentPanel';
const MyTabs = () => {
const classes = useStyles();
const initialTabIndex = 0;
const [value, setValue] = useState(initialTabIndex);
const handleChange = (event, newValue) => {
setValue(newValue);
};
return (
<>
<Tabs
value={value}
onChange={handleChange}
aria-label=""
className={classes.tabHeight}
classes={{ indicator: classes.indicator }}
>
{TabsData.map((tabInfo, index) => (
<>
<Tab
label={tabInfo.tabTitle}
id={`simple-tab-${index}`}
ariaControls={`simple-tabpanel-${index}`}
/>
</>
))}
</Tabs>
{TabsData.map((tabInfo, index) => (
<TabContentPanel value={value} index={index}>
{tabInfo.tabContent}
</TabContentPanel>
))}
</>
);
};
export default MyTabs;
And here is the TabsPanel component:
import React from 'react';
import PropTypes from 'prop-types';
// Material UI
import { Box } from '#material-ui/core';
function TabContentPanel(props) {
const { children, value, index, ...other } = props;
const classes = useStyles();
return (
<div
role="tabpanel"
hidden={value !== index}
id={`simple-tabpanel-${index}`}
aria-labelledby={`simple-tab-${index}`}
{...other}
>
{value === index && <Box className={classes.contentContainer}>{children}</Box>}
</div>
);
}
TabContentPanel.propTypes = {
children: PropTypes.node,
index: PropTypes.any.isRequired,
value: PropTypes.any.isRequired,
};
export default TabContentPanel;
It doesn't work because you added some extra Fragments (<> and </>) in the Tabs component and the Tabs component doesn't accept a Fragment as a child:
If you remove those, it will work as expected:
{TabsData.map((tabInfo, index) => (
<Tab
label={tabInfo.tabTitle}
id={`simple-tab-${index}`}
key={tabInfo.tabTitle}
ariaControls={`simple-tabpanel-${index}`}
/>
))}
And please use the key prop with a unique id if you create an array of elements. Each child in a list should have a unique "key" prop.

How to pass data from mapped objects to its parent component in React?

I'm building a shopping cart app, the app includes each item as a card component. I rendered these cards by mapping some dummy object data like this:
const Home = () => {
const dummyData = [
{
id: 1,
title: 'tshirt',
price: 10
},
{
id: 2,
title: 'coat',
price: 20
}
]
const RenderCards = () => {
return (
dummyData.map(
(d) => {
return (
<Card key={d.id} title={d.title} price={d.price} handleAddToCart={handleAddToCart}/>
)
}
)
)
}
const handleAddToCart = () => {
// maybe access id and price here?
}
return (
<>
<RenderCards />
</>
)
}
and the Card component being rendered:
const Card = ({id, title, price}) => {
return (
<>
<div key={id}>
<p>{title}</>
<p>{price}</>
<button onClick={handleAddToCart}>Add to cart</button>
</div>
</>
)
}
Now on click of the button in each card, I'd like to send the data (the id of the card and the price of the item) back to the parent Home component. Say 2nd card is clicked, I want to have access to id and price in Home.
EDIT:
Maybe I didn't make myself clear, I'd like to access the clicked card's price and id in handleAddToCart function.
You can either pass the handler down and have the child pass the details to it, like this:
items.map(item => <Item addToCart={addToCart} {...item} />)
const Item = ({ id, name, addToCart }) =>
<div>
{name}
<button onClick={() => addToCart(id)}>Add to Cart</button>
</div>
Or pass down a values-included callback like this:
items.map(item => <Item addToCart={() => handleAddToCart(item.id)} {...item} />)
const Item = ({ id, name, addToCart }) =>
<div>
{name}
<button onClick={addToCart}>Add to Cart</button>
</div>
In <Home /> component, first you can introduce a new state with useState as:
const [selectedItem, setSelectedItem] = useState(null)
Then pass down through props the setSelectedItem in order to be able to trigger there:
<Card key={d.id} title={d.title} price={d.price} handleAddToCart={handleAddToCart}
setSelectedItem={setSelectedItem} />
Then in <Card /> component use as:
const Card = ({id, title, price, setSelectedItem}) => {
return (
<>
<div key={id}>
<p>{title}</>
<p>{price}</>
<button onClick={() => {
handleAddToCart();
setSelectedItem({ id, title, price});
}}>Add to cart</button>
</div>
</>
)
}
+1 suggestion:
I would pass down to <Card /> component the details in one attribute as:
<Card key={d.id}
data={d}
handleAddToCart={handleAddToCart}
setSelectedItem={setSelectedItem} />
Then destructure inside as:
const Card = (props) => {
const { data, setSelectedItem, handleAddToCart } = props
const { id, title, price } = data
// more code
}

React : using props outside of render

I use the semantic tab components of the user interface in my project. I want to render dynamically tabs with tab items. But, I can’t call the props dialog outside the component to go through all the elements of the object and pass a property to Tab.Pane that will display the contents
const panes = dialogs.map((d, index) => ({
menuItem:
<Menu.Item key={index}>
<AppMessageUserList {...d} />
</Menu.Item>,
render: () =>
<Tab.Pane>
<AppMessageItems {...d} />
</Tab.Pane>
}));
Render:
const AppMessages = () => {
return (
<Container>
<Tab
menu={{fluid: true, vertical: true}}
menuPosition='left'
panes={panes}
/>
</Container>
);
};
Data from redux
function mapStateToProps(state) {
return {
dialogs: state.dialogs,
}
}

How can i achieve 100% reusability of that react-bootstrap component?

I am trying to create a reusable carousel using react-bootstrap, i could create that one"
const ReusableCarousel = (props) => {
return (
<Carousel className="mt-4 border">
{props.items.map((item, index) => {
return (
<Carousel.Item key={index}>
<Row>
{props.children}
</Row>
</Carousel.Item>
);
})}
</Carousel>
);
}
Now as you see it is reusable till the point of the carousel item, props.children may represent multiple elements per one slide or single element per slide, but i can not achieve that according to my logic
in the parent:
<ReusableCarousel items={categories}> //categories is an array of arrays of objects
{
//item prop should be passed here to carouselItemCategories component
//but i couldn't find a way to achieve that
<CarouselItemCategories key={i} />
}
</ReusableCarousel>
carouselItemCategories Component:
const CarouselItemCategories = (props) => {
//still in my dreams
const { item } = props;
return (
<>
{
item.map((c, index) => {
return (
<Col key={index}>
//a category card here
</Col>
);
})
}
</>
);
}
Now i know what makes it work, it is about passing item prop(which represent an array of objects represents fraction of my categories) but i could not find any way to achieve that
you can imagine categories like that:
const categories = [
[
{
title: 'Laptops',
background: 'red'
},
{
title: 'Tablets',
background: 'blue';
}
],
[
{
title: 'Mouses',
background: 'yellow'
},
{
title: 'Printers',
background: 'orange';
}
]
]
If I understand you correctly, you want to use each of the items from your ReusableCarousel to generate a new CarouselItemCategories with the individual item passed in as a prop?
If so, you may want to take a look at the cloneElement function. Effectively, inside your mapping of the items prop, you would create a clone of your child element, and attach the individual item as a prop to that clone. Something like this:
const ReusableCarousel = (props) => {
return (
<Carousel className="mt-4 border">
{props.items.map((item, index) => {
return (
<Carousel.Item key={index}>
<Row>
{React.cloneElement(props.children, { item })}
</Row>
</Carousel.Item>
);
})}
</Carousel>
);
}
I just found another solution by using react context, i created a CarouselContext module :
import React from 'react';
const CarouselContext = React.createContext([]);
export const CarouselProvider = CarouselContext.Provider;
export default CarouselContext
and then in the ReusableCarousel component:
import { CarouselProvider } from './carouselContext'
const ReusableCarousel = (props) => {
return (
<Carousel >
{props.items.map((item, index) => {
return (
<Carousel.Item key={index} >
<Row >
{
<CarouselProvider value={item}>
{props.children}
</CarouselProvider>
}
</Row>
</Carousel.Item>
);
})}
</Carousel>
);
}
and then using the context to get item global variable
const CarouselItemCategories = () => {
const item = useContext(CarouselContext);
return (
<>
{
item.map((c, index) => {
return (
<Col>
//category card here
</Col>
);
})
}
</>
);
}

Categories

Resources