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

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

Related

Material UI: best way to set elements side by side

I'm currently using Material UI to style an ecommerce project. I'm trying to figure out how to make all of the elements (Typography, Buttons) display side by side in a row instead of vertically.
Is there a way to do this simply with material UI? Should I add CSS? I previously tried adding each element to a Grid item within a Container, it kind of worked (couldn't center), but doesn't seem like the right approach for this scenario.
If you see my screenshot below, I'm trying to set "T-Shirt Twilight", the image, "$49.99", the quantity input buttons and "Remove" side by side horizontally.
Elements I'm trying to set side by side:
<div className="App">
{products.map((item, index) => (
<Grid container item xs={12}>
<Grid item xs={1} />
<Grid item xs={10}>
<Grid item xs={12} container key={item.id}>
<Grid item xs={1} />
<Grid item xs={10}>
<Typography>{item.title}</Typography>
<img src={require(`../images/${item.image}`)} className={classes.productImage}></img>
<Typography>${(item.quantity * item.price).toFixed(2)}</Typography>
<ButtonGroup size="small">
<Button type="button" onClick={item.quantity > 1 ? () => decreaseQuantity(index) : null}>-</Button>
<Button>{item.quantity}</Button>
<Button type="button" onClick={() => increaseQuantity(index)}>+</Button>
</ButtonGroup>
<Button
onClick={() => removeItem(index)}>
Remove
</Button>
</Grid>
<Grid item xs={1} />
</Grid>
</Grid>
<Grid item xs={1} />
</Grid>
))}
</div>
Screenshot for reference:
Full code:
import React, { useState, useEffect } from 'react';
import './../App.css';
import * as ReactBootStrap from 'react-bootstrap';
import {Link} from 'react-router-dom';
import { getQuantity, getTotal } from '../helpers/helperTools';
import {Grid, Typography,useMediaQuery, useTheme, Container, Button, ButtonGroup} from '#material-ui/core';
import {makeStyles} from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
productImage: {
maxWidth: '20%'
}
}))
function Cart({ setQty: setParentQty }) {
const classes = useStyles();
const [products, setProducts] = useState([]);
function updateQty(products){
/* var holder = 0;
products.forEach((a, b) => {
holder = holder + a.quantity
})*/
// setQty({quantity: holder})
// localStorage.setItem('quantity', JSON.stringify({ quantity: newQty }))
setParentQty({ quantity: getQuantity(products) });
}
useEffect(function() {
const storageItems = JSON.parse(localStorage.getItem('product'));
const products = storageItems || [];
setProducts(products);
updateQty(products);
}, []);
function decreaseQuantity(index) {
if (products[index]){
const newProducts = products.map((a, b) => {
if (b === index) return {...a, quantity: a.quantity - 1}
else return a
});
setProducts(newProducts);
localStorage.setItem('product', JSON.stringify(newProducts))
updateQty(newProducts)
}
}
function increaseQuantity(index) {
if (!products[index]) return;
const newProducts = products.map((a, b) => {
if (b === index) return {...a, quantity: a.quantity + 1}
else return a
})
setProducts(newProducts)
localStorage.setItem('product', JSON.stringify(newProducts))
updateQty(newProducts);
}
function removeItem(index){
const product = products[index];
if (!product) return;
const newProducts = products.filter((v, z) => z !== index);
setProducts(newProducts);
localStorage.setItem('product', JSON.stringify(newProducts));
updateQty(newProducts);
}
if (products.length === 0) {
return (
<div className="App">
<p>
Cart Empty
</p>
<Link to={`/`}>
<p>Continue shopping</p>
</Link>
</div>)
}
return (
<div className="App">
{products.map((item, index) => (
<Grid container item xs={12}>
<Grid item xs={1} />
<Grid item xs={10}>
<Grid item xs={12} container key={item.id}>
<Grid item xs={1} />
<Grid item xs={10}>
<Typography>{item.title}</Typography>
<img src={require(`../images/${item.image}`)} className={classes.productImage}></img>
<Typography>${(item.quantity * item.price).toFixed(2)}</Typography>
<ButtonGroup size="small">
<Button type="button" onClick={item.quantity > 1 ? () => decreaseQuantity(index) : null}>-</Button>
<Button>{item.quantity}</Button>
<Button type="button" onClick={() => increaseQuantity(index)}>+</Button>
</ButtonGroup>
<Button
onClick={() => removeItem(index)}>
Remove
</Button>
</Grid>
<Grid item xs={1} />
</Grid>
</Grid>
<Grid item xs={1} />
</Grid>
))}
</div>
);
}
export default Cart;
Just use inline style for it. I also removed some unnecessary grid items. May be you want to grid more about breakpoints in Grid. Working CodeSandBox
<Grid container>
<Grid item xs={1} />
<Grid item xs={10} style={{ display: "flex", gap: "1rem" }}>
<Typography>{item.title}</Typography>
<img src={item.image} className={classes.productImage}></img>
<Typography>${(item.quantity * item.price).toFixed(2)}</Typography>
<ButtonGroup size="small">
<Button
type="button"
onClick={
item.quantity > 1 ? () => decreaseQuantity(index) : null
}
>
-
</Button>
<Button>{item.quantity}</Button>
<Button type="button" onClick={() => increaseQuantity(index)}>
+
</Button>
</ButtonGroup>
<Button onClick={() => removeItem(index)}>Remove</Button>
</Grid>
<Grid item xs={1} />
</Grid>

React : Unable to set the values in the form

I want to set the values of the form if the edit is true. I am passing the editable state to true and passing the first element of the array but when the component is mounted it didn't show the 0th element values that were sent from the parent component to the child component.
Here's the code :
Milestone.js
import React, { Component } from 'react';
import "./milestone.css";
import MileStoneForm from './mileStoneForm'
import {LEFT_ARROW, ROUTES_PATH } from "../../constants";
export default class MilestoneComp extends Component {
constructor(props) {
super(props);
this.state = {
arr: [],
edit:true
}
}
backPage = () => {
this.props.history.push(ROUTES_PATH.CREATE_PROJECT)
}
handleData=(data)=>{
const {arr}=this.state
arr.push(data)
//localStorage.setItem('mileStoneData',JSON.stringify(arr))
console.log(arr)
}
render() {
return (
<div style={{ background: "#F3F3F3", minHeight: "100vh" }}>
<div className="header-div">
<img
src={LEFT_ARROW}
alt="back"
style={{ cursor: "pointer" }}
className="left-arrow-size left-back-btn-bt-mar"
onClick={this.backPage}
/>
</div>
<MileStoneForm arr={this.state.arr[0]} handleData={this.handleData} edit={this.state.edit}/>
</div>
)
}
}
MileStoneForm.js
import { InputBase,Grid,TextareaAutosize} from '#material-ui/core'
import React, { Component } from 'react'
import { DROP_D } from "../../constants";
import { PRIVATE_SwITCH,PUBLIC_SwITCH } from "../../constants";
import NormalButton from '../../common/component/normalButton';
import DatePicker from 'react-datepicker'
import 'react-datepicker/dist/react-datepicker.css';
class MileStoneForm extends Component {
constructor(props){
super(props)
this.state={
deliverable_name:"",
due_date:"",
deliverable_notes:"",
milestone_based_payment:false,
deliverable_name_error:"",
due_date_error:"",
deliverable_notes_error:"",
percent_rate:0,
percent_rate_error:""
}
}
componentDidMount(){
console.log('component did mount')
if(this.props.edit===true){
console.log('editable')
if(this.props.arr){
console.log('array')
this.setState({
deliverable_name:this.props.arr.milestoneName,
deliverable_notes:this.props.arr.description,
due_date:this.props.arr.dueDate,
milestone_based_payment:this.props.arr.isMilestoneBasedPayment,
percent_rate:this.props.arr.percentageRate
},()=>{
console.log('edit')
})
}
}
}
render() {
const {deliverable_name,deliverable_name_error,deliverable_notes,deliverable_notes_error,
due_date,due_date_error,milestone_based_payment,}=this.state
return (
<>
<div className="milestone">
<div className="milestone-header">ADD MILESTONE</div>
<Grid container className="milestone-deliverable-name-date">
<Grid item md={6} lg={6} xs={12}>
<div className="milestone-deliverable-name">DELIVERABLE NAME</div>
<InputBase
className={`milestone-input-deliverable-name`}
autoComplete={"off"}
placeholder={"MileStone Name"}
onChange={e=>this.handleChange(e,'deliverable_name')}
value={deliverable_name}
maxLength="100"
autoFocus={true}/>
{deliverable_name_error && (
<div className="input-error-style">{deliverable_name_error}</div>
)}
</Grid>
<Grid item md={6} lg={6} xs={12}>
<div className="milestone-due-date">
DUE DATE
</div>
<label>
<DatePicker
dateFormat="MM/dd/yyyy"
margin="normal"
selected={due_date}
placeholderText="Due Date"
onChange={date=>this.handleChangeDate(date,'due_date')}
maxDate={new Date()}
className={`milestone-input-due-date`}
/>
<img src={DROP_D} alt="drop down" style={{cursor:'pointer'}} className='dropdown-milestone'/>
</label>
{due_date_error && (
<div className="input-error-style">{due_date_error}</div>
)}
</Grid>
<Grid item md={12} lg={12} xs={12}>
<div className="milestone-notes-description">
<div className="milestone-deliverable-notes">DELIVERABLE NOTES</div>
<div className="milestone-description-notes">Add description below</div>
<TextareaAutosize className={`milestone-textarea-description`}
onChange={(e)=>this.handleChange(e,'deliverable_notes')}
value={deliverable_notes}/>
{deliverable_notes_error && (
<div className="input-error-style">{deliverable_notes_error}</div>
)}
</div>
</Grid>
<Grid item md={12} lg={12} xs={12}>
<div className="milestone-payment">MILESTONE BASED PAYMENT?</div>
{this.togglePayment()}
</Grid>
<Grid item md={12} lg={12} xs={12}>
{milestone_based_payment ?<>
<div className="percent-rate">PERCENT RATE</div>
<InputBase
className={`milestone-percent-rate`}
autoComplete={"off"}
placeholder={"20%"}
maxLength="100"
value={this.state.percent_rate}
onChange={(e)=>{this.handleChange(e, "percent_rate")}}
/>
{
this.state.percent_rate_error && (
<div className="input-error-style">{this.state.percent_rate_error}</div>
)
}
</> :''}
</Grid>
<Grid item xs={12} sm={12} md={4} lg={4}></Grid>
<Grid item xs={12} sm={12} md={4} lg={4}></Grid>
<Grid item xs={12} sm={12} md={4} lg={4}>
<div className={milestone_based_payment?"milestone-button":"milestone-button-margin-btm"}>
<NormalButton
buttonValue="ADD"
className="btn-create-project flex-justify"
icon_color="black"
handleButtonAction={()=>this.handleSubmit()}
/>
</div>
</Grid>
</Grid>
</div>
</>
)
}
}
export default MileStoneForm
You are mutating your state with push, never do that. You have to create a new state object.
handleData=(data)=>{
const { arr }= this.state
const nextArr= {...arr, data}
//localStorage.setItem('mileStoneData',JSON.stringify(arr))
this.setState({arr: nextArr});
console.log(arr)
}
The console.log will show the new data anyway, since you are changing that object, but since the object reference will be the same (same object as before), it will not rerender.

React : How can I set the error messages?

I am trying to set the error message but as in the state I've passed initial Values object, I'm unable to set the state. How can I achieve this in react? How can I set the error messages in the set State function? Can someone help me?. After console logging it is showing the error in the console. But how can I show this on screen? Thanks in advance.
Here's the code :
const initialValues={
deliverable_name:"",
due_date:new Date(),
deliverable_notes:"",
milestone_based_payment:false,
deliverable_name_error:"",
due_date_error:"",
deliverable_notes_error:""
}
class MileStoneForm extends Component {
constructor(props){
super(props)
this.state={
initialValues
}
}
handleChange=(e,type)=>{
let error=true
if(type==='deliverable_notes'){
if(e.target.value.length>300){
error=false
this.setState({deliverable_notes_error:'Description must contain less than 300 characters'})
}
}
if(error){
this.setState({
[type]:e.target.value,
[type+"_error"]: ""
})
}
}
validateMileStoneName=()=>{
if(!this.state.initialValues.deliverable_name || this.state.initialValues.deliverable_name.length< 4 ){
console.log("enter deliverable name")
this.setState({deliverable_name_error:'Please enter deliverable name'})
}
}
validateMileStoneDueDate=()=>{
if(!this.state.initialValues.due_date){
this.setState({due_date_error:'Please enter due date'})
}
}
validateMileStoneDescription=()=>{
if(!this.state.initialValues.deliverable_notes){
this.setState({deliverable_notes_error:'Please enter description'})
}
}
handleSubmit=(data)=>{
this.validateMileStoneName()
this.validateMileStoneDescription()
console.log(data)
this.props.handleData(data)
}
render() {
const {initialValues}=this.state
return (
<>
<div className="milestone">
<div className="milestone-header">ADD MILESTONE</div>
<Grid container className="milestone-deliverable-name-date">
<Grid item md={6} lg={6} xs={12}>
<div className="milestone-deliverable-name">DELIVERABLE NAME</div>
<div>
<InputBase
className={`milestone-input-deliverable-name`}
autoComplete={"off"}
placeholder={"MileStone Name"}
onChange={e=>this.handleChange(e,'initialValues.deliverable_name')}
value={initialValues.deliverable_name}
maxLength="100"
autoFocus={true}/>
{initialValues.deliverable_name_error && (
<div className="input-error-style">{initialValues.deliverable_name_error}</div>
)}
</div>
</Grid>
<Grid item md={6} lg={6} xs={12}>
<div>
<div className="milestone-due-date">
DUE DATE
</div>
<InputBase
className={`create-project-due-date`}
autoComplete={"off"}
type="date"
placeholder={"DUE DATE"}
onChange={e=>this.handleChange(e,'due_date')}
value={initialValues.due_date}/>
{initialValues.due_date_error && (
<div className="input-error-style">{initialValues.due_date_error}</div>
)}
</div>
</Grid>
</Grid>
<Grid container className="milestone-deliverable-notes">
<Grid item md={12} lg={12} xs={12}>
<div className="milestone-notes-description">
<div className="milestone-deliverable-notes">DELIVERABLE NOTES</div>
<div className="milestone-description-notes">Add description below</div>
<TextareaAutosize className={`milestone-textarea-description`}
onChange={(e)=>this.handleChange(e,'deliverable_notes')}
value={initialValues.deliverable_notes}/>
{initialValues.deliverable_notes_error && (
<div className="input-error-style">{initialValues.deliverable_notes_error}</div>
)}
</div>
</Grid>
</Grid>
<div className={initialValues.milestone_based_payment?"milestone-button":"milestone-button-margin-high"}>
<Grid container spacing={10}>
<Grid item xs={12} sm={12} md={4} lg={4}></Grid>
<Grid item xs={12} sm={12} md={4} lg={4}></Grid>
<Grid item xs={12} sm={12} md={4} lg={4}>
<div className="milestone-button-margin-btm">
<NormalButton
buttonValue="ADD"
className="btn-create-project flex-justify"
icon_color="black"
handleButtonAction={()=>this.handleSubmit(initialValues)}
/>
</div>
</Grid>
</Grid>
</div>
</div>
</>
)
}
}
export default MileStoneForm
You're mixing initialValue in the state which is an object, with the properties you're setting. So after an error, your state is actually looking like this:
this.state = {
initialValue: [object] // this could potentially mutate (a bug).
deliverable_notes_error: "Please set ....",
}
What you need to do is use the spread syntax to set your initial state:
constructor(props) {
this.state = {
...initialValues // your state is now a soft clone of the object
};
}
render(props) {
const { deliverable_notes_error } = this.state;
return (
{ deliverable_notes_error && <div>{ deliverable_notes_error }</div> }
);
}
3 things
I would move the error messages inside State.
deliverable_name_error:"",
due_date_error:"",
deliverable_notes_error:""
Update this.setState - already done
Display error messages - also doing.
Basically, if you move everything from initialValues into state, your code should work.

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

Resources