If statement inside .map()? - javascript

I'd like to know how I can implement an if statement inside .map()
See code below.
Currently the delete button is disabled if the image is not uploaded by the current user, but my goal is to not render the delete button at all.
return <Grid container justify="center" spacing={2}>
{/* All images */}
{docs && docs
// For every image
.map(image => (
// In a grid item
<Grid className="img-item" item key={image.id} xs={12} md={6} lg={4}>
{/* all accounts */}
{docs2 && docs2
// For every single image:
// Filter statament only contains the user of specific image
// https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d
.filter((user) => image.userID === user.userID)
//Now you have the user that belongs to the image.ID
.map(user => (
<div key={image.id}>
<img src={image.url} alt="uploaded pic" />
<Typography variant="subtitle1"> By {user.userName}
{/* How do I implement this if statement for the IconButton? */}
{/* if ({handleButton(image.userID)} === false){
return
} */}
{/* Delete button */}
<IconButton
disabled={handleButton(image.userID)}
color="secondary" aria-label="delete image"
onClick={() => handleDeleteImage(image.id, image.userID, image.name)}
component="span" >
<DeleteForever />
</IconButton>
</Typography>
</div>
))}
</Grid>
))}
</Grid>
}
export default ImageGrid;

What you are looking for is
return <Grid container justify="center" spacing={2}>
{/* All images */}
{docs && docs
// For every image
.map(image => (
// In a grid item
<Grid className="img-item" item key={image.id} xs={12} md={6} lg={4}>
{/* all accounts */}
{docs2 && docs2
// For every single image:
// Filter statament only contains the user of specific image
// https://medium.com/poka-techblog/simplify-your-javascript-use-map-reduce-and-filter-bd02c593cc2d
.filter((user) => image.userID === user.userID)
//Now you have the user that belongs to the image.ID
.map(user => (
<div key={image.id}>
<img src={image.url} alt="uploaded pic" />
<Typography variant="subtitle1"> By {user.userName}
{/* How do I implement this if statement for the IconButton? */}
{/* if ({handleButton(image.userID)} === false){
return
} */}
{/* Delete button */}
{ handleButton(image.userID) &&
<IconButton
disabled={handleButton(image.userID)}
color="secondary" aria-label="delete image"
onClick={() => handleDeleteImage(image.id, image.userID, image.name)}
component="span" >
<DeleteForever />
</IconButton>
}
</Typography>
</div>
))}
</Grid>
))}
</Grid>
}
export default ImageGrid;

you just need to add curly bracelet and return;
const items = [1,2,3,4,5,6].map(item => {
if(item > 2){
return item;
}
return -1;
});
console.log(items);

You can return null as follows:
const imgArray = ['img1', 'img2', 'img3', 'img4', 'img5'];
imgArray.map((item) => {
if (item === 'img4') {
return null;
}
return null;
});
return imgArray;
ReactJs will not render null.

Related

Problems using a Collapse MaterialUI componente inside an iterableble component

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>
...
)

uneven spacing when using material ui grid

In both the images the spacing on the right is more,I'm not using any custom css to alter the padding or margin
the Mui grid system seems to give me this result
spacing on the right
same on mobile screen
code for grid
const MovieCard = (props) => {
const lengthSettler = (text) => {
if (text.length > 301) {
return `${text.substring(0, 300)}...Read More`;
} else {
return text;
}
};
const print = () => {
console.log('MovieCard', props);
};
print();
return (
<Grid container direction={'row'} justify="center" spacing={6}>
{props.movies.map((movie) => (
<Grid item xs={4}>
<Card sx={{ maxWidth: 345, backgroundColor: '#ece3e3' }}>
<CardActionArea>
{movie.poster_path && (
<CardMedia
component="img"
image={`https://image.tmdb.org/t/p/w500/${movie.poster_path}`}
alt={movie.title}
/>
)}
{!movie.poster_path && (
<CardMedia
component="img"
image="https://upload.wikimedia.org/wikipedia/commons/thumb/6/65/No-Image-Placeholder.svg/1665px-No-Image-Placeholder.svg.png"
alt={movie.title}
/>
)}
<CardContent>
<Typography gutterBottom variant="h5" component="div">
{movie.title}
</Typography>
{movie.overview && (
<Typography variant="body2" color="text.secondary">
{lengthSettler(movie.overview)}
</Typography>
)}
</CardContent>
</CardActionArea>
</Card>
</Grid>
))}
</Grid>
);
};
export default MovieCard;
things I tried
adding justify space-between
removing all the grid items
reinstalling mui

Calling onClick inside map function

I have a custom Input Box and when I type inside a custom input component It'll re-render the typed input inside.
import {
Badge,
Box,
CloseButton,
Grid,
GridItem,
Input,
Text
} from "#chakra-ui/react";
import React, { useEffect, useState } from "react";
function InputTag(props) {
const [tags, setTags] = useState(props.values);
const removeTag = (index) => {
setTags(tags.filter((_, i) => i !== index));
};
const addTag = (event) => {
if (event.target.value !== "") {
setTags([...tags, event.target.value]);
props.setFieldValue("tags", [...tags, event.target.value]);
event.target.value = "";
}
};
useEffect(() => {
props.show === false && setTags([]);
}, [props.show]);
//update values based on click suggestions
useEffect(() => {
setTags([props.values, props.suggTag]);
}, [props.suggTag, props.values]);
return (
<Box
display={"flex"}
border="1px"
borderColor={"gray.200"}
borderRadius={"md"}
padding={2}
>
<Grid templateColumns="repeat(3, 1fr)" gap={2} overflow="visible">
{tags &&
tags.map((tag, index) => (
<GridItem key={index}>
<Badge
variant="solid"
colorScheme={"purple"}
display={"flex"}
borderRadius="full"
justifyContent="space-between"
alignItems="center"
gap={2}
>
<Text>{tag}</Text>
<CloseButton onClick={() => removeTag(index)} />
</Badge>
</GridItem>
))}
</Grid>
<Input
type="text"
name="tags"
id="tags"
variant={"unstyled"}
placeholder="Add Tag"
_placeholder={{ fontsize: "md" }}
onChange={props.handleChange}
onBlur={props.handleBlur}
onError={props.errors}
onKeyUp={(event) =>
event.key === "Enter" ? addTag(event) && event.preventDefault() : null
}
/>
</Box>
);
}
export default InputTag;
Here, when I hit enter It'll render them inside the custom Input Box
I Inserted a custom array of strings as "ex_Tag" inside Previewer.js so that when I click on the word in array, it'll also get rendered inside custom input as well.
function NewUploader({ isOpen, onClose }) {
const cancelRef = useRef();
const ex_tags = ["Design", "Strategy", "Human Centered Design"];
const [show, Setshow] = useState(true);
const [suggTag, setSuggTag] = useState();
const initialValues = {
files: null,
tags: []
};
const validationSchema = yup.object({
files: yup.mixed().required("File is Required"),
tags: yup.mixed().required("tags required")
});
const onSubmit = (values, actions) => {
const formData = new FormData();
formData.append("files", values.files[0]);
formData.append("tags", values.tags);
for (var pair of formData.entries()) {
console.log(pair[0] + ", " + pair[1]);
}
actions.setSubmitting(false);
actions.resetForm();
Setshow(!show);
onClose();
};
const handlethis = (e) => {
e.preventDefault();
};
//insert suggested word to useState so i can pass it to custom input
const handleClick = (tag) => {
setSuggTag(tag);
};
return (
<Modal isOpen={isOpen} onClose={onClose} isCentered>
{/* update code on model here */}
<ModalOverlay />
<ModalContent>
<ModalHeader>
<Text fontWeight={"bold"} color="gray.900">
Upload Buddy
</Text>
</ModalHeader>
<ModalCloseButton />
<ModalBody>
<Flex direction="column" gap={3}>
<Box>
<Text fontWeight={"normal"} color="gray.700">
This learning contentwill not be summarised. to summarize your
content, use{" "}
<Link color={"purple.400"}>Create Knowledge Nugget</Link> option
instead.
</Text>
</Box>
<Box>
<Formik
initialValues={initialValues}
onSubmit={onSubmit}
validationSchema={validationSchema}
>
{(formik) => (
<Form
onSubmit={handlethis}
autoComplete="off"
encType="multipart/form-data"
>
<FormLabel htmlFor="file">
<Text
fontSize="sm"
fontWeight="normal"
color="gray.900"
fontFamily={"body"}
>
Upload files
</Text>
</FormLabel>
{/* drag droop sec */}
{formik.isSubmitting ? (
<>
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{formik.values.files &&
formik.values.files.map((file, index) => (
<GridItem key={index}>
<Badge
variant="solid"
borderRadius="xl"
colorScheme={"gray"}
w={file.name.length * 4}
h="8"
display="flex"
justifyContent="center"
alignItems="center"
my={2}
>
<Text fontFamily={"body"}>{file.name}</Text>
<CloseButton colorScheme={"blackAlpha"} />
</Badge>
</GridItem>
))}
</Grid>
<Progress colorScheme={"yellow"} isIndeterminate />
</>
) : (
<>
<Dragdrop setFieldValue={formik.setFieldValue} />
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{formik.values.files &&
formik.values.files.map((file, index) => (
<GridItem key={index}>
<Badge
variant="solid"
borderRadius="xl"
colorScheme={"gray"}
w={file.name.length * 4}
h="8"
display="flex"
justifyContent="space-between"
alignItems="center"
my={2}
>
<Text fontFamily={"body"}>{file.name}</Text>
<CloseButton colorScheme={"blackAlpha"} />
</Badge>
</GridItem>
))}
</Grid>
{formik.errors.files && formik.touched.files && (
<Text fontFamily={"body"} color="red">
{formik.errors.files}
</Text>
)}
</>
)}
<FormErrorMessage>
<ErrorMessage name="file" />
</FormErrorMessage>
<FormLabel htmlFor="tags">
<Text
fontSize="sm"
fontWeight="normal"
color="gray.900"
fontFamily={"body"}
>
Tags
</Text>
</FormLabel>
<InputTag
setFieldValue={formik.setFieldValue}
handleChange={formik.handleChange}
handleBlur={formik.handleBlur.call}
values={formik.values.tags}
show={show}
suggTag={suggTag}
/>
{formik.errors.tags && formik.touched.tags && (
<Text fontFamily={"body"} color="red">
{formik.errors.tags}
</Text>
)}
<FormErrorMessage>
<ErrorMessage name="tags" />
</FormErrorMessage>
<Box
aria-invalid="true"
display={"flex"}
flexDir="row"
gap={2}
my={2}
>
<Text fontFamily={"body"}>Suggested</Text>
<Grid
templateColumns="repeat(3, 1fr)"
gap={2}
overflow="hidden"
>
{ex_tags.map(
(tag, index) => (
<GridItem key={index}>
//I inserted on click call here
<Box onClick={handleClick(tag)}>
<Badge
variant={"subtle"}
borderRadius="lg"
colorScheme={"gray"}
_hover={{
cursor: "pointer",
bgColor: "gray.200"
}}
>
<Text fontFamily={"body"}>{tag}</Text>
</Badge>
</Box>
</GridItem>
),
this
)}
</Grid>
</Box>
<Box display={"flex"} justifyContent="center" my={3}>
<Button
type="button"
ref={cancelRef}
colorScheme="yellow"
isLoading={formik.isSubmitting}
onClick={formik.handleSubmit}
>
<Text
fontWeight="bold"
fontSize="18px"
color="gray.900"
fontFamily={"body"}
>
Submit
</Text>
</Button>
</Box>
</Form>
)}
</Formik>
</Box>
</Flex>
</ModalBody>
</ModalContent>
</Modal>
);
}
export default NewUploader;
but It seems when I render them to the screen it will come out as I triggered the onClick even though I didn't.
For now I commented out the useEffect func inside input component
I have uploaded it to code sandbox Bellow.
https://codesandbox.io/s/amazing-heyrovsky-9kr0ex?file=/src/Previewer.js

How can I store the information and render this hook in ReactJS?

I use a button from another component to access this Favorites component, but when I press the button, it doesn't load anything and the page goes blank. Now if I press the button twice, it shows me the information.
const Favorites = (props) => {
const [products, setProducts] = useState([]);
useEffect(() =>{
let productsArray = []
firebase.database().ref().child('favorites').orderByKey()
.once('value', snap => {
snap.forEach(child => {
if(child.val().user_id === firebase.auth().currentUser.uid){
firebase.database().ref('products/' + child.val().product_id)
.once('value')
.then(snapshot =>{
const favorite = {
id: snapshot.key,
name: snapshot.val().name,
category: snapshot.val().category,
description: snapshot.val().description,
image: snapshot.val().image,
price: snapshot.val().price,
stock: snapshot.val().stock,
};
productsArray.push(favorite);
});
}
});
setProducts(productsArray);
});
},[]);
return (
<Fragment>
<ul>
{ products && products.map((item, index) => {
return(
<Grid container justify="center" alignItems="center" key={index}>
<div className={classes.root}>
<Paper className={classes.paper}>
<Grid container spacing={2}>
<Grid item>
<ButtonBase className={classes.image}>
<img className={classes.img} alt="complex" src={item.image} />
</ButtonBase>
</Grid>
<Grid item xs={12} sm container>
<Grid item xs container direction="column" spacing={2}>
<Grid item xs>
<Typography gutterBottom variant="subtitle1">
{item.name}
</Typography>
<Typography variant="body2" gutterBottom>
{"Categoria: " + item.category}
</Typography>
<Typography variant="body2" color="textSecondary">
{item.description}
</Typography>
</Grid>
<Grid item>
<Button
onClick={(event) => removeFavorite(event, index)}>
<HighlightOff/> Eliminar de Favoritos
</Button>
</Grid>
</Grid>
<Grid item>
<Typography variant="subtitle1">{"Bs " + item.price + "/ Kg"}</Typography>
</Grid>
</Grid>
</Grid>
</Paper>
</div>
</Grid>
);
})
}
</ul>
</Fragment>
);
}
export default Favorites;
I think I am doing something wrong with the useEffect() when I perform the query and store it with the setProducts() fix. I don't know what's happening, why does it go blank and then rerun the Favorites component if it shows? I appreciate your help.
In the App component I have the Shopping Cart Sharp button, which redirects to "/ shoppingcart", which is the component where I have the problem.
const MyLink = React.forwardRef((props, ref) => <RouterLink innerRef={ref}
{...props} />);
function App() {
const [user, setUser] = useState(null);
const onLogout = () => {
setUser(null);
};
return (
<Router>
<CssBaseLine/>
<Header user={user}>
{user && <Button to="/shoppingcart" component={MyLink}
color="inherit"><ShoppingCartSharp /></Button>}
</Header>
<Routes/>
</Router>
);
}
export default App;

Not able to see the cards

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

Categories