Pass render props to children in a React complement - javascript

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.

Related

How to pass props to a children object? children will only have a button or a text

export const PopOver = ({
children, -----------------------------------------> I get children
headerText,
bodyText,
hasHeader,
hasBody,
hasFooter,
cancelButtonText,
okButtonText,
hasCloseButton,
onOkPress,
onCancelPress,
...rest
}: PopoverProps): JSX.Element => {
return (
<Popover
trigger={(triggerProps) => {
return <Button {...triggerProps}>Trigger</Button>; //How do I pass trigger props to the children I get???
}}
{...rest}
>
In the code I have mentioned above, I want to return children (this.props.children) instead of the button to make it more generic. something to the equivalent of:
return (
<Popover
trigger={(triggerProps) => {
return <child {...triggerProps}>Trigger</child>;
}}
{...rest}
>
Try this:
<Popover
trigger={triggerProps =>
React.Children.map(children, child =>
React.isValidElement(child)
? React.cloneElement(child, { ...triggerProps })
: child
)
}
{...rest}
/>
References:
https://reactjs.org/docs/react-api.html#reactchildren
https://reactjs.org/docs/react-api.html#cloneelement

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
}

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>
);
})
}
</>
);
}

Function components in react native

I am trying to change view of ListItem by pressing on it.
In My screen which is normal React component i have functional List component and selectedItemState (only 1 or no items will be selected).
In List there are few also functional ListItem components.
The problem is lack of re-render ability for item.
I've tried memo as official React page says but with no results. Changing components to normal ones gave the same result.
Screen Component:
export default class myScreen extends Component {
constructor () {
super ()
this.state = {
data: [], // <-- there are my objects
isDataEmpty: false,
selectedItemId: ''
}
}
// ... some code
render () {
return (
<View style={styles.container}>
<List
itemList={this.state.data}
onItemPress={ /* go to next screen */}
onItemLongPress={id => {
this.setState({ selectedItemId: this.state.selectedItemId === id ? '' : id })
}}
selectedItemId={this.state.selectedItemId}
/>
</View>
)
}
}
List Component:
const List = props => {
return (
<FlatList
style={style.itemList}
data={props.itemList}
renderItem={info => (
<ListItem
item={info.item}
selectedItemId={props.selectedItemId}
onItemPress={id => props.onItemPress(id)}
onItemLongPress={id => props.onItemLongPress(id)}
/>
)}
/>
)
}
const areEqual = (previous, next) => {
return next.selectedItemId !== '' && (previous.selectedItemId === next.selectedItemId)
}
export default React.memo(List, areEqual)
List Item Component:
const ListItem = props => {
return (
<TouchableWithoutFeedback
onPress={() => props.onItemPress(props.item.id)}
onLongPress={() => {
props.onItemLongPress(props.item.id)
} }>
<View style={style.listItem}>
<Image resizeMode='cover' source={props.item.image} style={style.image} />
<Text>{props.selectedItemId === props.item.id ? 'XXX' : props.item.name}</Text>
</View>
</TouchableWithoutFeedback>
)
}
const areEqual = (previous, next) => {
return next.selectedItemId && (next.selectedItemId === next.item.id)
}
export default React.memo(ListItem, areEqual)
After pressing on any item i want it name to change to 'XXX'. If item will be pressed twice all items should be in normal state
As long as there are no changes on the item itself there will be no rerender of the according listitem.
You could try to force a rerender of the list by changing the value of the extraData flatlist prop though.

How re-render just copmonent to which I clicked - react

I'm trying to make a simple color memory game in React (find where there are two identical images).
When I click on one card all of the other cards re-render. How can I prevent this?
//app class
handleClick = index => {
this.setState((prevState) => {
var temp = [...prevState.clickedPicture, index]
return{clickedPicture: temp}
})
}
isClicked = (index) => this.state.clickedPicture.indexOf(index) === -1
render() {
return(
<div className="content">
<div className="header">
<h1>Memory</h1>
</div>
<div className="main">
_.shuffle(this.state.colors).map((current,index) =>
<Game
key={index}
index={index}
current={current}
status={this.state.status}
handleClick={this.handleClick}
bool={this.isClicked(index)}
/>)
</div>
}
// Game component
class Game extends Component {
clickHandle = () => {
if(this.props.bool){
this.props.handleClick(this.props.index)
}
}
render() {
return(
<div className={this.props.status}
style={{ backgroundColor: this.props.bool ?
'black' : this.props.current }}
onClick={this.clickHandle}>
</div>
);
}
}
Make Cards their own component so they have access to lifecycle hooks. then
make the Cards a PureComponents, therefore they will only update if the reference to a prop changes.

Categories

Resources