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

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

Related

pass multiple refs to child components

Before diving to the main problem, my use case is I am trying to handle the scroll to a desired section. I will have navigations on the left and list of form sections relative to those navigation on the right. The Navigation and Form Section are the child component. Here is how I have structured my code
Parent.js
const scrollToRef = ref => window.scrollTo(0, ref.current.offsetTop);
const Profile = () => {
const socialRef = React.useRef(null);
const smsRef = React.useRef(null);
const handleScroll = ref => {
console.log("scrollRef", ref);
scrollToRef(ref);
};
return (
<>
<Wrapper>
<Grid>
<Row>
<Col xs={12} md={3} sm={12}>
<Navigation
socialRef={socialRef}
smsRef={smsRef}
handleScroll={handleScroll}
/>
</Col>
<Col xs={12} md={9} sm={12}>
<Form
socialRef={socialRef}
smsRef={smsRef}
/>
</Col>
</Row>
</Grid>
</Wrapper>
</>
);
};
Navigation.js(child component)
I tried using forwardRef but seems like it only accepts one argument as ref though I have multiple refs.
const Navigation = React.forwardRef(({ handleScroll }, ref) => {
// it only accepts on ref argument
const items = [
{ id: 1, name: "Social connections", pointer: "social-connections", to: ref }, // socialRef
{ id: 2, name: "SMS preference", pointer: "sms", to: ref }, // smsRef
];
return (
<>
<Box>
<UL>
{items.map(item => {
return (
<LI
key={item.id}
active={item.active}
onClick={() => handleScroll(item.to)}
>
{item.name}
</LI>
);
})}
</UL>
</Box>
</>
);
});
export default Navigation;
Form.js
I do not have idea on passing multiple refs when using forwardRef so for form section I have passed the refs as simple props passing.
const Form = ({ socialRef, smsRef }) => {
return (
<>
<Formik initialValues={initialValues()}>
{({ handleSubmit }) => {
return (
<form onSubmit={handleSubmit}>
<Social socialRef={socialRef} />
<SMS smsRef={smsRef} />
</form>
);
}}
</Formik>
</>
);
};
Social.js
const Social = ({ socialRef }) => {
return (
<>
<Row ref={socialRef}>
<Col xs={12} md={3}>
<Label>Social connections</Label>
</Col>
<Col xs={12} md={6}></Col>
</Row>
</>
);
};
Can anyone help me at passing multiple refs so when clicked on the particular navigation item, it should scroll me to its respective component(section).
I have added an example below. I have not tested this. This is just the idea.
import React, { createContext, useState, useContext, useRef, useEffect } from 'react'
export const RefContext = createContext({});
export const RefContextProvider = ({ children }) => {
const [refs, setRefs] = useState({});
return <RefContext.Provider value={{ refs, setRefs }}>
{children}
</RefContext.Provider>;
};
const Profile = ({ children }) => {
// ---------------- Here you can access refs set in the Navigation
const { refs } = useContext(RefContext);
console.log(refs.socialRef, refs.smsRef);
return <>
{children}
</>;
};
const Navigation = () => {
const socialRef = useRef(null);
const smsRef = useRef(null);
const { setRefs } = useContext(RefContext);
// --------------- Here you add the refs to context
useEffect(() => {
if (socialRef && smsRef) {
setRefs({ socialRef, smsRef });
}
}, [socialRef, smsRef, setRefs]);
return <>
<div ref={socialRef}></div>
<div ref={smsRef}></div>
</>
};
export const Example = () => {
return (
<RefContextProvider>
<Profile>
<Navigation />
</Profile>
</RefContextProvider>
);
};

React nested rendering

I have an object containing several arrays like:
const Items = {
Deserts: [{name:cookies}, {name:chocolate}],
Fruits: [{name:apple}, {name:orange}]
...
}
I want to render it as:
<title>Deserts</title>
Cookies
Chocolate
<title>Fruits</title>
Apple
Orange
So first I render the type:
return <Grid>
{Object.keys(Items).map(type => {
return <Box key={type}>
{type} // <== this would be the title, Fruits or whatever
{this.createCard(Items[type])}
</Box>
})}
</Grid>
Then I want to add the content of each type:
createCard = (items) => {
return <Box>
{items.forEach(item => {
return <div>{item.name}</div>
})}
</Box>
}
Content is not returned, it works fine if instead of a forEach loop I just add some predefined content.
The forEach method only iterates over all items but does not return anything. Instead, what you want to use is a map.
Also, make sure you wrap your return value when it extends more than one line:
createCard = (items) => {
return (<Box>
{items.map(item => {
return <div>{item.name}</div>
})}
</Box>);
}
If you don't do that it works as if a semicolon was introduced after the first line. So, in reality, your current code is equivalent to:
createCard = (items) => {
return <Box>;
// The code below will never be executed!
{items.forEach(item => {
return <div>{item.name}</div>
})}
</Box>
}
When returning an element you need to wrap it in parentheses, ( ) and I generally use map instead of forEach.
const createCard = items => {
return (
<Box>
{items.map(item => {
return ( <div>{item.name}</div> )
})}
</Box>
)
}
I believe you can also nix the curly braces if the function doesn't need any logic.
const createCard = items => (
<Box>
{items.map(item => {
return ( <div>{item.name}</div> )
})}
</Box>
)
-- Edit --
Now that I'm reading back over your question a much cleaner way to approach this would be to declare the component function outside of your class like
class Grid extends react.Component {
render(){
return (
<Box>
<CreateCard props={ item }/>
</Box
)
}
}
const CreateCard = props => (
<Box>
{props.items.map(item => {
return ( <div>{item.name}</div> )
})}
</Box>
)

How can i use map() and find() in the same codeline

I have 2 files, like in the example:
player.js - for now, I use only one item in the object
const players = {
playerId: '5555',
playerName: 'JHON',
playerTeams: [real, barcelona, liverpol],
};
Team.js
const Teams = [
{ name: 'real', teamImageSrc: '' },
{ name: 'barcelona', teamImageSrc: '' },
{ name: 'liverpol', teamImageSrc: '' },
];
My purpose at this stage of the project is to check that my UI code display the data well, and later I will correct the logic behind the UI, and right now what I'm trying to do is to display the list of teams the player is member, This task that I have managed to do using map () but I need to locate the image of each value I got from the map () like in the example below:
class GridPlayerTeamsMembership extends React.Component {
render() {
const { classes } = this.props;
return (
<div className={classes.root}>
<List>
{players.playerTeams.map(value => (
<ListItem dense button className={classes.listItem}>
<Avatar ? />
<ListItemText primary={` ${value}`} />
</ListItem>
))}
</List>
</div>
);
}
}
GridPlayerTeamsMembership.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(GridPlayerTeamsMembership);
You can make a function like this:
teamImage = (teamName) => Teams.find(t=>t.name===teamName).teamImageSrc
And use the teamImage like this:
<Avatar src={this.teamImage(value)} />
ok, I succeeded but without the this.
class GridPlayerTeamsMembership extends React.Component {
render() {
const { classes } = this.props;
teamImage = (teamName) => Teams.find(t=>t.name===teamName).teamImageSrc
return (
<div className={classes.root}>
<List>
{players.playerTeams.map(value => (
<ListItem key={value} dense button className={classes.listItem}>
<img src={teamImage(value)} className={classes.img} />
<ListItemText primary={` ${value}`} />
</ListItem>
))}
</List>
</div>
);
}
}
GridPlayerTeamsMembership.propTypes = {
classes: PropTypes.object.isRequired,
};
export default withStyles(styles)(GridPlayerTeamsMembership);

React + MaterialUi handling actions in IconMenu and ListItem

I'm learning react and I try to create simple TODO based on material-ui, I have problem with handling IconMenu menu actions, menu is displayed in listItem element. At this moment I have no idea how trigger deleteItem function with item name as a parameter when delete action is clicked in menu.
const iconButtonElement = (
<IconButton touch={true} tooltip="More" tooltipPosition="bottom-left">
<MoreVertIcon color="black"/>
</IconButton>
);
const rightIconMenu = (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem value="done" leftIcon={<Done />}>Mark as done</MenuItem>
<MenuItem value="delete" leftIcon={<Delete />}>Delete</MenuItem>
</IconMenu>
);
class TodoElements extends Component {
deleteItem(nameProp)
{
this.props.delete(nameProp);
}
render() {
var listItemRender = function(item) {
return <ListItem key={item.name} primaryText={item.name} style={listItemStyle} rightIconButton={rightIconMenu}/>
};
listItemRender = listItemRender.bind(this);
return (
<List>
{this.props.items.map(listItemRender)}
</List>
)
}
}
As far as I can see, you should be able to add an onChange handler to your IconMenu. So your rightIconMenu can look like this:
const RightIconMenu = ({onChange}) => (
<IconMenu iconButtonElement={iconButtonElement} onChange={onChange}>
<MenuItem value="done" leftIcon={<Done />}>Mark as done</MenuItem>
<MenuItem value="delete" leftIcon={<Delete />}>Delete</MenuItem>
</IconMenu>
);
Then you can use it in your TodoElements like this:
class TodoElements extends Component {
constructor(props){
super(props);
this.state = {
items: props.items
};
}
createChangeHandler = (nameProp) => {
return (event, value) => {
if(value==="delete"){
this.deleteItem(nameProp);
}
};
}
deleteItem = (nameProp) =>
{
this.setState({
items: this.state.items.filter((item) => {
return item.name !== nameProp);
})
});
}
render() {
return (
<List>
{this.state.items.map((item) => {
<ListItem key={item.name} primaryText={item.name} style={listItemStyle}
rightIconButton={<RightIconMenu onChange={this.createChangeHandler(item.name)} />}/>
})}
</List>
)
}
}
Alternative
As an alternative solution you could bind an onClick handler to your delete MenuItem instead. I would probably implement it like this:
const RightIconMenu = ({onDelete}) => (
<IconMenu iconButtonElement={iconButtonElement}>
<MenuItem value="done" leftIcon={<Done />}>Mark as done</MenuItem>
<MenuItem value="delete" leftIcon={<Delete />} onClick={onDelete}>Delete</MenuItem>
</IconMenu>
);
And then replace the appropriate functions in the TodoElements:
createChangeHandler = (nameProp) => {
return (event, value) => {
this.deleteItem(nameProp);
};
}
render() {
return (
<List>
{this.state.items.map((item) => {
<ListItem key={item.name} primaryText={item.name} style={listItemStyle}
rightIconButton={<RightIconMenu onDelete={this.createDeleteHandler(item.name)} />}/>
})}
</List>
)
}
As for handling the state of your list of items, you should probably take a look at global state management such as Redux.
I think that a nicer approach would be using the onTouchTap every MenuItem has, So the onChange function won't have a switch or many if statements.
I'm actually using it when I iterate over all menu items,
To me it looks like this:
_.map(menuItems, (currItem, index) => {
return (<MenuItem primaryText={currItem.primaryText}
rightIcon={currItem.rightIcon}
leftIcon={currItem.leftIcon}
key={`menu-item-${index}`}
value={currItem.value}}
onTouchTap={currItem.onTouchTap}/>)
})

React DnD drags whole list of cards instead of single card

I am trying to use react DnD in my react Project. In my render method I define a variable named Populate like show below, which returns a list of cards like this
render() {
var isDragging = this.props.isDragging;
var connectDragSource = this.props.connectDragSource;
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<Card style= {{marginBottom: 2, opacity: isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
//onTouchTap={() => {this.handleClick(value.id)}}
zDepth={this.state.shadow}>
<CardHeader
title={value.Episode_Name}
//subtitle={value.description}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</MuiThemeProvider>
</div>
)
});
And my return of render method looks like this
return connectDragSource (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
)
Problem is when I try using drag, then the whole list of cards gets selected for drag. I want all the cards having individual drag functionality.
If you want each card to have drag functionality than you'll have to wrap each card in a DragSource, and not the entire list. I would split out the Card into it's own component, wrapped in a DragSource, like this:
import React, { Component, PropTypes } from 'react';
import { ItemTypes } from './Constants';
import { DragSource } from 'react-dnd';
const CardSource = {
beginDrag: function (props) {
return {};
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
}
class CardDragContainer extends React.Component {
render() {
return this.props.connectDragSource(
<div>
<Card style= {{marginBottom: 2, opacity: this.props.isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.props.onMouseOver}
onMouseOut={this.props.onMouseOut}
zDepth={this.props.shadow}>
<CardHeader
title={props.title}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</div>
)
}
}
export default DragSource(ItemTypes.<Your Item Type>, CardSource, collect)(CardDragContainer);
Then you would use this DragContainer in render of the higher level component like this:
render() {
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<CardDragContainer
value={value}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
shadow={this.state.shadow}
/>
</MuiThemeProvider>
</div>
)
});
return (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
);
}
That should give you a list of Cards, each of which will be individually draggable.

Categories

Resources