I have a few pictures in my react project which I get from my JSON file,I need it to open in full screen when I click on the picture, but the pictures open all at once and not in full screen, perhaps my choice of solution is not the right one.Im new at react so any help or suggestions would help me. have this code:
import React from "react";
import axios from "axios";
import Card from "#material-ui/core/Card";
import CardActionArea from "#material-ui/core/CardActionArea";
import CardContent from "#material-ui/core/CardContent";
import CardMedia from "#material-ui/core/CardMedia";
import Typography from "#material-ui/core/Typography";
import "../App.scss";
export default class Portfolio extends React.Component {
state = {
persons: [],
};
handleShowDialog = () => {
this.setState({ isOpen: !this.state.isOpen });
console.log("cliked");
};
// onClick={() => imageClick()}
componentDidMount() {
axios.get("http://localhost:3000/persons.json").then((res) => {
const persons = res.data;
this.setState({ persons });
});
}
render() {
// const imageClick = () => {
// }
return (
<div className="container">
<div className="row justify-content-center">
{this.state.persons.map((person) => (
<Card key={person.id} className="col-3 axios-items">
<CardActionArea>
<CardMedia
component="img"
alt={person.alt}
height="140"
image={person.src}
title={person.title}
onClick={this.handleShowDialog}
/>
{this.state.isOpen && (
<dialog
className="dialog"
style={{ position: "absolute" }}
open
onClick={this.handleShowDialog}
>
<img
className="image"
src={person.src}
onClick={this.handleShowDialog}
alt="no image"
/>
</dialog>
)}
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{person.title}
</Typography>
<Typography
variant="body2"
color="textSecondary"
component="p"
>
{person.desc}
</Typography>
</CardContent>
</CardActionArea>
</Card>
))}
</div>
</div>
);
}
}
I think you can use another state too..
import React from "react";
import axios from "axios";
import Card from "#material-ui/core/Card";
import CardActionArea from "#material-ui/core/CardActionArea";
import CardContent from "#material-ui/core/CardContent";
import CardMedia from "#material-ui/core/CardMedia";
import Typography from "#material-ui/core/Typography";
import "../App.scss";
export default class Portfolio extends React.Component {
state = {
persons: [],
};
handleShowDialog = (id) => {
this.setState({ ...this.state, selected: id, isOpen: true });
console.log("cliked");
};
handleHideDialog = () => {
this.setState({ ...this.state, isOpen: false });
console.log("closed");
};
// onClick={() => imageClick()}
componentDidMount() {
axios.get("http://localhost:3000/persons.json").then((res) => {
const persons = res.data;
this.setState({ persons });
});
}
render() {
// const imageClick = () => {
// }
return (
<div className="container">
<div className="row justify-content-center">
{this.state.persons.map((person) => (
<Card key={person.id} className="col-3 axios-items">
<CardActionArea>
<CardMedia
component="img"
alt={person.alt}
height="140"
image={person.src}
title={person.title}
onClick={()=>{this.handleShowDialog(person.id)}}
/>
{(this.state.isOpen&&this.state.selected === person.id) && (
<dialog
className="dialog"
style={{ position: "absolute" }}
open
onClick={this.handleHideDialog}
>
<img
className="image"
src={person.src}
onClick={this.handleShowDialog}
alt="no image"
/>
</dialog>
)}
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{person.title}
</Typography>
<Typography
variant="body2"
color="textSecondary"
component="p"
>
{person.desc}
</Typography>
</CardContent>
</CardActionArea>
</Card>
))}
</div>
</div>
);
}
}
Related
In my application, the home page contains a form that creates workout objects. I am trying to do the same thing with meal objects, but on another page, and after clicking the "meals" button from my navbar, I get an error.
This is my navbar component where I click on the meals button.
import React,{useState, useEffect} from 'react';
import {Link, useLocation} from 'react-router-dom';
import { AppBar, Avatar, Toolbar, Typography, Button} from '#material-ui/core';
import { useDispatch } from 'react-redux';
import decode from 'jwt-decode';
import {useNavigate} from 'react-router-dom';
import useStyles from './styles';
const Navbar = () => {
const classes = useStyles();
const [user, setUser]=useState(JSON.parse(localStorage.getItem('profile')));
const dispatch=useDispatch();
const navigate=useNavigate();
const location=useLocation();
const logout=()=>{
dispatch({type:'LOGOUT'});
navigate('/');
setUser(null);
};
const meals=()=>{
dispatch({type:'MEALS'});
navigate('/meals');
};
useEffect(()=>{
const token=user?.token;
if(token){
const decodedToken=decode(token);
if (decodedToken.exp * 1000 < new Date().getTime()) logout();
}
setUser(JSON.parse(localStorage.getItem('profile')));
},[location])
return (
<AppBar className={classes.AppBar} position="static" color="inherit">
<div className={classes.brandContainer}>
<Typography component={Link} to="/" className={classes.heading} variant="h2" align="center">Workouts</Typography>
</div>
<Toolbar className={classes.toolbar}>
{user ? (
<div className={classes.profile}>
<Avatar className={classes.purple} referrerPolicy="no-referrer" alt={user.result.name} src={user.result.imageUrl}>{user.result.name.charAt(0)}</Avatar>
<Typography className={classes.userName} variant="h6">{user.result.name}</Typography>
<Button variant="contained" className={classes.logout} color="secondary" onClick={meals}>Meals</Button>
<Button variant="contained" className={classes.logout} color="secondary" onClick={logout}>Logout</Button>
</div>
) : (
<Button component={Link} to="/auth" variant="contained" color="primary">Sign In</Button>
)}
</Toolbar>
</AppBar>
)
}
export default Navbar
This is my single Meal component
import React from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#material-ui/core/';
import ThumbUpAltIcon from '#material-ui/icons/ThumbUpAlt';
import DeleteIcon from '#material-ui/icons/Delete';
import MoreHorizIcon from '#material-ui/icons/MoreHoriz';
import ThumbUpAltOutlined from '#material-ui/icons/ThumbUpAltOutlined';
import moment from 'moment';
import {useDispatch} from 'react-redux';
import useStyles from './styles';
import {deleteMeal, likeMeal} from '../../../actions/meals';
const Meal=({meal, setCurrentId})=> {
const classes=useStyles();
const dispatch=useDispatch();
const user = JSON.parse(localStorage.getItem('profile'));
const Likes = () => {
if (meal.likes.length > 0) {
return meal.likes.find((like) => like === (user?.result?.googleId || user?.result?._id))
? (
<><ThumbUpAltIcon fontSize="small" /> {meal.likes.length > 2 ? `You and ${meal.likes.length - 1} others` : `${meal.likes.length} like${meal.likes.length > 1 ? 's' : ''}` }</>
) : (
<><ThumbUpAltOutlined fontSize="small" /> {meal.likes.length} {meal.likes.length === 1 ? 'Like' : 'Likes'}</>
);
}
return <><ThumbUpAltOutlined fontSize="small" /> Like</>;
};
return (
<Card className={classes.card}>
<CardMedia className={classes.media} image={meal.selectedFile|| 'https://user-images.githubusercontent.com/194400/49531010-48dad180-f8b1-11e8-8d89-1e61320e1d82.png'} title={meal.category}/>
<div className={classes.overlay}>
<Typography variant="h6">{meal.calories}</Typography>
<Typography variant="body2">{moment(meal.createdAt).fromNow()}</Typography>
</div>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<div className={classes.overlay2}>
<Button style={{color:'white'}} size="small" onClick={()=>setCurrentId(meal._id)}>
<MoreHorizIcon fontSize="medium"/>
</Button>
</div>
)}
<div className={classes.details}>
<Typography className={classes.title} gutterBottom variant="h5" component="h2">{meal.name}</Typography>
</div>
<Typography className={classes.difficulty} variant="body2" color="textSecondary" component="p">{meal.category}</Typography>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">{meal.calories}</Typography>
</CardContent>
<CardActions className={classes.cardActions}>
<Button size='small' color="primary" disabled={!user?.result} onClick={()=>{dispatch(likeMeal(meal._id))}}>
<Likes/>
</Button>
{(user?.result?.googleId===meal?.creator || user?.result?._id===meal?.creator) && (
<Button size='small' color="primary" onClick={()=>{dispatch(deleteMeal(meal._id))}}>
<DeleteIcon fontSize="small"/>
Delete
</Button>
)}
</CardActions>
</Card>
);
}
export default Meal;
This is my Meals components that stands for all the Meal objects.
import React from 'react';
import {Grid, CircularProgress} from '#material-ui/core';
import { useSelector } from 'react-redux';
import Meal from './Meal/Meal';
import useStyles from './styles';
const Meals=({setCurrentId})=> {
const meals=useSelector((state)=>state.meals);
const classes =useStyles();
return (
!meals.length ? <CircularProgress/>:(
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
{
meals.map((meal)=>(
<Grid key={meal._id} item xs={12} sm={6} md={6}>
<Meal meal={meal} setCurrentId={setCurrentId}/>
</Grid>
))}
</Grid>
)
);
}
export default Meals;
The error says "cannot read properties of undefined (reading 'length')" and length is in this component.
And, below I have my MealsForm which is the form used for creating the objects.
import React, { useState, useEffect } from 'react';
import { TextField, Button, Typography, Paper } from '#material-ui/core';
import { useDispatch, useSelector } from 'react-redux';
import FileBase from 'react-file-base64';
import useStyles from './styles';
import { createMeal, updateMeal } from '../../actions/meals';
const MealForm=({ currentId, setCurrentId=()=>{} })=>{
const [mealData, setMealData]=useState({
category:'',name:'', calories:'',selectedFile:''
});
const meal = useSelector((state) => (currentId ? state.meals.find((meal) => meal._id === currentId) : null));
const dispatch=useDispatch();
const classes=useStyles();
const user=JSON.parse(localStorage.getItem('profile'));
useEffect(()=>{
if(meal) setMealData(meal);
},[meal]);
const clear = () => {
setCurrentId(0);
setMealData({ category:'',name:'', calories:'',selectedFile:'' });
};
const handleSubmit=async (e)=>{
e.preventDefault();
if(!currentId) {
dispatch(createMeal(mealData));
clear();
} else{
dispatch(updateMeal(currentId, mealData));
clear();
}
};
if (!user?.result?.name) {
return (
<Paper className={classes.paper}>
<Typography variant="h6" align="center">
Please Sign In to add meals
</Typography>
</Paper>
);
}
return(
<Paper className={classes.paper}>
<form autoComplete="off" noValidate className={`${classes.root} ${classes.form}`} onSubmit={handleSubmit}>
<Typography variant="h6">{currentId ? `Editing "${meal.calories}"` : 'Creating a meal'}</Typography>
<TextField name="category" variant="outlined" label="Category" fullWidth value={mealData.category} onChange={(e)=>setMealData({...mealData, category:e.target.value})}/>
<TextField name="name" variant="outlined" label="Name" fullWidth value={mealData.name} onChange={(e)=>setMealData({...mealData, name:e.target.value})}/>
<TextField name="calories" variant="outlined" label="Calories" fullWidth value={mealData.calories} onChange={(e)=>setMealData({...mealData, calories:e.target.value})}/>
<div className={classes.fileInput}>
<FileBase
type="file"
multiple={false}
onDone={({base64})=>setMealData({...mealData,selectedFile:base64})}
/>
</div>
<Button className={classes.buttonSubmit} variant="contained" color="primary" size="large" type="submit" fullWidth>Submit</Button>
<Button variant="contained" color="secondary" size="small" onClick={clear} fullWidth>Clear</Button>
</form>
</Paper>
);
}
export default MealForm;
Here I have the code for the actions on the meals object
import { FETCH_ALL, CREATE, UPDATE, DELETE, LIKE } from '../constants/actionTypes';
import * as api from '../api';
export const getMeals=()=> async (dispatch)=>{
try{
const {data}=await api.fetchMeals();
dispatch({type:FETCH_ALL,payload:data});
}catch(error){
console.log(error);
}
}
export const createMeal=(meal)=>async(dispatch)=>{
try{
const {data}=await api.createMeal(meal);
dispatch({type:CREATE,payload:data});
console.log(data);
}catch(error){
console.log(error.message);
}
}
export const updateMeal=(id,meal)=>async(dispatch)=>{
try{
const {data}=await api.updateMeal(id,meal);
dispatch({type:UPDATE,payload:data})
}catch(error){
console.log(error);
}
}
export const deleteMeal=(id)=>async (dispatch)=>{
try{
await api.deleteMeal(id);
dispatch({type:DELETE,payload:id});
}catch(error){
console.log(error);
}
}
export const likeMeal=(id)=>async(dispatch)=>{
try{
const {data}=await api.likeMeal(id);
dispatch({type:LIKE,payload:data})
}catch(error){
console.log(error);
}
}
Below I have the MealsPage component. The flow goes like this: Navbar, MealsPage, Meals, Meal
import React, {useState, useEffect} from 'react'
import {Container, Grow, Grid} from '#material-ui/core';
import {useDispatch} from 'react-redux';
import {getMeals} from '../../actions/meals';
import Meals from "../Meals/Meals";
import MealForm from "../MealForm/MealForm";
import useStyles from './styles';
function MealsPage() {
const [currentId, setCurrentId]=useState(null);
const classes=useStyles();
const dispatch=useDispatch();
useEffect(()=>{
dispatch(getMeals());
},[currentId, dispatch]);
const meals=getMeals();
console.log(meals);
return (
<Grow in>
<Container>
<Grid className={classes.mainContainer} container justifyContent="space-between" alignItems='stretch' spacing={3}>
<Grid item xs={12} sm={7}>
<Meals setCurrentId={setCurrentId}/>
</Grid>
<Grid item xs={12} sm={4}>
<MealForm currentId={currentId} setCurrentId={setCurrentId}/>
</Grid>
</Grid>
</Container>
</Grow>
)
}
export default MealsPage
I cannot see the problem. Could you help me solve this?
I have parent & its 2 child component
Parent code:
import React from 'react';
import { Grid, CircularProgress } from '#mui/material';
import { useSelector } from 'react-redux';
import Angle from './Angle/Angle';
import CustomSnackbar from '../Snackbar/CustomSnackbar';
import useStyles from './styles';
const Angles = ({ setCurrentId }) => {
const angles = useSelector((state) => state.angles);
const [snackbarOpenProp, setSnackbarOpenProp] = React.useState(false);
const classes = useStyles();
const handleSnackBarCloseAction = () => {
setSnackbarOpenProp(false)
}
const handleAddToCartAction = () => {
console.log('click');
setSnackbarOpenProp(true)
}
return (
!angles.length ? <CircularProgress /> : (
<Grid className={classes.container} container alignItems="stretch" spacing={3}>
{angles.map((angle) => (
<Grid key={angle._id} item xs={12} sm={6} md={6}>
<Angle angle={angle} setCurrentId={setCurrentId} handleAddToCart={handleAddToCartAction}/>
</Grid>
))}
<CustomSnackbar openState={snackbarOpenProp} handleSnackBarCloseProp={handleSnackBarCloseAction}/>
</Grid>
)
);
};
export default Angles;
Child 1 code:
import React from 'react';
import { Card, CardActions, CardContent, CardMedia, Button, Typography } from '#mui/material';
import Add from '#mui/icons-material/Add';
import { useDispatch } from 'react-redux';
import useStyles from './styles';
const Angle = ({ angle, setCurrentId,handleAddToCart }) => {
const dispatch = useDispatch();
const classes = useStyles();
return (
<Card className={classes.card}>
<CardMedia className={classes.media} image={angle.image} />
<div className={classes.overlay}>
<Typography variant="h6">{angle.qualityName}</Typography>
<Typography variant="body2">{angle.colors}</Typography>
</div>
<div className={classes.overlay2}>
<Button onClick={handleAddToCart} style={{ color: 'white' }} size="small" onClick={() => setCurrentId(angle._id)}><Add fontSize="large" /></Button>
</div>
</Card>
);
};
export default Angle;
Child 2 code:
import * as React from 'react';
import Button from '#mui/material/Button';
import Snackbar from '#mui/material/Snackbar';
import IconButton from '#mui/material/IconButton';
import CloseIcon from '#mui/icons-material/Close';
export default function CustomSnackbar(props) {
const action = (
<React.Fragment>
<Button color="secondary" size="small" onClick={props.handleSnackBarCloseProp}>
UNDO
</Button>
<IconButton
size="small"
aria-label="close"
color="inherit"
onClick={props.handleSnackBarCloseProp}
>
<CloseIcon fontSize="small" />
</IconButton>
</React.Fragment>
);
return (
<div>
<Snackbar
open={props.openState}
autoHideDuration={6000}
onClose={props.handleSnackBarCloseProp}
message="Note archived"
action={action}
/>
</div>
);
}
As you can see, I am trying to attach the onClick event which is in Child1 Angle code & based on its click I am trying to change a state value of another child2 CustomSnackbar and sending it as a prop, but on clicking I am not getting a response, How can I do it, also is there any more simple way to achieve this ?
There are two onClick methods on the button component. Please change according below example and try it.
Before:
<Button onClick={handleAddToCart} style={{ color: 'white' }} size="small" onClick={() => setCurrentId(angle._id)}><Add fontSize="large" /></Button>
After:
<Button
style={{ color: 'white' }}
size="small"
onClick={() => {
handleAddToCart();
setCurrentId(angle._id)
}
}>
<Add fontSize="large" />
</Button>
I have built a Trello clone using ReactJS, where I have 4 columns called TODO, DOING, DONE and REJECTED, where I can add a card to any column.
In a file I am trying to map over card component and rendering properties from defined dummy data.
What I want to do?
I want to delete a specific card when the button is clicked.
What I tried?
I have added the functionality in my Redux store, but when adding the onclick event to my button, I cannot access the dispatch method which will trigger the deleteCard function.
How do I do that?
My TaskboardList.js component :
import React from "react";
import TaskboardCard from "./TaskboardCard";
import TaskboardActionButton from "./TaskboardActionButton";
import { Droppable } from "react-beautiful-dnd";
const TaskboardList = ({ title, cards, listID }) => {
return (
<Droppable droppableId={String(listID)}>
{provided => (
<div
className="taskboardlist_container"
{...provided.droppableProps}
ref={provided.innerRef}
style={styles.container}
>
<div className="sub-heading">{title}</div>
{cards.map((card, index) => (
<TaskboardCard
key={card.id}
index={index}
text={card.text}
id={card.id}
/>
))}
<TaskboardActionButton listID={listID} />
{provided.placeholder}
</div>
)}
</Droppable>
);
};
const styles = {
container: {
backgroundColor: "#eee",
width: 300,
padding: "0.5rem",
marginRight: "1rem",
height: "100%"
}
};
export default TaskboardList;
My TaskboardCard.js component
import React from "react";
import Card from "#material-ui/core/Card";
import Typography from "#material-ui/core/Typography";
import CardContent from "#material-ui/core/CardContent";
import { Draggable } from "react-beautiful-dnd";
import { connect } from "react-redux";
import { deleteCard } from "../actions";
const TaskboardCard = ({ text, id, index, sample, cardId }) => {
// handleClickDelete = () => {
// // const { dispatch } = this.props;
// // dispatch(deleteCard(cardId));
// console.log("clicked");
// };
return (
<Draggable draggableId={String(id)} index={index}>
{provided => (
<div
className="taskboard_container"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card>
<CardContent>
<Typography style={{ fontSize: "1.5rem" }} gutterBottom>
{text}
</Typography>
</CardContent>
</Card>
{/* //delete added */}
<button
onClick={(cardId, props, sample, dispatch) => {
//const { dispatch } = this.props;
dispatch(deleteCard(cardId));
}}
>
DELETE
</button>
{/* ////////////////////// */}
</div>
)}
</Draggable>
);
};
export default connect()(TaskboardCard);
In the above component delete button is not working because somehow i cannot access the dispatch.
Here is my codesandbox link for further reference to files https://codesandbox.io/s/github/abhinav-anshul/consensolabs
Remove the props and dispatch from onclick event and add dispatch in component parameter list.
If you don't specify the second argument to connect(), your component will receive dispatch by default in porps.
import React from "react";
import Card from "#material-ui/core/Card";
import Typography from "#material-ui/core/Typography";
import CardContent from "#material-ui/core/CardContent";
import { Draggable } from "react-beautiful-dnd";
import { connect } from "react-redux";
import { deleteCard } from "../actions";
const TaskboardCard = ({ text, id, index, sample, cardId, dispatch }) => {
// handleClickDelete = () => {
// // const { dispatch } = this.props;
// // dispatch(deleteCard(cardId));
// console.log("clicked");
// };
return (
<Draggable draggableId={String(id)} index={index}>
{provided => (
<div
className="taskboard_container"
ref={provided.innerRef}
{...provided.draggableProps}
{...provided.dragHandleProps}
>
<Card>
<CardContent>
<Typography style={{ fontSize: "1.5rem" }} gutterBottom>
{text}
</Typography>
</CardContent>
</Card>
{/* //delete added */}
<button
onClick={(cardId, sample) => {
//const { dispatch } = this.props;
dispatch(deleteCard(cardId));
}}
>
DELETE
</button>
{/* ////////////////////// */}
</div>
)}
</Draggable>
);
};
export default connect()(TaskboardCard);
I don't understand why the following error appears.
I'm trying to render {links}, {collapse} in a return function, but it doesn't work.
links and collapse is about opening sub menus.
Thank you for your help.
The error message:
Objects are not valid as a React child (found: object with keys
{$$typeof, type, compare, displayName, muiName}). If you meant to
render a collection of children, use an array instead.
-- Sidebar.js
/*eslint-disable*/
import React from "react";
import classNames from "classnames";
import PropTypes from "prop-types";
import { NavLink } from "react-router-dom";
// #material-ui/core components
import { makeStyles } from "#material-ui/core/styles";
import {
Drawer,
Hidden,
List,
ListItem,
ListItemText,
Icon,
Collapse
} from "#material-ui/core";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import ListItemLink from "./ListItemLink";
// core components
import AdminNavbarLinks from "components/Navbars/AdminNavbarLinks.js";
import RTLNavbarLinks from "components/Navbars/RTLNavbarLinks.js";
import styles from "assets/jss/material-dashboard-react/components/sidebarStyle.js";
const useStyles = makeStyles(styles);
export default function Sidebar(props) {
const classes = useStyles();
// verifies if routeName is the one active (in browser input)
function activeRoute(routeName) {
return window.location.href.indexOf(routeName) > -1 ? true : false;
}
const { color, logo, image, logoText, routes } = props;
const [open, setOpen] = React.useState(true);
const handleClick = () => {
setOpen(!open);
};
var links = (
<div>
{routes.map((prop, key) => {
return (
<div>
{prop.submenu.length > 0 ? (
<ListItemLink
to={prop.layout + prop.path}
key={prop.id}
menuText={prop.name}
onClick={handleClick}
subOpen={open}
icon={prop.icon}
/>
) : (
<ListItemLink
to={prop.layout + prop.path}
key={prop.id}
menuText={prop.name}
icon={prop.icon}
/>
)}
</div>
);
})}
</div>
);
var collapse = (
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{routes.map((prop, key) => {
{
prop.submenu.map((sub, index) => {
return (
<ListItemLink
key={sub.id}
to={sub.path}
menuText={sub.name}
icon={sub.icon}
className={classes.nested}
/>
);
});
}
})}
</List>
</Collapse>
);
var brand = (
<div className={classes.logo}>
<a
className={classNames(classes.logoLink, {
[classes.logoLinkRTL]: props.rtlActive
})}
target="_blank"
>
<div className={classes.logoImage}>
<img src={logo} alt="logo" className={classes.img} />
</div>
{logoText}
</a>
</div>
);
return (
<div>
<Hidden mdUp implementation="css">
<Drawer
variant="temporary"
anchor={props.rtlActive ? "left" : "right"}
open={props.open}
classes={{
paper: classNames(classes.drawerPaper, {
[classes.drawerPaperRTL]: props.rtlActive
})
}}
onClose={props.handleDrawerToggle}
ModalProps={{
keepMounted: true // Better open performance on mobile.
}}
>
{brand}
<div className={classes.sidebarWrapper}>
{props.rtlActive ? <RTLNavbarLinks /> : <AdminNavbarLinks />}
<List component="nav" className={classes.list}>
{links}
{collapse}
</List>
</div>
{image !== undefined ? (
<div
className={classes.background}
style={{ backgroundImage: "url(" + image + ")" }}
/>
) : null}
</Drawer>
</Hidden>
<Hidden smDown implementation="css">
<Drawer
anchor={props.rtlActive ? "right" : "left"}
variant="permanent"
open
classes={{
paper: classNames(classes.drawerPaper, {
[classes.drawerPaperRTL]: props.rtlActive
})
}}
>
{brand}
<div className={classes.sidebarWrapper}>
<List component="nav" className={classes.list}>
{links}
{collapse}
</List>
</div>
{image !== undefined ? (
<div
className={classes.background}
style={{ backgroundImage: "url(" + image + ")" }}
/>
) : null}
</Drawer>
</Hidden>
</div>
);
}
Sidebar.propTypes = {
rtlActive: PropTypes.bool,
handleDrawerToggle: PropTypes.func,
bgColor: PropTypes.oneOf(["purple", "blue", "green", "orange", "red"]),
logo: PropTypes.string,
image: PropTypes.string,
logoText: PropTypes.string,
routes: PropTypes.arrayOf(PropTypes.object),
open: PropTypes.bool
};
-- ListItemLink.js
import React from "react";
import { ListItem, ListItemText, ListItemIcon } from "#material-ui/core";
import ExpandLess from "#material-ui/icons/ExpandLess";
import ExpandMore from "#material-ui/icons/ExpandMore";
import { PropTypes } from "prop-types";
import { Link as RouterLink } from "react-router-dom";
function ListItemLink(props) {
const { to, menuText, icon, subOpen, ...other } = props;
return (
<ListItem button component={RouterLink} to={to} {...other}>
<ListItemIcon>{icon}</ListItemIcon>
<ListItemText primary={menuText} />
{subOpen != null ? subOpen ? <ExpandLess /> : <ExpandMore /> : null}
</ListItem>
);
}
ListItemLink.propTypes = {
subOpen: PropTypes.bool,
to: PropTypes.string.isRequired
};
export default ListItemLink;
in collapse, you have two map functions. you should return the second map function.
something like this:
const collapse = (
<Collapse in={open} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{routes.map((prop, key) => {
return prop.submenu.map((sub, index) => {
return (
<ListItemLink
key={sub.id}
to={sub.path}
menuText={sub.name}
icon={sub.icon}
className={classes.nested}
/>
);
});
})}
</List>
</Collapse>
);
I solved my question as followed:
I guess I returned the wrong object...
<List
{...rest}
className={clsx(classes.root, className)}
>
{pages.map(page => (
<React.Fragment key={page.title}>
<ListItem
className={classes.item}
disableGutters
key={page.title}
onClick={page.submenu.length > 0 ? handleClick : null}
open={page.submenu.length > 0 ? open : null}
>
<Button
activeClassName={classes.active}
className={classes.button}
component={CustomRouterLink}
to={page.href}
>
<div className={classes.icon}>{page.icon}</div>
{page.title}
{page.submenu.length > 0 ? (
open ? (
<ExpandLess />
) : (
<ExpandMore />
)
) : null}
</Button>
</ListItem>
<Collapse in={open} timeout={100} unmountOnExit>
<List component="div" disablePadding>
{page.submenu.map((sub, index) => {
return (
<React.Fragment key={sub.title}>
<ListItem
className={classes.nested}
disableGutters
key={sub.title}
>
<Button
activeClassName={classes.active}
className={classes.button}
component={CustomRouterLink}
to={sub.href}
>
<div className={classes.icon}>{sub.icon}</div>
{sub.title}
</Button>
</ListItem>
</React.Fragment>
);
})}
</List>
</Collapse>
</React.Fragment>
))}
</List>
I have this code:
const useStyles = makeStyles({
card: {
maxWidth: 345,
},
media: {
height: 140,
},
});
export default function AlbumCard(props) {
const classes = useStyles();
let artist
let albumName
let artistHead
albumName = props.album.name
artist = props.album.artists.map((name, key) => {
if(albumName != name.name) {
return <Typography variant="body2" color="textSecondary" component="p" key={key}>
{name.name}
</Typography>
}
})
artistHead = props.album.artists.length > 1 ? 'Artists:' : 'Artist:'
const album = props.album
const page = "https://open.spotify.com/album/" + album.id
return(
<Grid item md={3}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.card}
component="img"
alt={album.name}
height="140"
width="100"
image={album.images[0].url}
title={album.name}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{album.name}
</Typography>
<Typography variant="body2" color="textSecondary" component="h3">
Release Date
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{album.release_date}
</Typography>
<Typography variant="body2" color="textSecondary" component="h3">
{artistHead}
</Typography>
{artist}
<Typography variant="body2" color="textSecondary" component="p">
{!props.showMarkets && album.available_markets.map((c, i) => {
return <li key={i}>{c}</li>
})}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
Album Page
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
<a href={album.uri}>Open In App</a>
</Typography>
</CardContent>
</CardActionArea>
<Button
size="small"
color="primary"
onClick={props.handleShow}
>{!props.showMarkets ? "Hide Markets" : "Show Markets"}</Button>
</Card>
</Grid>
)
}
This code is rendering by this high-order components:
class Album extends React.Component {
constructor(props) {
super(props)
this.state={
artistId: '',
albumName: '',
holeData: '',
marketsDisplay: true,
data: []
}
this.handleOnChange = this.handleOnChange.bind(this)
this.handleOnClick = this.handleOnClick.bind(this)
}
handleOnChange(e) {
e.preventDefault()
const target = e.target
const value = target.value
const name = target.name
this.setState({
[name]: value
})
}
handleAlert(e) {
e.preventDefault()
alert('Plead type the album name and/or artist id')
}
handleOnClick(e) {
e.preventDefault()
fetch('https://api.spotify.com/v1/artists/' + this.state.artistId + '/albums', {
"method": "GET",
"dataType": "json",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + this.props.access_token
}
})
.then(res => res.json())
.then((data) => {
this.setState({
holeData: data,
data: data.items
})
})
}
ToggleButton() {
this.setState((currentState) => ({
marketsDisplay: !currentState.marketsDisplay
}))
}
render() {
let albums
let albumList
let display
albumList = this.state.data.map((name, key) => {
return <AlbumCard
album={name}
showMarkets={this.state.marketsDisplay}
handleShow={() => this.ToggleButton()}
key={key}
/>
})
return(
<React.Fragment>
<CssBaseline />
<Container maxWidth="sm">
<Typography component="div" style={{backgroundColor: '#cfe8fc', marginTop: '5%', marginBottom: '5%'}}>
<FormControl fullWidth={true}>
<TextField
id="outlined-basic"
label="Artist ID"
variant="outlined"
type="text"
name="artistId"
value={this.state.artistId}
onChange={this.handleOnChange}>
</TextField>
<Button
type="button"
color="secondary"
onClick={this.state.artistId ? this.handleOnClick : this.handleAlert}>Getir</Button>
</FormControl>
</Typography>
</Container>
<Grid container spacing={6}>
{albumList}
</Grid>
</React.Fragment>
)
}
}
export default Album
In the end I have multiple cards. When I click the button of any card, I'm expecting open a list of JUST one card but happening is when I click any button, all card's lists are opening.
I couldn't achieve this problem.
I'm stuck so bad.
Pelase help me.
Thank you.
Well, I did it.
I created states from returning data from Spotify API and made the values false.
After, I created two functions. One is making the value true and the other false.
And finally I gave the functions to props of AlbumCard component as value.
Here is my codes last versions (also there are some other changes irrelevant with the issue):
Album.js
import React from 'react';
import CssBaseline from '#material-ui/core/CssBaseline';
import Typography from '#material-ui/core/Typography';
import Container from '#material-ui/core/Container';
import FormControl from '#material-ui/core/FormControl';
import Button from '#material-ui/core/Button';
import TextField from '#material-ui/core/TextField';
import AlbumCard from './Card';
import Grid from '#material-ui/core/Grid';
import SelectArtist from './SelectArtist'
class Album extends React.Component {
constructor(props) {
super(props)
this.state={
artistId: '',
artistName: '',
holeData: '',
artists: '',
showSelect: false,
marketsDisplay: true,
data: []
}
this.handleOnChange = this.handleOnChange.bind(this)
this.handleOnClick = this.handleOnClick.bind(this)
this.handleOnArtists = this.handleOnArtists.bind(this)
}
handleOnChange(e) {
e.preventDefault()
const target = e.target
const value = target.value
const name = target.name
this.setState({
[name]: value
})
}
handleAlert(e) {
e.preventDefault()
alert('Plead type the album name and/or artist id')
}
handleOnClick(e) {
e.preventDefault()
fetch('https://api.spotify.com/v1/artists/' + this.state.artistId + '/albums', {
"method": "GET",
"dataType": "json",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + this.props.access_token
}
})
.then(res => res.json())
.then((data) => {
let idObj={}
this.setState({
holeData: data,
data: data.items,
})
//create states with false value
//from returning data
data.items.map((name, key) => {
this.setState({
[name.id]: false
})
})
})
}
handleOnArtists(e) {
e.preventDefault()
fetch('https://api.spotify.com/v1/search?q=' + encodeURIComponent(this.state.artistName) + '&type=artist', {
"method": "GET",
"dataType": "json",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer " + this.props.access_token
}
})
.then(res => res.json())
.then((data) => {
this.setState({
artists: data.artists.items,
showSelect: true
})
})
}
//make the value true
ToggleOpen(event, type) {
this.setState({
[type]: true
})
}
//make the value false
ToggleClose(event, type) {
this.setState({
[type]: false
})
}
render() {
let albumList
let artists
let albumId
artists = <SelectArtist
artists={this.state.artists}
name="artistId"
artistId={this.state.artistId}
handleOnChange={this.handleOnChange}
showSelect={this.state.showSelect}
/>
albumList = this.state.data.map((name, key) => {
return <AlbumCard
album={name}
//give the states to AlbumCard component as props
showMarkets={this.state[name.id]}
//give the functions to AlbumCard component as props
handleShow={
this.state[name.id] ?
event => this.ToggleClose(event, name.id) :
event => this.ToggleOpen(event, name.id)
}
key={key}
/>
})
return(
<React.Fragment>
<CssBaseline />
<Container>
<Typography component="div" style={{marginTop: '5%', marginBottom: '5%'}}>
<FormControl fullWidth={true}>
<TextField
id="outlined-basic"
label="Artist Name"
variant="outlined"
type="text"
name="artistName"
value={this.state.artistName}
onChange={this.handleOnChange}>
</TextField>
<Button
variant="contained"
type="button"
color="primary"
onClick={this.state.artistName ? this.handleOnArtists : this.handleAlert}>Get Artists</Button>
{this.state.showSelect ? artists : null}
{
this.state.artistId ?
<Button
variant="contained"
type="button"
color="primary"
onClick={this.state.artistId ? this.handleOnClick : this.handleAlert}>
Get Albums
</Button> : null
}
</FormControl>
</Typography>
<Grid container spacing={6}>
{albumList}
</Grid>
</Container>
</React.Fragment>
)
}
}
export default Album
Card.js
import React from 'react';
import { makeStyles } from '#material-ui/core/styles';
import Card from '#material-ui/core/Card';
import CardActionArea from '#material-ui/core/CardActionArea';
import CardActions from '#material-ui/core/CardActions';
import CardContent from '#material-ui/core/CardContent';
import CardMedia from '#material-ui/core/CardMedia';
import Typography from '#material-ui/core/Typography';
import Grid from '#material-ui/core/Grid';
import Button from '#material-ui/core/Button';
const useStyles = makeStyles({
card: {
maxWidth: 345,
},
media: {
height: 140,
},
});
export default function AlbumCard(props) {
const classes = useStyles();
let artist
let albumName
let artistHead
albumName = props.album.name
artist = props.album.artists.map((name, key) => {
if(albumName != name.name) {
return <Typography variant="body2" color="textSecondary" component="p" key={key}>
{name.name}
</Typography>
}
})
artistHead = props.album.artists.length > 1 ? 'Artists:' : 'Artist:'
const album = props.album
const page = "https://open.spotify.com/album/" + album.id
return(
<Grid item md={3}>
<Card className={classes.card}>
<CardActionArea>
<CardMedia
className={classes.card}
component="img"
alt={album.name}
height="140"
width="100"
image={album.images[0].url}
title={album.name}
/>
<CardContent>
<Typography gutterBottom variant="h5" component="h2">
{album.name}
</Typography>
<Typography variant="body2" color="textSecondary" component="h3">
Release Date:
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
{album.release_date}
</Typography>
<Typography variant="body2" color="textSecondary" component="h3">
{artistHead}
</Typography>
{artist}
<Typography variant="body2" color="textSecondary" component="p">
{props.showMarkets && album.available_markets.map((c, i) => {
return <li key={i}>{c}</li>
})}
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
Album Page
</Typography>
<Typography variant="body2" color="textSecondary" component="p">
<a href={album.uri}>Open In App</a>
</Typography>
</CardContent>
</CardActionArea>
<Button
size="small"
color="primary"
onClick={props.handleShow}
>{props.showMarkets ? "Hide Markets" : "Show Markets"}</Button>
</Card>
</Grid>
)
}
I guess I'll be never a good narrator but hope some others find my solution helpful