Display my cards in 3 columns using reactjs Material ui - reactjs

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

Related

Retrieve image url from database and display in React using Material-UI

I have a list of movies and I want to display the name of the movie and the image attached to that movie. I got image URLs from the web and put them inside an MySQL database(varchar300).
const MovieView = (props: { movie: Movie; }) => {
const {movie} = props;
return (
<Grid item xs={12} sm={6} md={3}
className="movie">
<Link to={`movies/${movie.id}`}>
<Paper elevation={3} className="movie-paper">
<div>
<h2>{movie.title}</h2>
<img src="movie.image" alt=""/>
</div>
</Paper>
</Link>
</Grid>
);
In order to do this, you need the src to be in template form (using squiggly brackets {}) instead of quotes. Try this instead
const MovieView = (props: { movie: Movie; }) => {
const {movie} = props;
return (
<Grid item xs={12} sm={6} md={3}
className="movie">
<Link to={`movies/${movie.id}`}>
<Paper elevation={3} className="movie-paper">
<div>
<h2>{movie.title}</h2>
<img src={movie.image} alt=""/>
</div>
</Paper>
</Link>
</Grid>
);
}

React Material UI CardMedia issue

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/

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.

Reusing multiple instances of react component with different props

So I have a child component that I want to render multiple instances of in a parent container component. Passing in different props to each so they display differently.
What is happening is that they are both being rendered with the last instance of the props in the script being read into both instances. Thus the both components below end up with placeHolder==='Describe yourself'
Is there a work around for this so that they will each be injected with their props in turn exclusively?
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
buttonName='Update'
modalOpen={this.modalOpen}
placeHolder='Describe yourself'
label='Bio'
change={this.handleBio}
/>
ButtonMode
class ButtonMode extends Component {
constructor(props){
super(props)
this.state = {
input:''
}
this.handleInput = this.handleInput.bind(this);
this.handle = this.handle.bind(this);
}
handleInput(val){
this.setState({input:val})
};
handle() {
this.props.change(this.state.input);
};
render(){
const { classes } = this.props;
return (
<div>
<Button
className={classes.button}
onClick={this.props.modalOpen}
>Update
</Button>
<Modal
aria-labelledby="simple-modal-title"
aria-describedby="simple-modal-description"
open={this.props.open}
onClose={this.props.handleClose}
>
<div className={classes.paper}>
<TextField
id="filled-textarea"
label={this.props.label}
placeholder={this.props.placeHolder}
multiline
className={classes.textField}
onChange={(e)=>{this.handleInput(e.target.value)}}
rows= '4'
/>
<Button
onClick={this.handle}
className={classes.button}
color="secondary">Submit</Button>
</div>
</Modal>
</div>
)
}
}
Then I used it like that
class UserCard extends Component {
constructor(props){
super(props);
this.state = {
tempPro:'',
open: false,
profilePicture:''
}
this.modalOpen = this.modalOpen.bind(this);
this.handleClose = this.handleClose.bind(this);
this.handlePicture = this.handlePicture.bind(this);
}
// componentDidMount(){
// const {userId, profilePic} = this.props;
// this.setState({profilePicture:profilePic});
// // axios.get(`/api/profile/${userId}`).then(res=>{
// // let {profilePic} = res.data[0];
// // this.setState({profilePic})
// // })
// }
handlePicture(val){
this.props.changePic(val);
this.setState({open:false});
};
handleBio(val){
this.setState({open:false});
};
handleClose(){
this.setState({open: false});
};
modalOpen(){
this.setState({open:true});
};
render() {
const { classes } = this.props;
const {stories} = this.props;
let storyShow = stories.map((story,id) => {
return(
<div value={story.story_id}>
<h3>{story.title}</h3>
<ul className={classes.background}>
<li>{story.description}</li>
<li>{story.is_complete}</li>
</ul>
</div>
)
});
return (
<div className={classes.rootD}>
<Grid container>
<Grid className={classes.itemFix} >
<Card className={classes.card}>
<CardMedia
className={classes.media}
image={this.props.proPic}
title={this.props.userName}
/>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder="New picture url"
change={this.handlePicture}
label='URL'
/>
</div>
<CardHeader
className={classes.titles}
title={this.props.userName}
subheader="Somewhere"
/>
<CardHeader className={classes.titles} title='Bio' />
<CardContent className={classes.background}>
<Typography className={classes.bio} paragraph>
{this.props.bio}
</Typography>
</CardContent>
<div>
<ButtonMode
open={this.state.open}
handleClose={this.handleClose}
modalOpen={this.modalOpen}
placeHolder='Describe you how you want'
label='Bio'
change={this.handleBio}
/>
</div>
</Card>
</Grid>
<Grid className={classes.itemFixT}>
<Card className={classes.card}>
<CardContent>
<CardHeader
className={classes.titles}
title='Works'/>
<Typography paragraph>
<ul>
{storyShow}
</ul>
</Typography>
</CardContent>
</Card>
</Grid>
</Grid>
</div>
);
}
}
UserCard.propTypes = {
classes: PropTypes.object.isRequired,
};
function mapStateToProps(state){
const {userId, profilePic} = state;
return {
userId,
profilePic
}
}
export default connect(mapStateToProps,{})(withStyles(styles)(UserCard));
I had a similar issue where I was trying to pass different functions to the children components. I had a UploadFile component that contained an <input/> and a <Button/> from material-ui, and I wanted to reuse this component multiple times throughout a page, as the user has multiple files to upload, and in order to save the files, I needed callback functions in the main page.
What I had to do, was give each child component <UploadFile/> in my case, and <ButtonMode/> in your case, a unique id passed in as a prop, since otherwise, the top level page cannot tell each reference to the child component apart from any others.
The code of the child component:
function UploadFile({ value, handleFile }) {
const classes = useStyles();
return (
<>
<input
accept=".tsv,.fa,.fasta"
className={classes.input}
id={value}
type="file"
style={{ display: 'none' }}
onChange={e => handleFile(e.target.files[0])}
/>
<label htmlFor={value}>
<Button
variant="contained"
color='default'
component="span"
startIcon={<CloudUploadIcon />}
className={classes.button}>
Upload
</Button>
</label>
</>
);
}
The usage of this component in the parent (handleFile is the function I am passing in and is defined above in the parent component):
<UploadFile value='prosite' handleFile={handlePrositeFile} />
<UploadFile value='pfam' handleFile={handlePfamFile} />
I spent an embarrassingly long time on a similar issue. I tried all sorts of JS debugging and even re-read the entire concept of closure :)
This is was my culprit: <TextField id="filled-textarea" ... />
i.e. the id is static. If we have multiple instances of the same id on one page, we have a problem.
Make id dynamic, e.g. <TextField id={this.props.label} ... />
I was using the same state for both modals and in each instance of handleOpen() it was only ever opening the last instance of modal in the script.

ReactJS - Rendering a specific component based on scenario

I'm trying to render a specific component based on stage scenario of the page. I'm using a varriable "transitComponent" to render one of three components - a circular progress (wait) or one of two buttons once the response is received.
Any suggestions?
render() {
const { classes } = this.props;
if (this.state.stage==1){ transitComponent = CircularProgress};
if (this.state.stage==2){ transitComponent = CancelButton };
if (this.state.stage==3){ transitComponent = OKButton };
return (
<div align="center">
<br />
<Button align="center" variant="contained" color="primary" onClick= {this.handleOpen}>Create Profile</Button>
<Modal aria-labelledby="simple-modal-title" aria-describedby="simple- modal-description" open={this.state.open} onClose={this.handleClose}>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="title" id="modal-title" align="center">
{this.state.message}
</Typography>
{transitComponent}
</div>
</Modal>
</div>
);
}
You are assigning CircularProgress, CancelButton or OKButton to a temporary variable transitComponent, depending on the current state.stage. That's OK, the only part you got wrong is how you render that component.
Since transitComponent is a component like any other, you don't render it with curly braces, but as a component, so <transitComponent /> would be the proper way.
One more thing: Since React naming convention requires you to name components capitalized, you should name it TransitComponent and render it as <TransitComponent />.
And don't forget to declare TransitComponent with a let statement!
Updated code example:
render() {
const { classes } = this.props;
let TransitComponent;
if (this.state.stage==1){ TransitComponent = CircularProgress};
if (this.state.stage==2){ TransitComponent = CancelButton };
if (this.state.stage==3){ TransitComponent = OKButton };
return (
<div align="center">
<br />
<Button align="center" variant="contained" color="primary" onClick={this.handleOpen}>Create Profile</Button>
<Modal aria-labelledby="simple-modal-title" aria-describedby="simple-modal-description" open={this.state.open} onClose={this.handleClose}>
<div style={getModalStyle()} className={classes.paper}>
<Typography variant="title" id="modal-title" align="center">
{this.state.message}
</Typography>
<TransitComponent />
</div>
</Modal>
</div>
);
}

Resources