Fetch API with Axios in React - reactjs

const key = '618479acc2ef5ba8018516ac'
function UserPost1 () {
const [post, setPost] = useState(['']);
const handleExpandClick = () => {
setExpanded(!expanded);
};
useEffect(() =>{
axios.get('https://dummyapi.io/data/v1/post' , { headers: { 'app-id': key } })
.then(res => {
setPost(res.data.data)
console.log(res)
})
.catch(err =>{
console.log(err)
})
},[]);
return(
<div className="Post-style">
{post.map(post =>(
<Box sx={{ flexGrow: 1 }}>
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 2, lg: 2 }} >
<Grid
container
direction="row"
justifyContent="center">
<Grid item xs={4} sm={12} md={6} lg={4}>
<div className="card-Style">
<Card sx={{ width: 355}} style={{backgroundColor: "aquamarine"}} >
<CardHeader
avatar={
<Avatar
src={post.owner.picture}
/>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={post.owner.firstName + " " + post.owner.lastName}
subheader={post.publishDate}
/>
<CardMedia
component="img"
height="194"
image={post.image}
alt="Paella dish"
backgroundcolor="blue"
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
{post.text}
<br></br>
{post.likes}
<br></br>
{post.tags}
</Typography>
</CardContent>
</Card>
</div>
</Grid>
</Grid>
</Grid>
</Box>
))}
</div>
)
}
export default UserPost1;
When im run this code i cant get the data from API using Axios, it says error cannot read properties of undefined (reading 'picture'). I tried to catch the error but it does not show in console log. How do i solve this problem.
should i make the axios to wait until it gets the data API or make it to false.
What should i do, its my first time with API.

Somehow, the picture property is missing inside the owner object.
Add optional chaining before picture:
<CardHeader
avatar={
<Avatar
src={post.owner?.picture}
/>
}
...

Render your post component after complete async query and set state value. Use condition rendering and change default state value from [''] to null.
If your component has a specific height and width, then create a placeholder with the same values.
To exclude layout jumps during query execution, show a placeholder. Show the placeholder while the request is running, and after the request is complete and the value is set to the state, show the post component. Good luck!
// change default useState value
const [post, setPost] = useState(null);
// add condition rendering
{post ? post.map(post => (
<Box sx={{ flexGrow: 1 }}>
<Grid container rowSpacing={0} columnSpacing={{ xs: 1, sm: 2, md: 2, lg: 2 }} >
<Grid
container
direction="row"
justifyContent="center">
<Grid item xs={4} sm={12} md={6} lg={4}>
<div className="card-Style">
<Card sx={{ width: 355 }} style={{ backgroundColor: "aquamarine" }} >
<CardHeader
avatar={
<Avatar
src={post.owner.picture}
/>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={post.owner.firstName + " " + post.owner.lastName}
subheader={post.publishDate}
/>
<CardMedia
component="img"
height="194"
image={post.image}
alt="Paella dish"
backgroundcolor="blue"
/>
<CardContent>
<Typography variant="body2" color="text.secondary">
{post.text}
<br></br>
{post.likes}
<br></br>
{post.tags}
</Typography>
</CardContent>
</Card>
</div>
</Grid>
</Grid>
</Grid>
</Box>
)) : <div> PLACEHOLDER </div>}

Related

Having grid items display based on their size MUI Quilted Grid Layout

Image of Issue
Apologies for the zoomed out image, but I am using MUI in React to display a bunch of nested cards dynamically (The data has nested components rendering other components).
Each grid has cards that has grids within them and cards within the grids. I am trying to have the cards fit the page.
So in the image below, I would like the grid component on the second row and second column to come up to where the grid component on the first row second column ends.
I've been searching around trying to find the answer this question but a newbie to frontend so I am not sure how to phrase it. Any advice would be helpful!
Edit:
I am curious about making a quilted grid layout
const useStyles = makeStyles({
gridContainer: {
paddingLeft: "85px",
paddingRight: "85px",
},
root: {
minWidth: 200,
},
bullet: {
display: "inline-block",
margin: "0 2px",
transform: "scale(0.8)",
},
title: {
fontSize: 14,
},
pos: {
marginBottom: 12,
},
parentFlexRight: {
display: "flex",
justifyContent: "flex-end",
},
leftAlignItem: {
marginRight: "auto",
marginTop: "auto",
},
stretch: { height: "100%" },
item: {
display: "flex",
flexDirection: "column",
},
});
var checkIndex = 0;
const renderCourseRequirements = (RequiredCourses) => {
return (
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justifyContent="center"
>
{RequiredCourses.map((course, index) => {
var Index = checkIndex;
checkIndex = checkIndex + 1;
return (
<Grid key={index} item xs={12} sm={6} md={4}>
<Card sx={{ width: 200 }} variant="outlined">
<CardContent>
<Typography color="textSecondary" gutterBottom>
{course}
</Typography>
<Checkbox
id={`custom-checkbox-${Index}`}
name={course}
value={course}
checked={checkedState.includes(course)}
onChange={() => handleOnChange(course)}
/>
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
const renderComponents = (components) => {
return (
<Grid
container
spacing={0}
direction="column"
alignItems="center"
justifyContent="center"
>
{components.map((component, index) => {
return (
<Grid
container
spacing={0}
direction="column"
alignItems="stretch"
justifyContent="center"
key={index}
item
xs={12}
sm={6}
md={4}
>
<Card className={classes.root} variant="outlined">
<CardContent>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
{component.component_name}
</Typography>
<Typography className={classes.pos} color="textSecondary">
Required Number of Courses: {component.required_num_courses}
</Typography>
{renderCourseRequirements(component.course_list)}
</CardContent>
</Card>
</Grid>
);
})}
</Grid>
);
};
const renderComponentFamilies = (componentFamilies) => {
return (
<Grid container spacing={4} className={classes.item}>
{componentFamilies.map((componentFamily, index) => {
if (componentFamily.component_list.length > 1)
return (
<Grid key={index} xs={8} sm={6} md={4}>
<Card className={classes.stretch} variant="outlined">
<CardContent>
<Typography
className={classes.title}
color="textSecondary"
gutterBottom
>
{componentFamily.component_family_name}
</Typography>
<Typography className={classes.pos} color="textSecondary">
Required Number of Components :{" "}
{componentFamily.required_num_components}
</Typography>
{renderComponents(componentFamily.component_list)}
</CardContent>
</Card>
</Grid>
);
return (
<>
<Grid key={index} item xs={12} sm={6} md={4}>
{renderComponents(componentFamily.component_list)}
</Grid>
</>
);
})}
</Grid>
);
};
A couple of resources that may point you in the right direction. Setting some CSS breakpoints may also work with your content.
https://mui.com/system/sizing/
https://mui.com/material-ui/customization/default-theme/?expand-path=$.breakpoints.values
If that's not what you're looking for, I'd recommend wrapping your elements in an element that references a method with a 'resize' event listener that makes your preferred changes after the resize is triggered.

How to target single item in list with onClick when mapping in ReactJS?

My react component returns data from my Firestore DB and maps the data it on Material-UI cards. However, when I press the ExpandMoreIcon, it opens EVERY card. I just want to open each card individually. I know the solution has to do with useState function for expanded & setExpanded.
I've tried to fix this bug but I cant seem to make it work. Any help would be greatly appreciated.
export const NFTprojects = () => {
const [expanded, setExpanded] = useState(false);
const handleExpandClick = (id) => {
setExpanded(!expanded)
};
const [projects, setProjects] = useState([]);
const ref = firebase.firestore().collection("NFTprojects");
function getProjects() {
ref.onSnapshot((querySnapshot) => {
const items = []; //initiliaze empty array
querySnapshot.forEach((doc) => {
items.push(doc.data());
});
setProjects(items);
});
}
useEffect(() => {
getProjects();
}, []);
return (
<div>
<Grid container spacing={4} direction="row" justifyContent="flex-start" alignItems="flex-start">
{projects.map((project) => (
<Grid item xs={4}>
<Card sx={{ maxWidth: 400, borderRadius: 3, mb: 5 }}>
<CardMedia
component="img"
height="140"
image={project.imageUrl}
alt={project.projectName}
/>
<CardContent>
<Typography gutterBottom variant="h5" sx={{ fontWeight: 'bold' }}>
{project.projectName}
</Typography>
<Typography variant="h6" gutterBottom component="div" fontWeight="bold">
{project.jobPosition}
</Typography>
<Typography variant="body2" color="text.secondary" style={{ fontFamily: 'Merriweather' }}>
{project.projectDesc}
</Typography>
</CardContent>
<CardActions disableSpacing>
<Tooltip title="Website">
<IconButton aria-label="secondary marketplace" href={project.websiteLink} target="_blank">
<WebsiteIcon />
</IconButton>
</Tooltip>
<Tooltip title="Twitter">
<IconButton aria-label="twitter" href={project.twitterLink} target="_blank">
<TwitterIcon />
</IconButton>
</Tooltip>
<Tooltip title="Secondary">
<IconButton aria-label="Secondary market link" href={project.secondaryMarket} target="_blank">
<ShoppingCartIcon />
</IconButton>
</Tooltip>
<Tooltip title="Discord">
<IconButton aria-label="discord" href={project.discordLink} target="_blank">
<SvgIcon component={DiscordIcon} viewBox="0 0 600 476.6" />
</IconButton>
</Tooltip>
<Button size="small" variant="contained" sx={{ ml: 15, backgroundColor: 'black' }}>Apply</Button>
<ExpandMore
expand={expanded}
onClick={handleExpandClick}
aria-expanded={expanded}
aria-label="show more"
>
<ExpandMoreIcon />
</ExpandMore>
</CardActions>
<Collapse in={expanded} timeout="auto" unmountOnExit>
<CardContent>
<Typography variant="h6" sx={{ fontWeight: 'bold' }} style={{ fontFamily: 'Merriweather' }}>Job Description:</Typography>
<Typography paragraph>
{project.jobDesc}
</Typography>
<Typography variant="h6" sx={{ fontWeight: 'bold' }}>Prerequisites</Typography>
<Typography paragraph>
{project.jobPrereq}
</Typography>
</CardContent>
</Collapse>
</Card>
</Grid>
))}
</Grid>
</div >
);
}
One approach is to create a separate component for the card. This will enable you to add states to the component and control them. Here is a minimal example demonstrating how you can approach it.
import React, { useState } from "react";
// this is just sample data to work with - equivalent to the data you get from Firebase
const sampleCardsArray = [
{
id: 0,
name: "Card 1",
color: "red",
description: "This is card 1",
},
{
id: 1,
name: "Card 2",
color: "blue",
description: "This is card 2",
},
{
id: 2,
name: "Card 3",
color: "green",
description: "This is card 3",
},
];
// component for all cards
export const AllCards = () => {
// this state is used to store the INDEX of the card that is currently expanded
const [expandedCard, setExpandedCard] = useState(null);
return (
<div>
{sampleCardsArray.map((card, index) => (
<OneCard
card={card}
key={card.id}
// this prop passes the boolean value of whether the card is expanded or not
isExpanded={expandedCard === index}
// this prop receives the index of the card that is expanded and sets the state
expandCard={() => setExpandedCard(index)}
/>
))}
</div>
);
};
// component for one card
// We only show the fields: name and color. We show the description when the card is clicked
export const OneCard = ({ card, isExpanded, expandCard }) => {
return (
<div>
<h1>{card.name}</h1>
<h2>{card.color}</h2>
{
// showing expand button only when card is not expanded
}
{isExpanded ? (
<p>{card.description}</p>
) : (
<button onClick={() => expandCard()}>Expand card</button>
)}
</div>
);
};

How can I automatically import an icon which fetches from the server in NextJS?

I want to dynamically generate a page in the Next-JS app. Inside this page should be imported automatically Icons which fetches from the server Instead of writing it statically:
{
"id": 1,
"title": "Budget",
"value": "$24K",
"persent" :"12%",
"duration" :"Since Last Month",
"icon":"MoneyIcon",
"rise":"false"
},
in this timeMoneyIcon from Material-UI.
In this case, was used map method in order to render fetched data.
How can I put this fetched icon name as a tag same as <MoneyIcon/> in the component?
{posts.map((post) => (
<>
<Grid item lg={3} sm={6} xl={3} xs={12}>
<Card sx={{ height: "100%" }}>
<CardContent>
<Grid container spacing={3} sx={{ justifyContent: "space-between" }}>
<Grid item>
<Typography color="textSecondary" gutterBottom variant="overline">
{post.title}
</Typography>
<Typography color="textPrimary" variant="h4">
{post.value}
</Typography>
</Grid>
<Grid item>
<Avatar
sx={{
backgroundColor: "error.main",
height: 56,
width: 56,
}}
>
**<MoneyIcon />**
</Avatar>
</Grid>
</Grid>
<Box
sx={{
pt: 2,
display: "flex",
alignItems: "center",
}}
>
<ArrowDownwardIcon color="error" />
<Typography
color="error"
sx={{
mr: 1,
}}
variant="body2"
>
{post.persent}
</Typography>
<Typography color="textSecondary" variant="caption">
{post.duration}
</Typography>
</Box>
</CardContent>
</Card>
</Grid>
</>
))}
If I understand you correctly you are trying to pass the MUI Icon to the Component and render the icon. For that you can simply pass the Icon as a value of your object wrapped in a React Fragment.
import AccountBalanceIcon from '#mui/icons-material/AccountBalance';
const myObject = {
id: 1,
value: "24K",
icon: <><AccountBalanceIcon/></>
)};
export default function MyComponent(props) {
return(
<div>
<span>{myObject.value}</span>
{myObject.icon}
<div>
)
}
You can import them dynamically by name for example you can fetch the Icon name from server then in the page do this:
import * as MuiIcons from '#mui/icons-material'
function YourPage({IconName}){
const IconComponent = MuiIcons[IconName]
return <Grid container>
<IconComponent />
</Grid>
}
and if you want for example #mui/icons-material/Memory the Icon name is "Memory".
But I think this approach is not treeshakable you may bring the entire jungle , I am not sure about that though, you should check the bundle after compiling.

react render using map on Grid

I'm trying to build a page that shows the weather in all the locations mentioned in a list.
I want to render a card for each location and sort the cards next to each other so every row will contain 5 location cards [][][][][],
currently, it renders only one under the other in a column:
how can I solve this?
(weather.favoritesWeather is a list that contains all the data which needs).
const FavoriteWeatherCards = weather.favoritesWeather.map(
(favoriteLocation) => (
<div>
<Row className="justify-content-md-center">
<Col md={3} >
<SmallConditionsCard data={favoriteLocation} />
</Col>
</Row>
</div>
)
);
return <div>
{FavoriteWeatherCards}
</div>;
};
code :
const SmallConditionsCard = ({data}) => {
const { location, weather } = data;
let history = useHistory();
const handleClick = () => {
history.push('/');
};
return (
<div>
<Card>
<CardHeader
sx={{ background: 'ghostwhite' }}
title={
<Typography align="center" variant="h5">
{location.name}
</Typography>
}
/>
<CardContent sx={{ textAlign: 'center' }}>
<WeatherIcon
number={weather[0].WeatherIcon}
xs={12}
sx={{ maxHeight: 200, maxWidth: 200 }}
/>
<Typography variant="h6">{weather[0].WeatherText}</Typography>
<Typography variant="p">
{formatTemp(weather[0].Temperature.Metric.Value, celcius)}
</Typography>
</CardContent>
<CardActions>
<Button size="small" onClick={handleClick}>Learn More </Button>
</CardActions>
</Card>
</div>
);
};
this is the result now:
If you are using react-bootstrap you need your container
const FavoriteWeatherCards = weather.favoritesWeather.map(
(favoriteLocation) => (
<Row className="justify-content-md-center">
<Col>
<SmallConditionsCard data={favoriteLocation} />
</Col>
</Row>
)
);
return <Container>
{FavoriteWeatherCards}
</Container>;
};
In case that you want to render something using material UI grid could be something like this
const FavoriteWeatherCards = weather.favoritesWeather.map(
(favoriteLocation) => (
<Grid item xs={3}>
<Item>
<SmallConditionsCard data={favoriteLocation} />
</item>
</Row>
</Grid>
)
);
return <Grid container spacing={2}>
{FavoriteWeatherCards}
</grid>;
};

How to contain images in react material ui CardMedia component

Certain images are much bigger and parts of them are hidden:
The closest I get in getting it right was by playing around with width and height:
const useStyles = makeStyles((theme) => ({
...
media: {
height: 100,
width: 100,
margin: 'auto',
},
...
}));
const Brands = (props) => {
...
return <div style={{ marginTop: props._marginTop }}>
<Grid container justify='center'>
<Grid item xs={10}>
<Grid container spacing={2}>
{brands.map((brand, i)=> {
return <Grid item key={i} lg={3} xs={12}>
<Card>
<CardMedia
className={classes.media}
image={brand.image.length > 0 ? brand.image : knightdemon}
title={brand.name}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{brand.name.toUpperCase()}
</Typography>
<Typography
onClick={()=>setFlip(true)}
className={classes.description}
gutterBottom variant="body2"
component="p"
>
DESCRIPTION
</Typography>
</CardContent>
</Card>
</Grid>
})}
</Grid>
</Grid>
</Grid>
</div>
}
export default Brands;
Height looks good on all of them, the problem is with width and if I increase it it affects height as well.
How do I contain them in the given space so they look something like this:
To fit image in CardMedia, add props component="img" like:
<CardMedia
className={classes.media}
image={brand.image.length > 0 ? brand.image : knightdemon}
title={brand.name}
component="img"
/>
This should solve your problem.

Resources