Currently working on a social media type app for class, but came across some issues grabbing values from user object.
friends is set to be an empty array. None of the other values destructered appear when passing the variable they are assigned too. want to know why the user is not getting set to data in the getUser function.
import {
ManageAccountsOutlined,
EditOutlined,
LocationOnOutlined,
WorkOutlineOutlined,
} from "#mui/icons-material";
import { Box, Typography, Divider, useTheme } from "#mui/material";
import UserImage from "../../components/UserImage";
import FlexBetween from "../../components/FlexBetween";
import WidgetWrapper from "../../components/WidgetWrapper";
import { useSelector } from "react-redux";
import { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
const UserWidget = ({ userId, picturePath }) => {
const [user, setUser] = useState(null);
const { palette } = useTheme();
const navigate = useNavigate();
const token = useSelector((state) => state.token);
const dark = palette.neutral.dark;
const medium = palette.neutral.medium;
const main = palette.neutral.main;
const getUser = async () => {
const response = await fetch(`http://localhost:3001/users/${userId}`, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
setUser(data);
};
useEffect(() => {
getUser();
}, []); // eslint-disable-line react-hooks/exhaustive-deps
if (!user) {
return null;
}
const {
firstName,
lastName,
location,
occupation,
viewedProfile,
impressions,
friends
} = user;
return (
<WidgetWrapper>
{/* FIRST ROW */}
<FlexBetween
gap="0.5rem"
pb="1.1rem"
onClick={() => navigate(`/profile/${userId}`)}
>
<FlexBetween gap="1rem">
<UserImage image={picturePath} />
<Box>
<Typography
variant="h4"
color={dark}
fontWeight="500"
sx={{
"&:hover": {
color: palette.primary.light,
cursor: "pointer",
},
}}
>
{firstName} {lastName}
</Typography>
<Typography color={medium}>{friends.length} friends</Typography>
</Box>
</FlexBetween>
<ManageAccountsOutlined />
</FlexBetween>
<Divider />
{/* SECOND ROW */}
<Box p="1rem 0">
<Box display="flex" alignItems="center" gap="1rem" mb="0.5rem">
<LocationOnOutlined fontSize="large" sx={{ color: main }} />
<Typography color={medium}>{location}</Typography>
</Box>
<Box display="flex" alignItems="center" gap="1rem">
<WorkOutlineOutlined fontSize="large" sx={{ color: main }} />
<Typography color={medium}>{occupation}</Typography>
</Box>
</Box>
<Divider />
{/* THIRD ROW */}
<Box p="1rem 0">
<FlexBetween mb="0.5rem">
<Typography color={medium}>Who's viewed your profile</Typography>
<Typography color={main} fontWeight="500">
{viewedProfile}
</Typography>
</FlexBetween>
<FlexBetween>
<Typography color={medium}>Impressions of your post</Typography>
<Typography color={main} fontWeight="500">
{impressions}
</Typography>
</FlexBetween>
</Box>
<Divider />
{/* FOURTH ROW */}
<Box p="1rem 0">
<Typography fontSize="1rem" color={main} fontWeight="500" mb="1rem">
Social Profiles
</Typography>
<FlexBetween gap="1rem" mb="0.5rem">
<FlexBetween gap="1rem">
<img src="../assets/twitter.png" alt="twitter" />
<Box>
<Typography color={main} fontWeight="500">
Twitter
</Typography>
<Typography color={medium}>Social Network</Typography>
</Box>
</FlexBetween>
<EditOutlined sx={{ color: main }} />
</FlexBetween>
<FlexBetween gap="1rem">
<FlexBetween gap="1rem">
<img src="../assets/linkedin.png" alt="linkedin" />
<Box>
<Typography color={main} fontWeight="500">
Linkedin
</Typography>
<Typography color={medium}>Network Platform</Typography>
</Box>
</FlexBetween>
<EditOutlined sx={{ color: main }} />
</FlexBetween>
</Box>
</WidgetWrapper>
);
};
export default UserWidget;
const {
firstName,
lastName,
location,
occupation,
viewedProfile,
impressions,
friends
} = user;
store all your data from fetch inside of useState
make it useState({your object})
Don't do this:
if (!user) {
return null;
}
const {
firstName,
lastName,
location,
occupation,
viewedProfile,
impressions,
friends
} = user;
istead do:
const [user, setUser] = useState({});
const getUser = async () => {
const response = await fetch(`http://localhost:3001/users/${userId}`, {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
});
const data = await response.json();
if(response.statuse===200){
setUser(data);
}else{console.log(response)}
};
I am also working fetch in react. Do not forget to use
try{}
catch(err){
console.log(err)}
Related
Sometimes when I click the Add button the function is adding an empty array to the JSON file. But sometimes it works as intended. I've tried moving the variables and state around and it is still doing the same thing. The exercise prop comes from a search of an API and the prop is passed down to this component. The component displays a list of saved exercise cards that can be added to the database. Why is this happening?
import {
Button,
Card,
CardContent,
CardMedia,
Container,
Typography,
} from "#mui/material";
import { Box } from "#mui/system";
import React, { useState } from "react";
const ExerciseCard = ({ exercise }) => {
const [selectedExercise, setSelectedExercise] = useState([]);
const [selectedExerciseName, setSelectedExerciseName] = useState();
const [fetchedData, setFetchedData] = useState([]);
const addExerciseToDB = async () => {
await fetch("http://localhost:3001/savedexercises")
.then((res) => {
return res.json();
})
.then((data) => {
setFetchedData(data);
return fetchedData;
});
const savedFetchedName = fetchedData.map((fetched) => fetched.name);
setSelectedExercise([]);
setSelectedExercise({
apiId: exercise.id,
name: exercise.name,
target: exercise.target,
gifUrl: exercise.gifUrl,
});
setSelectedExerciseName(exercise.name);
if (savedFetchedName.includes(selectedExerciseName)) {
console.log("already added exercise");
} else {
console.log("adding new exercise");
await fetch("http://localhost:3001/savedExercises", {
method: "POST",
body: JSON.stringify(selectedExercise),
headers: { "Content-Type": "application/json" },
});
}
};
return (
<>
<Container maxWidth="xl">
<Box>
<Card>
<CardMedia
component="img"
alt={exercise.name}
image={exercise.gifUrl}
/>
<CardContent sx={{ pb: 2, height: "75px" }}>
<Typography variant="h5" sx={{ pb: 1 }}>
{exercise.name.toUpperCase()}
</Typography>
<Typography variant="body2">
{exercise.target.toUpperCase()}
</Typography>
</CardContent>
<Box>
<Box>
<Button
variant="contained"
color="error"
size="medium"
sx={{ m: 2 }}
onClick={() => addExerciseToDB()}
>
Add
</Button>
</Box>
</Box>
</Card>
</Box>
</Container>
</>
);
};
export default ExerciseCard;
await fetch("http://localhost:3001/savedexercises")
.then((res) => {
return res.json();
})
.then((data) => {
setFetchedData(data);
return fetchedData;
});
When I make API call and store the response in useState, it shows the data while running console.log(), however when this state value is passed as an argument to another component which is supposed to take the data and mapped it to show the result, it gives me an error saying
" Cannot read properties of undefined (reading 'map')"
Can anyone help me figure out what's wrong with my code?
Edit - As Mr.Silva suggested below, I added {menFootwears &&
menFootwears.map((menFootwear)=> () )}
It no longer shows error anymore, however, it also doesn't show the
data even though the data shows as an output in console.log() in
Product.jsx whereas it shows undefined in MenShoes.jsx and WomenShoes.jsx
Here's my code for Product.jsx
import { useMediaQuery } from '#mui/material';
import { Box } from '#mui/system';
import React from 'react'
import { theme } from '../style/theme';
import MenShoes from './collections/MenShoes';
import WomenShoes from './collections/WomenShoes';
export const Products = () => {
const matchScreen = useMediaQuery(theme.breakpoints.down('md'))
const [isLoading, setIsLoading] = React.useState(true);
const [menFootwears, setMenFootwears] = React.useState([]);
const [womenFootwears, setWomenFootwears] = React.useState([]);
//Women FootWears
async function fetchWomenFootwear () {
setIsLoading(true)
await fetch('https://dummyjson.com/products/category/womens-shoes')
.then(response => response.json())
.then(response => setWomenFootwears(response.products))
setIsLoading(false);
}
//Men Footwears
async function fetchMenFootwear () {
setIsLoading(true)
await fetch('https://dummyjson.com/products/category/mens-shoes')
.then(response => response.json())
.then(response => setMenFootwears(response.products))
setIsLoading(false)
}
React.useEffect(()=> {
fetchWomenFootwear()
fetchMenFootwear()
}, [])
const handleProductCard = (id) => {
console.log('hello')
}
console.log( womenFootwears, menFootwears)
return (
<Box>
<WomenShoes data={womenFootwears} onclick={handleProductCard} loadingStatus={isLoading}/>
<MenShoes data={menFootwears} onclick={handleProductCard} loadingStatus={isLoading}/>
</Box>
)
}
Both WomenShoes and MenShoes are designed using the same code except for the API response array data.
MenShoes/WomenShoes.jsx
import { ShoppingCartSharp } from '#mui/icons-material';
import { Button, Card, CardActionArea, CardContent, CardMedia, Divider, Rating, Skeleton, Typography, useMediaQuery } from '#mui/material';
import { Box } from '#mui/system';
import React from 'react'
import { theme } from '../../style/theme';
export default function MenShoes({menFootwears, handleProductCard, isLoading}) {
const matchScreen = useMediaQuery(theme.breakpoints.down('md'))
return(
<Box pt={2} mt={4}>
<Divider variant='middle' sx={{
"&.MuiDivider-root": {
"&::before, &::after": {
borderTopColor:theme.palette.primary.light,
borderTopWidth:'thin',
borderTopStyle:'solid'
},
}
}}>
<Typography color={theme.palette.primary.main} variant={!matchScreen ? 'h3': 'h5'}>
Men Footwears Collections
</Typography>
</Divider>
<Box display='flex'
justifyContent='space-evenly'
alignItems='center'
flexWrap='wrap'
pt={2}
mt={2}
px={2}>
{menFootwears.map((menFootwear)=> (
<Card key={menFootwear.id}
sx={{maxWidth:335,
height:'auto',
marginTop:'3.5em',
flex:!matchScreen ? '0 0 45%' : '0 0 80%'
}}
elevation={4}
onClick={()=>{handleProductCard(menFootwear.id)}}>
<CardActionArea>
{isLoading ?
<>
<Skeleton variant='rectangular' width='335' height='220' animation='wave'/>
</> :
<CardMedia component='img'
height='220'
image={menFootwear.images[0]}/>}
<CardContent sx={{
textAlign:'center',
}}>
{ isLoading ?
<>
<Skeleton variant='h6' animation='wave'/>
</> :
<Typography gutterBottom variant='h6'
fontWeight='bold'
color={theme.palette.primary.main}>
{menFootwear.title}
</Typography>}
{isLoading ?
<>
<Skeleton variant='body2' animation='wave'/>
</> :
<Typography variant='body2' gutterBottom color={theme.palette.primary.dark}>
Brand : {menFootwear.brand}
</Typography>}
{ isLoading ?
<>
<Skeleton variant='h5' animation='wave'/>
</> :
<Typography variant='h5' gutterBottom color={theme.palette.primary.main}>
$ {menFootwear.price}
</Typography>}
<Rating
size='small'
name="rating"
value={menFootwear.rating}
readOnly/>
</CardContent>
</CardActionArea>
{ isLoading ?
<>
<Skeleton variant='rectangular' width='335' height='20' animation='wave'/>
</> :
<Button size='medium'
sx={{all:'unset',
textAlign:'center',
fontFamily:theme.typography.fontFamily,
fontSize:16,
width:'100%',
padding:'0.7em',
margin:0,
color:'white',
background:`linear-gradient(90deg, ${theme.palette.primary.main},transparent) ${theme.palette.tertiary.main}`,
transition:'background 0.5s',
'&:hover': {
background:theme.palette.secondary.main,
}
}}>
<span style={{display:'inline-flex', alignItems:'center'}}>
<ShoppingCartSharp size='small'/> Add to Cart
</span>
</Button>}
</Card>
))}
</Box>
</Box>
)
}
Check before using the map if the variable menFootwears is not undefined or if the array is empty.
{menFootwears && menFootwears.map(el => () )}
Actually, I've got a straightforward React web app. Additionally, it integrates two apis. One is for all blogs, and the other is for particular blog information. Users can view specific blog details whenever they click on a particular blog. The issue is that I want to include the views count in the Redux Tool Kit store whenever I click to view a specific blog's detail page.
blogSlice.js:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit';
import axios from 'axios';
const url = 'https://61791a83aa7f3400174047a6.mockapi.io/v1/GetBLogs/';
const initialState = {
blogs: [],
views: 0,
isLoading: false,
};
export const getBlogs = createAsyncThunk('blog/getBlogs', async () => {
return await axios
.get(url)
.then(res => res.data)
.catch(err => {
console.log(err);
});
});
const blogSlice = createSlice({
name: 'blogs',
initialState,
reducers: {
addViewCount: (state, { payload }) => {
state.views = payload++;
},
},
extraReducers: {
[getBlogs.pending]: state => {
state.isLoading = true;
},
[getBlogs.fulfilled]: (state, { payload }) => {
state.isLoading = false;
state.blogs = payload;
},
[getBlogs.pending]: state => {
state.isLoading = false;
},
},
});
export const { addViewCount } = blogSlice.actions;
export default blogSlice.reducer;
BlogCard.jsx: (mapping all blogs)
import { Heading, Image, Stack, Text } from '#chakra-ui/react';
import axios from 'axios';
import { useNavigate } from 'react-router-dom';
const BlogCard = ({ blogs }) => {
const navigate = useNavigate();
const singleBlogHandler = async (e, id) => {
e.preventDefault();
const { data } = await axios.get(
`https://61791a83aa7f3400174047a6.mockapi.io/v1/GetBLogs/${id}`
);
navigate(`blogs/${id}`, { state: data });
};
return (
<>
{blogs.map(blog => {
return (
<Stack
key={blog.id}
width={'100%'}
height={'500'}
whiteSpace={'pre-wrap'}
overflow={'hidden'}
cursor={'pointer'}
onClick={e => singleBlogHandler(e, blog.id)}
>
<Stack direction={'column'} py={'2'} px="2">
<Stack>
<Image height={'250px'} src={blog.Image} />
</Stack>
<Stack
justifyContent={'space-between'}
direction={'row'}
width={'100%'}
color={' #939191'}
>
<Stack direction={'row'}>
<Text fontSize={'14px'}>Posted on October 6th 2021</Text>
</Stack>
<Stack>
<Text fontSize={'14px'}>563 views</Text>
</Stack>
</Stack>
<Heading
whiteSpace={''}
fontWeight={600}
fontSize={'26px'}
>
{blog.Title}
</Heading>
<Text color={'#232536'} height={'200px'}>
{blog.Article}
</Text>
</Stack>
</Stack>
);
})}
</>
);
};
export default BlogCard;
SingleBlogDetails:
import React from 'react';
import { Stack, Heading, Text, Image } from '#chakra-ui/react';
import blogheader from '../assets/images/blogheader.png';
const SingleBlogDetails = ({ data }) => {
const blogDetails = data;
if (!blogDetails) {
return <div>Loading....</div>;
}
return (
<Stack
height={'100%'}
width="100%"
justifyContent={'center'}
alignItems={'center'}
margin={'0px !important'}
>
<Stack width={'78%'} backgroundColor={'white'} alignContent={'center'}>
<Stack
p={'8'}
height={'100%'}
direction="row"
justifyContent={'center'}
>
<Stack
direction={'row'}
width={'70%'}
marginInlineStart={'0px !important'}
>
<Stack
direction={'column'}
justifyContent={'space-between'}
width="100%"
px={'3'}
>
<Stack fontSize={'14px'} direction={'row'} color={' #939191'}>
<Text>Posted on October 6th 2021</Text>
<Text>563 views</Text>
</Stack>
<Stack>
<Heading textAlign={'justify'} fontWeight={700} fontFamily={'Manrope !important'}>
{blogDetails.Title}
</Heading>
</Stack>
<Text color={'#232536'} fontSize={'14px'}>
{blogDetails.Subtitle}
</Text>
</Stack>
</Stack>
</Stack>
<Stack marginTop={'10px'}>
<Image src={blogDetails.Image} />
</Stack>
<Stack
p={'8'}
height={'100%'}
direction="row"
justifyContent={'center'}
>
<Stack
direction={'row'}
width={'70%'}
marginInlineStart={'0px !important'}
>
<Stack direction={'column'} width="100%" px={'3'}>
<Stack>
<Heading fontWeight={700} fontFamily={'Manrope !important'}>
{blogDetails.Title}
</Heading>
</Stack>
<Text color={'#232536'} fontSize={'14px'}>
{blogDetails.Subtitle}
</Text>
<Text color={'#232536'} fontSize={'14px'}>
{blogDetails.Article}
</Text>
<Stack marginTop={'10px'}>
<Image src={blogDetails.Image} />
</Stack>
</Stack>
</Stack>
</Stack>
</Stack>
</Stack>
);
};
export default SingleBlogDetails;
I am building a form where I have to login into the user by their phone number on CLICKING the send code button I got an error TypeError: Object(...) is not a function where it says that window is not a function can anybody solve my problem.
Error Image
Here is some of my code
import * as React from "react";
import { useState } from "react";
import Avatar from "#mui/material/Avatar";
import Button from "#mui/material/Button";
import ButtonGroup from "#mui/material/ButtonGroup";
import CssBaseline from "#mui/material/CssBaseline";
import TextField from "#mui/material/TextField";
import FormControlLabel from "#mui/material/FormControlLabel";
import Checkbox from "#mui/material/Checkbox";
import Link from "#mui/material/Link";
import Paper from "#mui/material/Paper";
import Box from "#mui/material/Box";
import Grid from "#mui/material/Grid";
import LockOutlinedIcon from "#mui/icons-material/LockOutlined";
import Typography from "#mui/material/Typography";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import background from "../staticfiles/signin-background.jpg";
import "react-phone-input-2/lib/style.css";
import { auth, db, captcha } from "../config/Config";
import { RecaptchaVerifier } from "firebase/auth";
import { Link as RouterLink } from "react-router-dom";
import { useHistory } from "react-router-dom";
import socialMediaAuth from "../service/auth";
function Copyright(props) {
return (
<Typography
variant="body2"
color="text.secondary"
align="center"
{...props}
>
{"Copyright © "}
<Link color="inherit" href="https://mui.com/">
Your Website
</Link>{" "}
{new Date().getFullYear()}
{"."}
</Typography>
);
}
const theme = createTheme();
export default function SignInUserPhone() {
let history = useHistory();
const [PhoneNumber, setPhoenNumber] = useState("");
const [VerificationCode, setVerificationCode] = useState("");
const [error, setError] = useState("");
const handleSubmit = (event) => {
event.preventDefault();
console.log(PhoneNumber);
console.log(error);
};
const handleSubmit2 = (e) => {
e.preventDefault();
console.log(VerificationCode);
};
const handleOnClick = async (provider) => {
const res = await socialMediaAuth(provider);
await db
.collection("SignedUpUsersData")
.doc(res.uid)
.set({
Email: res.email,
Name: res.displayName,
})
.then(() => {
history.push("/");
})
.catch((err) => setError(err.message));
};
const handleUserButton = (event) => {
event.preventDefault();
history.push("/signinuser");
};
const handleSellerButton = (event) => {
event.preventDefault();
history.push("/signinseller");
};
auth.languageCode = "it";
const setUpCaptcha = () => {
window.recaptchaVerifier = auth().RecaptchaVerifier("recaptcha-container", {
size: "invisible",
callback: (response) => {
// reCAPTCHA solved, allow signInWithPhoneNumber.
console.log(response);
console.log("Ok recapthca sloved");
onSignInSubmit();
},
});
};
const onSignInSubmit = (e) => {
e.preventDefault();
setUpCaptcha();
const phoneNumber = PhoneNumber;
const appVerifier = window.recaptchaVerifier;
auth()
.signInWithPhoneNumber(PhoneNumber, appVerifier)
.then((confirmationResult) => {
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
window.confirmationResult = confirmationResult;
console.log(confirmationResult);
// ...
})
.catch((error) => {
// Error; SMS not sent
// ...
console.log(error.message);
//( Or, if you haven't stored the widget ID:
});
};
return (
<ThemeProvider theme={theme}>
<Grid container component="main" sx={{ height: "100vh" }}>
<CssBaseline />
<Grid
item
xs={false}
sm={4}
md={7}
sx={{
backgroundImage: `url(${background})`,
backgroundRepeat: "no-repeat",
backgroundColor: (t) =>
t.palette.mode === "light"
? t.palette.grey[50]
: t.palette.grey[900],
backgroundSize: "cover",
backgroundPosition: "center",
}}
/>
<Grid item xs={12} sm={8} md={5} component={Paper} elevation={6} square>
<Box
sx={{
my: 8,
mx: 4,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<Avatar sx={{ m: 1, bgcolor: "secondary.main" }}>
<LockOutlinedIcon />
</Avatar>
<Typography component="h1" variant="h5">
Sign in With Phone Number
</Typography>
<Box
sx={{
my: 4,
mx: 4,
display: "flex",
flexDirection: "column",
alignItems: "center",
}}
>
<ButtonGroup size="large" disableElevation variant="contained">
<Button onClick={handleSellerButton}>SELLER</Button>
<Button onClick={handleUserButton}>USER</Button>
</ButtonGroup>
</Box>
<Box
component="form"
noValidate
onSubmit={onSignInSubmit}
sx={{ mt: 1 }}
>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Phone Number"
name="Phone"
autoComplete="phoenumber"
value={PhoneNumber}
onChange={(phone) => setPhoenNumber(phone.target.value)}
/>
<div id="recaptcha-container"></div>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
onSubmit={onSignInSubmit}
id="sign-in-button"
>
Send Code
</Button>
<Grid container>
<Grid item xs></Grid>
<Grid item>
<Link
component={RouterLink}
to="/signup"
href="#"
variant="body2"
>
{"Don't have an account? Sign Up"}
</Link>
</Grid>
</Grid>
</Box>
{error && <div>{error}</div>}
<Box
component="form"
noValidate
onSubmit={handleSubmit2}
sx={{ mt: 1 }}
>
<TextField
margin="normal"
required
fullWidth
id="email"
label="Verification Code"
name="Verification"
autoComplete="Verification"
value={VerificationCode}
onChange={(verification) =>
setVerificationCode(verification.target.value)
}
/>
<Button
type="submit"
fullWidth
variant="contained"
sx={{ mt: 3, mb: 2 }}
>
Submit
</Button>
<Copyright sx={{ mt: 5 }} />
</Box>
</Box>
</Grid>
</Grid>
</ThemeProvider>
);
}
All the files are correctly exported from config js cause sign in with email and password and sign in with social media are working
React uses a virtual DOM so doesn't have the same access to window as writing a website with pure javascript. So accessing the window object will often cause issues.
This does seem to be a duplicate of this answer though. That might help you fix the bug
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