I'm going to try and explain this as best I can, bear with me please sorry. I have an array called works that contains multiple objects from a portfolio. I imported the array into my component file and I can just rewrite it over and over but I have multiple elements for each value and my code will be very long. I feel like that isn't very DRY. How I can just put the information into my component once and have it iterate through everything in the array.
Here is a prototype of how I am currently doing it.
class PortfolioCard extends React.Component {
render() {
return (
<Card className>
<CardHeader
avatar={<Avatar aria-label="Recipe">R</Avatar>}
title={works[0].title}
subheader={works[0].name}
/>
<CardMedia className image={works[0].pic} />
<CardContent>
<Typography component="p">
{works[0].desciption}
</Typography>
</CardContent>
<CardActions className disableActionSpacing>
<IconButton aria-label="Live Site">
<FavoriteIcon> {works[0].link}
</FavoriteIcon>
</IconButton>
<IconButton aria-label="Github">
<ShareIcon> {works[0].github}
</ShareIcon>
</IconButton>
</CardActions>
</Card>
);
}
}
You can use .map to render list of cards something like below in render. Also when you render array of jsx elements don’t forget to set unique key to top jsx element in your case it’s Card
return (
{works.map((work, index) => (<Card key={"Key-"+index} className>
<CardHeader
avatar={<Avatar aria-label="Recipe">R</Avatar>}
title={work.title}
subheader={works.name}
/>
<CardMedia className image={work.pic} />
<CardContent>
<Typography component="p">
{work.desciption}
</Typography>
</CardContent>
<CardActions className disableActionSpacing>
<IconButton aria-label="Live Site">
<FavoriteIcon> {work.link}
</FavoriteIcon>
</IconButton>
<IconButton aria-label="Github">
<ShareIcon> {work.github}
</ShareIcon>
</IconButton>
</CardActions>
</Card>))}
);
You can use array#map to render your array with all the information.
class PortfolioCard extends React.Component {
render() {
return (
<Card className>
{works.map(({title, name, desciption, pic, link, github}) => ({
<React.Fragment>
<CardHeader
avatar={<Avatar aria-label="Recipe">R</Avatar>}
title={title}
subheader={name}
/>
<CardMedia className image={pic} />
<CardContent>
<Typography component="p">
{desciption}
</Typography>
</CardContent>
<CardActions className disableActionSpacing>
<IconButton aria-label="Live Site">
<FavoriteIcon> {link}
</FavoriteIcon>
</IconButton>
<IconButton aria-label="Github">
<ShareIcon> {github}
</ShareIcon>
</IconButton>
</CardActions>
<React.Fragment>
}))}
</Card>
);
}
}
Does this example help?
import React, { Component } from 'react';
class App extends Component {
state = {
works: [
{ name: 'First Work' },
{ name: 'Second Work' },
{ name: 'Third Work' },
],
};
render() {
return (
<div>
{this.state.works.map((work, i) => {
return <div key={i}>{work.name}</div>;
})}
</div>
);
}
}
export default App;
Related
Im having troubles to expand and contract a Collapse Component from MaterialUI since Im mapping and array and iterating the same component, when i press the collapse button, all components expands/contracts at the same time ( I suppose that Im not providing an identifier to point where the collapse function should be used),Im currently Using an State to control the collapse action:
const [expanded, setExpanded] = useState(false);
This is the return where I iterate the component using map on RecetasAll object,
return (
<React.Fragment key={RecetasAll.id}>
<Card className="searchItem" sx={{ maxWidth: 345 }}>
<CardHeader
action={<IconButton aria-label="settings"></IconButton>}
title={RecetasAll.titulo}
/>
<h4
className="Dieta"
style={{
backgroundColor: color(RecetasAll.Tiporeceta.tipoReceta),
}}
>
{RecetasAll.Tiporeceta.tipoReceta}
</h4>
<span className="Calorias">{RecetasAll.informacionNutricional}</span>
<CardMedia
component="img"
height="194"
image={RecetasAll.imagen}
alt="Paella dish"
/>
<CardContent>
{RecetasAll.Productos.map((Productos) => {
return (
<React.Fragment key={Productos.id}>
<Typography variant="body2" color="text.secondary">
{Productos.producto}
</Typography>
</React.Fragment>
);
})}
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<FavoriteIcon />
</IconButton>
<ExpandMore
expand={expanded}
onClick={() => setExpanded(!expanded)}
aria-expanded={expanded}
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent id={RecetasAll.id}>
<Typography paragraph>Preparacion:</Typography>
<Typography paragraph>{RecetasAll.pasos}</Typography>
<Button
href="#contained-buttons"
variant="contained"
onClick={handleSearch}
>
Ver mas
</Button>
</CardContent>
</Collapse>
</Card>
</React.Fragment>
);
});
return <>{itemRecetas}</>;
}
Im triying to set an id property to the CardContent since its the child of the Collapse component
id={RecetasAll.id}
this is the function Im using to expand or collapse but I dont know how to get the id properly to compare its value with expanded state:
const handleExpandClick = (e) => {
let clickedItemId = e.currentTarget.id;
if (expanded === clickedItemId) {
setExpanded(!expanded);
} else {
setExpanded(clickedItemId);
}
};
You could refactor every card into a new component and that way you can have a state to open/close the individual card. When iterating you can pass in the RecetasAll.
const MyCard = ({ RecetasAll }) => {
const [isExpanded, setIsExpanded] = useState(false);
const toggleExpanded = () => {
setIsExpanded(prevIsExpanded => !prevIsExpanded);
};
return (
...
<ExpandMore
expand={isExpanded}
onClick={toggleExpanded}
aria-expanded={isExpanded}
>
...
<Collapse in={isExpanded} timeout="auto" unmountOnExit>
...
);
};
If you don't want to use a new component you could store all the ids of the expanded cards in a state. Based on if the id is in the array the card will be expanded or collapsed.
const [expandedIds, setExpandedIds] = useState([]);
const toggleExpanded = (id) => {
setExpandedIds((prevExpandedIds) => {
// if id is already in array remove
if (prevExpandedIds.includes(id))
return prevExpandedIds.filter((i) => i !== id);
// else add to array
return [...prevExpandedIds, id];
});
};
return (
...
<ExpandMore
expand={expandedIds.includes(RecetasAll.id)}
onClick={() => toggleExpanded(RecetasAll.id)}
aria-expanded={expandedIds.includes(RecetasAll.id)}
>
...
<Collapse in={expandedIds.includes(RecetasAll.id)} timeout="auto" unmountOnExit>
...
)
I have an array of object which contains images:
import React, { Fragment } from "react"
import { useStaticQuery, graphql } from "gatsby"
import Button from "#material-ui/core/Button"
import Card from "#material-ui/core/Card"
import CardActions from "#material-ui/core/CardActions"
import CardContent from "#material-ui/core/CardContent"
import CardMedia from "#material-ui/core/CardMedia"
import { CardActionArea, GridList } from "#material-ui/core"
import CssBaseline from "#material-ui/core/CssBaseline"
import Grid from "#material-ui/core/Grid"
import Toolbar from "#material-ui/core/Toolbar"
import Typography from "#material-ui/core/Typography"
import Container from "#material-ui/core/Container"
import Link from "#material-ui/core/Link"
import homePageStyles from "../ui/styles/homePageStyles"
import mobile from "../../images/mobileApps.jpeg"
import coporation from "../../images/coporations.png"
import statute from "../../images/gavel.png"
import policy from "../../images/policies.png"
export default function HeroBlock() {
const classes = homePageStyles()
const data = useStaticQuery(graphql`
query getCardCategoriesAndCounts {
allStrapiCategory {
edges {
node {
category
strapiId
}
}
}
allStrapiMobileApplication {
totalCount
}
allStrapiCorporation {
totalCount
}
allStrapiStatutoryLaw {
totalCount
}
allStrapiPolicyIssue {
totalCount
}
}
`)
const categories = data.allStrapiCategory.edges
const categoryImages = [
{ cImg: mobile },
{ cImg: coporation },
{ cImg: statute },
{ cImg: policy },
]
console.log(categoryImages)
const catImage = categoryImages.map(categoryImage => {
return (
<Fragment>
<CardMedia
image={categoryImage.cImg}
className={classes.images}
></CardMedia>
</Fragment>
)
})
const cards = (
<Fragment>
{categories.map(c => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage}</div>
<CardActionArea>
<CardContent>
<Typography variant={"h6"}>
{c.node.category.toUpperCase().replace(/_/g, " ")}
</Typography>
<Fragment>
{c.node.category === "Mobile_Apps" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiMobileApplication.totalCount} mobile applications.`}
</Typography>
) : c.node.category === "Corporations" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiCorporation.totalCount} coporations.`}
</Typography>
) : c.node.category === "Statutes" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiStatutoryLaw.totalCount} statutory laws.`}
</Typography>
) : c.node.category === "Policy_Issues" ? (
<Typography variant="paragraph">
{`There are currently ${data.allStrapiPolicyIssue.totalCount} policy issues.`}
</Typography>
) : (
<div></div>
)}
</Fragment>
</CardContent>
</CardActionArea>
</Card>
))}
</Fragment>
)
return (
<React.Fragment>
<CssBaseline />
<main>
<div className={classes.heroContent}>
<Grid container>
<Grid item classes={{ root: classes.heroColumn }}>
<Grid container>
<Grid
item
container
direction="column"
classes={{ root: classes.hero }}
>
<Typography component="h1" variant="h2" align="center">
MIKE <br /> The Mobile Information Knowledge Ecosystem
</Typography>
<Typography variant="h5" align="center" paragraph>
<strong>
<em>The</em>{" "}
</strong>
central repository for how to extract data from a mobile
device, extract data from a mobile application, what data
the mobile app provider retains, and the specific laws
pertaining to accessing that data.
</Typography>
</Grid>
</Grid>
</Grid>
</Grid>
</div>
<Grid Container>
<Grid item container justifyContent="space-evenly">
{cards}
</Grid>
</Grid>
</main>
</React.Fragment>
)
}
Updated with the entire code base. There is multiple map statements. If that is the issue how do you map multiple arrays inside React? Does that require using some sort of data store? It is returning each one of the images 4 times, rather than each images per card.
You generate an array of card media components, and then render this within another loop, so this is why you see the 4 category images repeated for each category mapped later.
const catImage = categoryImages.map(categoryImage => { // array
return (
<Fragment>
<CardMedia
image={categoryImage.cImg}
className={classes.images}
></CardMedia>
</Fragment>
)
})
const cards = (
<Fragment>
{categories.map(c => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage}</div> // <-- render array of images here!!
<CardActionArea>
...
Assuming the categoryImages and categories arrays have the same length and sorted order, then you can render the card media component directly in the mapping and use the current index to access the correct image from the categoryImages array.
const cards = (
<Fragment>
{categories.map((c, index) => ( // <-- use current index
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<CardMedia
image={categoryImages[index].cImg} // <-- access by index
className={classes.images}
/>
<CardActionArea>
...
Array.map returns array. So catImage is an array that contains components.You can use each CardMedia component by indicating index number.
catImage[index]
catImage[0] is the first card.
So you can update your code like below:
const cards = ( // <==== line 74
<Fragment>
{categories.map((c, index) => (
<Card style={{ width: "15.625rem", wordWrap: "break-word" }}>
<div>{catImage[index]}</div>
I hope my answer will be solve your problem.
Thanks.
i have a code from reactjs that is working but, what i mean by woring because it does what it needs, in this case it expand and close when the button is close.
i have an error from the console
Failed prop type: Invalid prop `className` of type `object` supplied to `ForwardRef(IconButton)`, expected `string`.
in ForwardRef(IconButton) (created by WithStyles(ForwardRef(IconButton)))
while my code is simple, i just want to add material ui expander
const { schedule, classes, onClick, deleteScd, visible } = props;
const [expanded, setExpanded] = React.useState(false);
const handleExpandClick = (e) => {
// setExpanded(!expanded);
e.preventDefault()
e.stopPropagation();
if(!expanded){
setExpanded(true);
}else{
setExpanded(false);
}
console.log(expanded)
};
<Draggable>
<Card className={classes.root} >
<CardActions disableSpacing className={classes.cardExpand } onClick={onClick} >
<Box className={classes.head} >
<Typography variant="h5" className={classes.title}>
{title}
</Typography>
<Typography className={classes.timezone} color="textSecondary">
{timezone}
</Typography>
</Box>
<IconButton
className={classes.expand, {
[classes.expandOpen]: expanded,
}}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
<ExpandMoreIcon className={classes.expandIcon} />
</IconButton>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<Card className={classes.root} variant="outlined">
<CardContent style={{ padding: "10px 3px 3px 3px" }}>
Content
</CardContent>
</Card>
</Collapse>
</Card>
</Draggable>
i want to fix this because i have another problems with my ref
The className prop of the IconButton component must receive a string value (as the prop type warning states). You could use a ternary expression to apply a class only when your view is expanded, something like className={expanded ? classes.expandOpen : ''}.
I have a simple card component which takes a title and description as props. I wanted to reuse the component but this time I only want to add a button the component without changing the original component.
For example, below is the card component.
export const SimpleCard = React.memo(function SimpleCard({
title,
description,
}) {
const styles = useStyles();
const shadowStyles = useOverShadowStyles();
return (
<Card className={cx(styles.root, shadowStyles.root)}>
<CardContent>
<Typography variant="h5" align="center" style={{ fontWeight: "bold" }}>
{title}
</Typography>
<Typography variant="subtitle1" align="center">
{description}
</Typography>
</CardContent>
</Card>
);
});
export default SimpleCard;
So now, I want to import the <SimpleCard/> component and just pass the a new button component as a prop.
I'm using the card component in other places so I need to have this original component without the button component. I know I can make another separate Card with button component or just pass an empty prop to the Card component with button but I want to know if it's possible to access the original Card component and add the button component as props.
I imagine it would be something like this.
<SimpleCard
title={"Just shoot us an inquiry about your dream app or website!"}
description={`Send us more details and we can discuss further`}
How to pass the <Button/> component from here?
/>
You can use the children prop and put your content as the content of your JSX as in the following example:
<SimpleCard>
<span>this is the children</span>
</SimpleCard>
To use the children in your component you have to refer to it as the children prop:
export const SimpleCard = React.memo(function SimpleCard({
title,
description,
children,
}) {
const styles = useStyles();
const shadowStyles = useOverShadowStyles();
return (
<Card className={cx(styles.root, shadowStyles.root)}>
<CardContent>
<Typography variant="h5" align="center" style={{ fontWeight: "bold" }}>
{title}
</Typography>
<Typography variant="subtitle1" align="center">
{description}
</Typography>
{children}
</CardContent>
</Card>
);
});
export default SimpleCard;
Then the children prop will be basically replaced with <span>this is the children</span> or whatever toy want.
Another way is to pass your custom content as a custom prop like in the following example:
<SimpleCard customContent={<span>this is the children</span>} />
And then, as usual:
export const SimpleCard = React.memo(function SimpleCard({
title,
description,
customContent,
}) {
const styles = useStyles();
const shadowStyles = useOverShadowStyles();
return (
<Card className={cx(styles.root, shadowStyles.root)}>
<CardContent>
<Typography variant="h5" align="center" style={{ fontWeight: "bold" }}>
{title}
</Typography>
<Typography variant="subtitle1" align="center">
{description}
</Typography>
{customContent}
</CardContent>
</Card>
);
});
export default SimpleCard;
You can maybe try something like this
<SimpleCard
title={"Just shoot us an inquiry about your dream app or website!"}
description={`Send us more details and we can discuss further`}
displayButton={true}
/>
return (
<Card className={cx(styles.root, shadowStyles.root)}>
<CardContent>
<Typography variant="h5" align="center" style={{ fontWeight: "bold" }}>
{title}
</Typography>
<Typography variant="subtitle1" align="center">
{description}
</Typography>
</CardContent>
</Card>
{props.displayButton && <Button />}
);
I am getting the data from backend & I am passing the data to Product.js. But Cards are not coming only search bar is coming. I am able to see the data in console using console.log(this.state.products);. Imports are there.
Here is my Products.js file content.
import Product from "./Product";
class Products extends Component {
constructor() {
super();
this.state = {
products: [],
searchString: ""
};
}
componentDidMount() {
axios.get("http://localhost:9022/products/getAll").then(res => {
this.setState({ products: res.data });
console.log(this.state.products);
});
}
render() {
return (
<div>
{this.state.products ? (
<div>
<TextField
style={{ padding: 24 }}
id="searchInput"
placeholder="Search for products"
margin="normal"
onChange={this.onSearchInputChange}
/>
<Grid container spacing={24} style={{ padding: 24 }}>
{this.state.products.map(currentProduct => (
<Grid item xs={12} sm={6} lg={4} xl={3}>
<Product products={currentProduct} />
</Grid>
))}
</Grid>
</div>
) : (
"No products found"
)}
</div>
);
}
}
export default Products;
Here is my Product.js file content.
const Product = props => {
return (
<div>
{props.product ? (
<Card>
<CardContent>
<Typography gutterBottom variant="headline" component="h2">
{props.product.fields.title}
</Typography>
<Typography component="p">{props.product.fields.id}</Typography>
</CardContent>
</Card>
) : null}
</div>
);
};
export default Product;
Its a typo. Its props.producs but not props.product. You are passing products as a prop to Product component but accessing as props.product so you need to access it using props.products. Try the below corrected code
const Product = (props) => {
return(
<div>
{ props.products ? (
<Card>
<CardContent>
<Typography gutterBottom variant="headline" component="h2">
{props.products.title}
</Typography>
<Typography component="p">
{props.products.id}
</Typography>
</CardContent>
</Card>
): null }
</div>
)
}
export default Product;
Also when you do .map or .forEach or for loop you should add unique id as key to the parent jsx element inside loop. iN your code you need to add unique like below
If you have unique id from each currentProduct then do this
{ this.state.products.map(currentProduct => (
<Grid key={currentProduct.id} item xs={12} sm={6} lg={4} xl={3}>
<Product products={currentProduct} />
</Grid>
))}
otherwise add index as key like below
{ this.state.products.map((currentProduct, index) => (
<Grid key={`Key_${index}`} item xs={12} sm={6} lg={4} xl={3}>
<Product products={currentProduct} />
</Grid>
))}