React Material UI CardMedia issue - reactjs

I used CardMedia to show images, when I go to home page, it doesn't show the images and have some errors. When I click some links and use 'Go Back' button in Browser, images come back.
I use getInitialProps() to get image url in Home component and pass as a property to child (Catalog) component.
I thought Image url would be received in Home component then CardMedia will render the image receiving from Home component as a prop.
Any thoughts?
export default function Home({ categories }) {
return (
<div>
<Header categories={ categories}/>
<Carousel />
<Catalog categories={ categories}/>
<Footer/>
</div>
)
}
Home.getInitialProps = async () => {
const response = await fetch('http://localhost:1337/categories/')
const categories = await response.json();
return {
categories:categories
}
}
export default function Catalog(props) {
const classes = useStyles();
const cards = props.categories
const server = 'http://localhost:1337'
console.log(cards)
return (
<React.Fragment>
<CssBaseline />
<main>
{/* Hero unit */}
<div className={classes.heroContent}>
<Container maxWidth="sm">
<Typography component="h1" variant="h2" align="center" color="textPrimary" gutterBottom>
Album layout
</Typography>
<Typography variant="h5" align="center" color="textSecondary" paragraph>
Something short and leading about the collection below—its contents, the creator, etc.
Make it short and sweet, but not too short so folks don&apos;t simply skip over it
entirely.
</Typography>
</Container>
</div>
<Container className={classes.cardGrid} maxWidth="md">
{/* End hero unit */}
<Grid container spacing={4}>
{cards.map((card) => (
<Link as={`/products/${card.category}`} href='/products/[bathroom]' key={card.id}>
<Grid item key={card.category} xs={12} sm={6} md={4} >
<Card className={classes.card}>
<CardMedia
className={classes.cardMedia}
image={server+ card.img.url}
category={card.category}
/>
<CardContent className={classes.cardContent}>
<Typography gutterBottom variant="h5" component="h2">
{card.category}
</Typography>
<Typography>
Product description.
</Typography>
</CardContent>
</Card>
</Grid>
</Link>
))}
</Grid>
</Container>
</main>
{/* Footer */}
{/* End footer */}
</React.Fragment>
);
}

It turns out that CSS rendering is a bit different in Next.js.
see this doc https://material-ui.com/guides/server-rendering/

Related

React Material UI Cards w/Modal

I'm trying to configure this React JS / Material UI modal dialog so that when the user clicks on the card, it opens a corresponding full-sized image (with title and subtitle). Data for each card is mapped from a JSON file (via AXIOS).
I can get the modal window to open, but it is showing all of the card images in the modal and they are stacked on top of each other. The console.log("SELECTED CAMPAIGN: ", selectedCampaign) code inside the handleOpen() function is one click behind...it actually logs the object that was selected prior to the current click event.
I'm relatively new to functional components and hooks, so I know that I am making simple and fundamental mistakes...please help me figure out the proper way to set this up:
const CampaignItems = ({campaigns, loading}) => {
const classes = useStyles();
const [open, setOpen] = useState(false);
const [selectedCampaign, setSelectedCampaign] = useState();
const handleOpen = (campaign) => {
setSelectedCampaign(campaign);
console.log("SELECTED CAMPAIGN: ", selectedCampaign);
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
..................
<div>
<GridContainer>
{campaigns && search(campaigns).map((campaign) => (
<GridItem key={campaign.id} xs={12} sm={6} md={4}>
<Card className={classes.root}>
<CardActionArea>
<CardMedia
component="img"
alt={campaign.alt}
height="140"
image={campaign.image}
title={campaign.title}
onClick={() => handleOpen(campaign)}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{campaign.title}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{campaign.subtitle}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<IconButton
size="medium"
color="primary"
aria-label="More Information"
onClick={() => handleOpen(campaign)}
>
<InfoIcon />
</IconButton>
<Modal
className={classes.modal}
open={open}
onClose={handleClose}
closeAfterTransition
BackdropComponent={Backdrop}
BackdropProps={{
timeout: 500
}}
>
<Fade
in={open}
>
<div className={classes.paper}>
<h6>{campaign.title}</h6>
<p>{campaign.subtitle}</p>
<img src={campaign.image} />
</div>
</Fade>
</Modal>
</CardActions>
</Card>
</GridItem>
))}
</GridContainer>
</div>
..................
It seems your issue is that you use only a single open state to trigger your modals to open, so they are all triggered open concurrently.
I suggest to instead using the selectedCampaign of the card you're interacting with and use the campaign id that to match which modal to open.
const CampaignItems = ({campaigns, loading}) => {
...
const [selectedCampaign, setSelectedCampaign] = useState(null);
const handleOpen = (campaign) => () => {
setSelectedCampaign(selectedCampaign =>
selectedCampaign.id === campaign.id ? null : campaign
);
};
const handleClose = () => {
setSelectedCampaign(null);
};
...
<div>
<GridContainer>
{campaigns && search(campaigns).map((campaign) => (
<GridItem key={campaign.id} xs={12} sm={6} md={4}>
<Card className={classes.root}>
<CardActionArea>
<CardMedia
...
onClick={handleOpen(campaign)}
/>
...
</CardActionArea>
<CardActions>
<IconButton
...
onClick={handleOpen(campaign)}
>
...
</IconButton>
<Modal
...
open={selectedCampaign.id === campaign.id} // <-- check id match
onClose={handleClose}
...
>
...
</Modal>
</CardActions>
</Card>
</GridItem>
))}
</GridContainer>
</div>
...

Display my cards in 3 columns using reactjs Material ui

I'm trying to retrieve images from an API and display them in a three column grid system. When I retrieved images, they are displayed one beneath each other.
Kindly advised best way to achieve this
import React from "react";
import Display from "./Display";
class App extends React.Component {
constructor() {
super();
this.state = {
loading: true,
image: [],
};
}
async componentDidMount() {
const url = "http://jsonplaceholder.typicode.com/photos?_start=0&_limit=10";
const response = await fetch(url);
const data = await response.json();
this.setState({ image: data, loading: false });
console.log(data);
}
render() {
if (this.state.loading) {
return <div> loading ... </div>;
}
if (!this.state.image.length) {
return <div> didnt get an image</div>;
}
return (
<div>
{this.state.image.map((img) => (
<div>
<div>
{" "}
<Display
showImage={img.url}
showTitle={img.title}
showAlbum={img.albumId}
/>{" "}
</div>
<div key={img.id}>
<ul></ul>
</div>
</div>
))}
</div>
);
}
}
export default App;
function Display(props) {
const classes = useStyles();
return (
<div className={classes.root}>
<Grid container spacing={4}>
<Grid item xs={12} sm={6} md={4}>
<CardActionArea>
<CardMedia
component="img"
alt="Contemplative Reptile"
height="200"
width="200"
img
src={props.showImage}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{props.showTitle}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
Album ID: {props.showAlbum}
</Typography>
</CardContent>
</CardActionArea>
<CardActions>
<Button size="small" color="primary">
Share
</Button>
<Button size="small" color="primary">
Learn More
</Button>
</CardActions>
</Grid>
</Grid>
</div>
);
}
export default Display;
In my App.js I'm able to using the map feature and get all the items from the array, I'm thinking I should modify my App.jS file to print out the results in the three columns but I'm not sure. Can I modify the Display file so that each card goes right beside each other? Do I need to use another array for the cards?
You can add display flex to the parent div in your render method as default flexDirection = 'row'
render() {
if (this.state.loading) {
return <div> loading ... </div>;
}
if (!this.state.image.length) {
return <div> didnt get an image</div>;
}
return (
<div style={{display:"flex"}}> //added display flex
{this.state.image.map((img) => (
<div>
<div>
{" "}
<Display
showImage={img.url}
showTitle={img.title}
showAlbum={img.albumId}
/>{" "}
</div>
<div key={img.id}>
<ul></ul>
</div>
</div>
))}
</div>
);
}

Text will not populate in React Grid

I am trying to create 4 react panels using Grid to showcase my skills for my Portfolio application. My text was working fine but, once I tried to implement a custom Paper Grid from material UI the text is being overridden somehow and I'm not sure why.
Here is my code where I declare the custom Grid:
import React from "react";
import { makeStyles} from "#material-ui/core/styles";
import Grid from "#material-ui/core/Grid";
import Paper from "#material-ui/core/Paper";
const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
color:'green',
},
paper: {
height: 150,
width: 100
},
}));
const SpacingGrid = () => {
const [spacing] = React.useState(2);
const classes = useStyles();
return (
<theme>
<Grid container className={classes.root} spacing={2}>
<Grid item xs={12}>
<Grid container justify="center" spacing={spacing}>
<Paper className={classes.paper} />
</Grid>
</Grid>
</Grid>
<Grid item xs={12}>
<Paper className={classes.control}>
<Grid container>
<Grid item></Grid>
</Grid>
</Paper>
</Grid>
</theme>
);
};
export default SpacingGrid;
Here is my code where I call the Grid and use it to implement my skills:
import React from 'react'
import Panel from '../accessories/Panels'
import SpacingGrid from '../accessories/Grid'
const frontEnd =['Django','Reactjs','Javascript','HTML','CSS']
const backEnd =['Python','Typescript','Nodejs','Express']
const databases =['MongoDB','MySQL']
const tools = ['AWS','AWS-CDK','Docker','AWS-Lambda','Figma','Microservices','Kubernetes']
//I should probably make my own styled components for this but NEED to learn how
const Skills = () =>{
return(
<div>
<h1 color="whitePrimary" align="center">
Skills
</h1>
<SpacingGrid>
<Panel flexWidth="25" padding="1" rounded>
<h1 color="whitePrimary" align="center">
Front-End
</h1>
<p color="whitePrimary" align="center">
</p>
{frontEnd.map((item) =>(
<p color="textPrimary" align="center" key={item}>
{item}
</p>
))}
</Panel>
<Panel flexWidth="31" padding="1" rounded>
<h1 color="whitePrimary" align="center">
Back-End
</h1>
<p color="whitePrimary" align="center">
</p>
{backEnd.map((item) => (
<p color="textPrimary" align="center" key={item}>
{item}
</p>
))}
</Panel>
<Panel flexWidth="31" padding="1" rounded>
<h1 color="whitePrimary" align="center">
Databases:
</h1>
<p color="whitePrimary" align="center">
</p>
{databases.map(((item) =>(
<p color="textPrimary" align="center" key={item}>
{item}
</p>
)))}
</Panel>
<Panel flexWidth="31" padding="1" rounded>
<h1 color="whitePrimary" align="center">
General Tooling:
</h1>
<p color="whitePrimary" align="center">
</p>
{tools.map((item => (
<p color="textPrimary" align="center" key={item}>
{item}
</p>
)))}
</Panel>
</SpacingGrid>
</div>
)
}
export default Skills;
Here is my App.js my file were all the components sit:
import {React, Suspense} from 'react'
import SocialFooter from '../components/SocialMediaFooter'
import Header from '../components/Header'
import Skills from '../components/SkillsPanels'
const Main = () =>{
return(
<div>
<Suspense fallback={<div>Loading...</div>}>
<Header />
</Suspense>
<Suspense fallback={<div>Loading...</div>}>
<Skills />
</Suspense>
<Suspense fallback={<div>Loading... </div>}>
<SocialFooter />
</Suspense>
</div>
)}
//add all react components here and export to app.js
export default Main;
My issue is that I believe the error to be in the Grid.js file. Now the code works but, it does not add the text to the app as I have it in SkillsPanels.js it just gives me blank Grids which leaves me stumped. Thank you all!
I passed props in the SpacingGrid function and then called {props.children} next to

React - Displaying Data from API

I am trying to consume an API and display it on my app. It is currently pulling in the data in the console log but I can't seem to access it to display it on my app as it is just stuck on loading... I want to show the data with the number of confirmed cases on the h5 variant of the first card {confirmed.value} But not to sure why it is not showing.
Cards
import React from 'react'
import {Card, CardContent, Typography, Grid} from '#material-ui/core'
import styles from './Cards.module.css'
export default function Cards({data: {confirmed, recovered, deaths}}){
if(!confirmed){
return`Loading...`
}
return (
<div className={styles.container}>
<Grid container spacing={3} justify="center">
<Grid item component={Card}>
<CardContent>
<Typography color="textSecondary" gutterBottom>Infected</Typography>
<Typography variant="h5">{confirmed.value}</Typography>
<Typography color="textSecondary">REAL DATE</Typography>
<Typography variant="body2">Number of active cases of COVID-19</Typography>
</CardContent>
</Grid>
</Grid>
<Grid container spacing={3} justify="center">
<Grid item component={Card}>
<CardContent>
<Typography color="textSecondary" gutterBottom>Recovered</Typography>
<Typography variant="h5">REAL DATA</Typography>
<Typography color="textSecondary">REAL DATE</Typography>
<Typography variant="body2">Number of Recovered cases of COVID-19</Typography>
</CardContent>
</Grid>
</Grid>
<Grid container spacing={3} justify="center">
<Grid item component={Card}>
<CardContent>
<Typography color="textSecondary" gutterBottom>Deaths</Typography>
<Typography variant="h5">REAL DATA</Typography>
<Typography color="textSecondary">REAL DATE</Typography>
<Typography variant="body2">Number of Death cases of COVID-19</Typography>
</CardContent>
</Grid>
</Grid>
</div>
)
}
App JS
import React, { Component } from 'react'
import {Cards, Chart, CountryPicker} from './components'
import styles from './App.module.css'
import {fetchData} from './api'
export default class App extends Component {
state = {
data: {},
}
async componentDidMount(){
const fetchedData = await fetchData()
console.log(fetchedData);
this.setState({data: fetchedData})
}
render() {
const {data} = this.state;
return (
<div className={styles.container}>
<Cards data={data} />
<CountryPicker/>
<Chart/>
</div>
)
}
}
Try to change the component header to:
export default function Cards({confirmed, recovered, deaths})

Change state of buttons

Conditional rendering buttons doesn't work
I tried to change the state of the buttons and add instead another component but none got worked
/// This is the button component
const AddToList: React.FC<IAddToListProps> = (props) => {
let [showBtn, setShowBtn] = useState(true);
const classes = useStyles(props);
let addToList = () => {
fetch(`http://127.0.0.1:3000/${props.action}/${props.id}`, {method:
'post'})
.then((response) => {
console.log(response);
});
}
return (
<div>
{
showBtn ?
<Button
onClick={addToList}
variant="contained"
color="primary"
className={classes.button}>
{props.label}
</Button>
: null
}
</div>
);
//This is the movieCard component
export default function MovieCard() {
const [movieTitle, setMovieTitle] = useState('lorem ipsum');
const [year, setYear] = useState('1999');
const classes = useStyles();
return (
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.media}
image='#'
title="anotherTitle"
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{movieTitle}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{year}
</Typography>
<AddToList
id={10}
label={'Add To Watch'}
action={'towatch'}
/>
<AddToList
id={10}
label={'Add To Seen'}
action={'watched'} />
</CardContent>
</CardActionArea>
<CardActions>
</CardActions>
</Card>
);
Expected results:
When I click "Add to Watch" button, "Add to Seen" must be removed and "Add to Watch" must be transformed in "Remove from watch list"
I would split up the code between logic and presentation. As you already reused AddToList it's just a visual component and should not contain any logic.
So I would move all logic to one component and then use the state to render the correct presentation:
const AddToList: React.FC<IAddToListProps> = props => {
const classes = useStyles(props);
return (
<div>
<Button
onClick={props.onClick}
variant="contained"
color="primary"
className={classes.button}
>
{props.label}
</Button>
</div>
);
};
With this you can provide any function which is called when clicking the button and you have a multipurpose component for resuse anywhere else. So a more generic name may be useful.
export default function MovieCard() {
const [movieTitle, setMovieTitle] = useState("lorem ipsum");
const [year, setYear] = useState("1999");
const [watched, setWatched] = useState(false);
const classes = useStyles();
const addToList = (action, id) => {
fetch(`http://127.0.0.1:3000/${action}/${id}`, {
method: "post"
}).then(response => {
console.log(response);
});
};
return (
<Card className={classes.card}>
<CardActionArea>
<CardMedia className={classes.media} image="#" title="anotherTitle" />
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{movieTitle}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{year}
</Typography>
{!watched && (
<AddToList
label={"Add To Watch"}
onClick={() => { addToList("towatch", 10); setWatched(true)} }
/>
)}
{watched && (
<AddToList
label={"Add To Seen"}
onClick={() => addToList("watched", 10)}
/>
)}
</CardContent>
</CardActionArea>
<CardActions />
</Card>
);
}
As you can see the MovieCard is now handling the whole logic of calling backend functions and also cares about showing the correct button. With this basic idea you can advance even more with loading the correct watched state and not starting with false or any other thing.

Resources