I have product page with a row of shoe brand logos like Nike, Adidas, etc..and below the row are images of all our shoe inventory. When the user clicks on a brand, for example nike, the url changes to /nike and filters the images to show just nike shoes. Then if I click Adidas, url changes to /adidas and filters to show only adidas shoes. However, when I the back button on the browser, the URL will change back to /nike but the images remain adidas. I have useEffect to run on any change to the brandId which is the prop that is used to filter the shoes.
Here's my code:
import React, { useState, useEffect} from 'react'
import "../styles/ShoesPage.css"
import "../styles/Players.css"
import { Link } from 'react-router-dom'
import Axios from 'axios'
export default props => {
const brandId = props.match.params.brand
const [shoes, setShoes] = useState([])
useEffect(() =>{
Axios.get(`/shoes/brands/+${brandId}`).then(res=>
setShoes(res.data)
)
}, [brandId])
return (
<>
<div id="playercontainer">
{shoes.map((shoe, i) => (
<Link to={"/product/" + shoe.id} key={'shoe' + i}>
<div className="player">
<img className="shoeImg" src={`${shoe.pic}`} alt="" />
<div className="playerDesc">
<div className="teamName">{shoe.brand}</div>
<div className="playerName">{shoe.shoe}</div>
<div className="shoePrice">${shoe.price}</div>
</div>
</div>
</Link>
))}
</div>
</>
)
}
Related
I have two components "search" and "Maindata". I am passing the input value from the search component to maindata component where I want to replace the city attribute with the input value(location) in API. but the browser display went blank and the console give an undefined 'city' error, etc. I got stuck in this problem if anyone has a solution?
Here "search" component;
import React , {useState} from "react";
import Maindata from "./Maindata";
import "../Componentstyle/search.css";
export default function Search() {
const [location, setLocation] = useState();
<Maindata city={location}/>
return (
<div className="main">
<nav className="istclass">
<form className="form">
<div className="search">
<input
value={location}
placeholder="search city"
className="searchbox"
onChange={(e)=>setLocation(e.target.value)}
/>
<button className="nd" onClick={(e)=>setLocation(e.target.value)}>
Submit
</button>
</div>
</form>
</nav>
</div>
);
}
Here "Maindata" component;
import React, { useState, useEffect } from "react";
import "../Componentstyle/Main.css";
export default function Maindata(props) {
const [data, setData] = useState(null);
let city = console.log(props.city);
let weather = async () => {
const key = "1ab6ef20384db1d7d9d205d609f7eef0";
await fetch(
`https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${key}&units=metric&formatted=0`
)
.then((response) => response.json())
.then((actualData) => setData(actualData));
};
useEffect(() => {
weather();
}, []);
if (!data) {
return <div>Loading...</div>;
}
const link = `http://openweathermap.org/img/w/${data.weather[0].icon}.png`;
return (
<div className="maindata">
<div className="city">{data.name}</div>
<div className="temp">{data.main.temp} C</div>
<div className="icon">
<img src={link} alt="not found" />{" "}
</div>
<div className="feel">feels Like {data.main.feels_like} C</div>
<div className="wind">Wind {data.wind.speed} Km/hr</div>
<div className="cloudy">{data.weather[0].main}</div>
<div className="humidity">humidity {data.main.humidity}%</div>
<div className="sunrise">
sunrise :- {new Date(data.sys.sunrise * 1000).toUTCString()}{" "}
</div>
<div className="sunset">
sunset :- {new Date(data.sys.sunset * 1000).toUTCString()}
</div>
</div>
);
}
<Maindata city={location}/>
keep this line of code inside the return
In your example, there is no meaningful connection between the Search and Maindata components. Meaning Maindata component will not get rendered on the page because it is not in the return statement of the Search component.
The Maindata component as below, is in JSX format, when you use JSX in your code in React, under the hood, React.createElement() method is being called.
Each call to React.createElement returns an object describing what to render to that part of the page. So it makes sense to put the Maindata component in the return statement. That is responsible for rendering the HTML elements from that component when you're loading a page containing that component.
<Maindata city={location}/> // is JSX and should be in the return statement to get rendered on the page and showing the right location
I'm learning React by making a Spotify clone and for now, what I'm trying to do is to show Spotify sections such as "last played songs", "top artists" and "top songs" through a component called Body.js.
I get the data from a Spotify official API library created by jmperez in a useEffect hook in the App.js component. Once I get the data from the API, I store it in an object called initialState in a file called reducer.js.
This reducer.js file contains the initial state and the reducer function for a custom hook called useDataLayer.js which is a useContext hook that passes as value a useReducer to all the branches of my program. This way what I do is update the initialState from App.js and access this object through the useDataLayer hook in the different branches of my program (among them the Body component).
The problem I am having now is that it is not rendering the three sections mentioned before in Spotify, but only shows me one which is the "top songs". The weird thing is that for a second it does render the other components as if it was getting the data and rendering but then it updates and they disappear. Please if someone can help me with this problem and explain to me why this happens it would be great.
App.js code
import React, {useState, useEffect} from 'react';
import Login from './components/Login';
import Player from './components/Player';
import { getTokenFromResponse } from './spotify';
import './styles/App.scss';
import SpotifyWebApi from "spotify-web-api-js";
import { library } from '#fortawesome/fontawesome-svg-core';
import { fas } from '#fortawesome/free-solid-svg-icons';
import { fab } from '#fortawesome/free-brands-svg-icons';
import { useDataLayer } from './components/Hooks/useDataLayer';
library.add(fas, fab);
//spotify library instance
const spotify = new SpotifyWebApi();
function App() {
const [token, setToken] = useState(null);
const [{ user }, dispatch] = useDataLayer();
// where I get the necessary data from the api
useEffect(() => {
// function to get access token
let accessToken = getTokenFromResponse();
window.location.hash = '';
if(accessToken){
spotify.setAccessToken(accessToken);
setToken(accessToken);
//FROM HERE I GET THE DATA I NEED
// here I get the data of my user
spotify.getMe().then((data) =>{
dispatch({
type: "GET_USER",
user: date
})
});
spotify.getUserPlaylists().then((data) => {
dispatch({
type: "GET_PLAYLISTS",
playlists: dates
})
});
spotify.getMyTopTracks({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_TRACKS",
top_tracks:data,
})
});
spotify.getMyRecentlyPlayedTracks({limit: 4}).then((data) => {
dispatch({
type: "RECENTLY_PLAYED",
recently_played: date,
})
});
spotify.getMyTopArtists({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_ARTISTS",
top_artists: date,
})
});
}
}, [token])
//if the token is valid enter Player.js where Body.js is inside and if not return to the login component
return (
<div className="App">
{ token ? <Player spotify= {spotify} /> : <Login />}
</div>
);
}
export defaultApp;
Body.js code
import React from 'react'
import '../styles/Body.scss'
import { useDataLayer } from "./Hooks/useDataLayer.js";
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
function Body({ spotify }) {
//get the properties of the necessary data that I want to display in this component with useDataLayer
const [{ spotify_recommendations, recently_played, top_tracks, top_artists }, dispatch] = useDataLayer();
return (
<div className= "main-body">
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the track
recently_played?.items.map((item, index) =>{
return (
<div className= "track" key= {index}>
<img src= {item.track.album.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{item.track.name}</h3>
<p>{item.track.artists.map(artist => artist.name).join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the track
top_tracks?.items.map((topArtist, index) => {
return (
<div className= "track" key= {index}>
<img src= {topArtist.album.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{topArtist.name}</h3>
<p>{topArtist.artists.map(artist => artist.name).join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
<div className= "body-option">
<span className= "see-more">See All</span>
<FontAwesomeIcon icon={['fas', 'arrow-right']} />
<div>
{
//to show the image and info of the artist
top_artists?.items.map((topTrack, index) => {
return (
<div className= "track" key= {index}>
<img src= {topTrack.images[1].url} alt= "recently played track"></img>
<div className= "track-data">
<h3>{topTrack.name}</h3>
<p>{topTrack.genres.join(", ")}</p>
</div>
</div>
)
})
}
</div>
</div>
</div>
)
}
export default Body
Code of my custom hook useDataLayer.js
import React, {useContext, createContext, useReducer} from 'react'
let DataContext = createContext();
export function DataLayer({reducer, initialState, children}) {
return (
<DataContext.Provider value= {useReducer(reducer, initialState)}>
{children}
</DataContext.Provider>
)
}
export let useDataLayer = () => useContext(DataContext);
SideBar.js: the component where i display the user's playlist
import React from 'react';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome';
import { useDataLayer } from './Hooks/useDataLayer.js';
import '../styles/SideBar.scss';
function SideBar({ spotify }){
const [{ playlists }, dispatch] = useDataLayer();
return (
<div className= "side-bar">
<div className= "side-bar-options">
<div className= "spotify-logo">
<FontAwesomeIcon icon={['fab', 'spotify']} size= "3x"/>
<h1 className= "spotify-title">Spotify</h1>
</div>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'home']} />
<p className= "option-title" >Inicio</p>
</a>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'search']} />
<p className= "option-title">Buscar</p>
</a>
<a className= "option" href= "./Player.js">
<FontAwesomeIcon icon={['fas', 'headphones']} />
<p className= "option-title" >Tu Biblioteca</p>
</a>
</div>
<p className= "playlist-title">PLAYLISTS</p>
<div className= "playlists">
{
playlists?.items?.map(
(list, index) =>
<p className="playlists-option"
key={index}
>
{list.name}
</p>
)
}
</div>
</div>
)
}
export default SideBar;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
Player.js
import React from 'react';
import "../styles/Player.scss";
import SideBar from "./SideBar.js";
import Body from "./Body.js";
function Player({spotify}) {
return (
<div className= "player-container">
<div className= "player_body">
<SideBar spotify= {spotify} />
<Body spotify= {spotify} />
</div>
</div>
)
}
export default Player;
spotify.js: the code where i get the token from the URL
const authEndpoint = "https://accounts.spotify.com/authorize";
const clientId = '84c134b175474ddabeef0e0b3f9cb389'
const redirectUri = 'http://localhost:3000/'
const scopes = [
"user-read-currently-playing",
"user-read-recently-played",
"user-read-playback-state",
"user-top-read",
"user-modify-playback-state",
"user-follow-modify",
"user-follow-read",
"playlist-modify-public",
"playlist-modify-private",
"playlist-read-private",
"playlist-read-collaborative"
];
//obtain acces token from url
export const getTokenFromResponse = () => {
let params = new URLSearchParams(window.location.hash.substring(1));
let token = params.get("access_token");
return token;
};
//acces url
const accessUrl = `${authEndpoint}?client_id=${clientId}&redirect_uri=${redirectUri}&scope=${scopes.join("%20")}&response_type=token&show_dialog=true`;
export default accessUrl;
Thank you very much for your time and attention.
By the look of it, your useEffect hook is being called twice in quick succession. It has the dependency of token, which starts as null for your first render. Inside the hook you then read the accessToken and set the state for it to your token. Doing this will trigger another render because the dependency changed from null to the value of your access token.
I would suggest to simply remove token from your useEffect dependency array (so that your hook acts as an "on mounted" event) and see if that gets you the desired effect. You should eventually move the getTokenFromResponse outside the hook, assuming it is not available on first render, as I'm just guessing it is immediately available.
Post comments, it might be better to separate your initializing code into a useEffect that only runs once, when the component is first mounted. This way, as I now suspect getTokenFromResponse() returns a new Object every call, you only need to call it once. Note, if it returns a Promise, this won't work, so you should verify this first before trying.
To better illustrate my point, I've put it in code with some comments:
useEffect(() => {
setToken(getTokenFromResponse());
// this only needs to happen once
window.location.hash = '';
}, []; // <- empty dependency array will only run once, on mount
useEffect(() => {
// out of the 2 times this will be called,
// it should only pass this condition on the last/2nd
if(token){
spotify.setAccessToken(token);
//FROM HERE I GET THE DATA I NEED
// here I get the data of my user
spotify.getMe().then((data) =>{
dispatch({
type: "GET_USER",
user: date
})
});
spotify.getUserPlaylists().then((data) => {
dispatch({
type: "GET_PLAYLISTS",
playlists: dates
})
});
spotify.getMyTopTracks({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_TRACKS",
top_tracks:data,
})
});
spotify.getMyRecentlyPlayedTracks({limit: 4}).then((data) => {
dispatch({
type: "RECENTLY_PLAYED",
recently_played: date,
})
});
spotify.getMyTopArtists({limit: 4}).then((data) => {
dispatch({
type: "GET_TOP_ARTISTS",
top_artists: date,
})
});
}
// this will trigger the effect any time token changes,
// which should now only be twice:
// 1st at it's default state value (null)
// 2nd after the previous useEffect sets its state
}, [token]);
Hope that helps but, if it's a promise, you'll need to handle it according (suggestion: moving the setToken into the then)
Good luck!
import React,{useState, useEffect} from 'react'
import { useParams } from 'react-router-dom'
import Home from './Home'
import './detailpage.css'
function DetailPage({name,
info,
genre,
_id,
episodeNumber,
poster}) {
const [shows, setShows]= useState([{name:'',
info:'',
airingDate:'',
_id:'',
genre:'',
episodeNumber:'',
poster:''
}])
const params= useParams();
useEffect(()=>{
fetch("/home")
.then(res => res.json())
.then(jsonRes => setShows(jsonRes))
}, [])
const b = JSON.stringify(params);
const newShows = shows.filter(a=>a._id===b)
console.log(newShows)
return (
<div>
<h2>.</h2>
<h2>.</h2>
<h2>.</h2>
<h2>{JSON.stringify(params)}</h2>
<h2>{shows.genre}</h2>
{newShows.map(a=>
<div>
<div className='container'>
<img className='showImg' src={a.poster} alt=''></img>
<h2 className='showTitle'>{a.title}</h2>
<h3>{a.genre}</h3>
<p className='showInfo'>{a.info} </p>
</div>
</div>
)}
<h2>{episodeNumber}</h2>
<h2>{shows.info}</h2>
</div>
)
}
export default DetailPage
I have tv shows on my Home page and after clicking the image I want it to load the detail page about the clicked show however I couldn't manage to do it. I tried 'filter' method in the code but it didn't work I also tried like this
const newShows = shows.filter(a=>a.genre.length>5)
it works but this is not what I want. I would be really happy if someone could've helped. Thank you so much.
If I were you, I wouldn't use this fetch, as when you click on the image from your home you already know which tv show you want to display more details about.
I would use something like useLocation from react-router-dom, and while changing pages (home -> detail page about a tv show) carry a state variable with the specific tv show details.
https://v5.reactrouter.com/web/api/Hooks/usehistory
const handleClick = (state) => {
history.push({ pathname: "/detail-page", state })
}
<YourTvShowImage onClick={() => handleClick(TvShowData)} />
Then on your detail page class you use something like
https://v5.reactrouter.com/web/api/Hooks/uselocation
const location = useLocation()
const [tvShowData, setTvShowData] = useState()
useEffect(() => {
if (location.state) {
setTvShowData(location.state)
}
}, [location])
To practice ReactJS, I'm trying to develop a small application. I am unable to solve the key-error.
What I'm trying to do:
The Pokemon can be added to a collection which is created by the user. I created 2 components for this. One component renders all the Pokemon cards and the other component is a Pokemon card.
The components are as follows -
PokemonCards.js
/** #jsx jsx */
import { jsx, css } from "#emotion/core"
import tw from "twin.macro"
import PokemonCard from "./PokemonCard"
import { useEffect } from "react"
import { useDispatch, useSelector } from "react-redux"
import { fetchPokemonNameUrl, selectorPokemon } from "./pokemonCardsSlice"
const PokemonCards = () => {
const dispatch = useDispatch()
const pokemonList = useSelector(selectorPokemon)
useEffect(() => {
dispatch(fetchPokemonNameUrl())
}, [dispatch])
return (
<div tw="p-2">
Pokemon Cards
<section tw="grid grid-cols-1 gap-2">
<ul>
{pokemonList.map(poke => (
<PokemonCard
key={`key-${poke.id}`}
pokemonId={poke.id}
pokemonName={poke.name}
pokemonType={poke.type}
pokemonHeight={poke.height}
pokemonWeight={poke.weight}
pokemonBaseExperience={poke.baseExperience}
pokemonSprite={poke.sprites}
/>
))}
</ul>
</section>
</div>
)
}
export default PokemonCards
PokemonCard.js
/** #jsx jsx */
import { jsx, css } from "#emotion/core"
import tw from "twin.macro"
import { useState } from "react"
import { useSelector, useDispatch } from "react-redux"
import { add, selectorCollection } from "../home/collectionSlice"
const PokemonCard = props => {
const collection = useSelector(selectorCollection)
const dispatch = useDispatch()
const [collectionInput, setCollectionInput] = useState("")
const addPokemonToList = e => {
e.preventDefault()
console.log(collectionInput)
}
const {
pokemonId,
pokemonName,
pokemonType,
pokemonHeight,
pokemonWeight,
pokemonBaseExperience,
pokemonSprite,
} = props
return (
<div tw="flex flex-row justify-around items-center bg-red-500 p-2 my-2 rounded">
<div tw="">
<img tw="bg-cover bg-center" alt={pokemonName} src={pokemonSprite} />
</div>
<div tw="mx-1">
<p>{pokemonName}</p>
<p>{pokemonType}</p>
<p>{pokemonHeight}</p>
<p>{pokemonWeight}</p>
<p>{pokemonBaseExperience}</p>
</div>
<div tw="mx-1">
<form onSubmit={addPokemonToList}>
<label>
Add Pokemon to collection <br />
<select
value={collectionInput}
onChange={e => setCollectionInput(e.target.value)}
>
{collection.map(col => (
<option key={`${pokemonId}-${col.name}`} value={col.name}>
{col.name}
</option>
))}
</select>
</label>
<button type="submit" tw="bg-gray-300 p-1 rounded">
add
</button>
</form>
</div>
</div>
)
}
export default PokemonCard
Here's what my state looks like:
I've put unique keys for both:
Pokemon cards
<option> in the <select> input which is present inside each card.
If I comment out the <form> in PokemonCard.js, the warning goes away. Which means, the error lies in the creation of <options> for the <select> input using map().
Here's the Github repo.
I checked your Github repo. Here's the problem
export const pokemonCardsSlice = createSlice({
name: "pokemonCards",
initialState: initialState,
reducers: {
getData: (state, action) => {
state.pokemonList.push(action.payload)
},
},
})
Here in reducer you push to the store. That's okay but you fetch data in a child component call PokemonCards.js. So every time this component re-render, your dispatcher called with the action and push pokemonList to the end of the store.
To solve the issue, you can either fetch pokemon list in App component, or you can filter payload in reducer and only push if it is not in the store
In react, key should be unique. So you can add an index with it to make it unique.
{
collection.map((col,index) => (
<option key={`${pokemonId}-${col.name}-${index}`} value={col.name}>
{col.name}
</option>
))
}
If you have a key id in collection, then you can use it as a key of your options:
{collection.map(col => (
<option key={`${col.id}-${col.name}`} value={col.name}>
{col.name}
</option>
))}
For a better way, use destructuring
{collection.map(({id, name}) => (
<option key={`${id}-${name}`} value={name}>
{col.name}
</option>
))}
I'm facing a weird problem here I've created a check whether an image is uploaded or not. But for some reason, my "else" is not working.
Inside my main component "Fragrances" I'm looping through my API. And checking if the Array of images is empty return else show the image.
What am I doing wrong?
My code:
image component:
import React from 'react';
import NoPicture from 'components/NoPicture/NoPicture';
const Image = props => {
const { url } = props;
return url.length > 0 ? <img src={url} className="image" /> : <NoPicture />;
};
export default Image;
NoPicture component:
import React from 'react';
// No Picture
import NoPhotoImageURL from '../../images/no-photo.svg';
console.log(NoPhotoImageURL);
const NoPicture = () => (
<img
src={NoPhotoImageURL}
alt="No Photo"
className="image image--default"
/>
);
export default NoPicture;
Main component:
import React from 'react';
import { SITE_URL } from 'constants/import';
import Name from './name';
import Category from './category';
import Image from './image';
import Gallery from 'components/Gallery/gallery';
import Rating from './rating';
import Description from './description';
import Notes from './notes';
const Fragrances = props => {
const { FragranceData } = props;
return (
<section className="fragrances">
<div className="row">
{FragranceData.map(fragrance => {
console.log(fragrance);
const {
category,
description,
image,
gallery,
name,
rating,
notes,
Publish: isPublished,
} = fragrance;
const imageURL = image.path;
return isPublished ? (
<div key={fragrance._id} className="col-xs-12 col-sm-6">
<div className="fragrance">
<Name name={name} />
<Category category={category} />
<Image url={SITE_URL + imageURL} />
<Rating rating={rating} />
<Description description={description} />
<Notes notes={notes} />
<Gallery imageData={gallery} />
</div>
</div>
) : (
'Nothing published yet!'
);
})}
</div>
</section>
);
};
export default Fragrances;
Your question is not entirely clear on what exactly you are experiencing, but here is the most obvious problem in your code. You have this line in your Image component:
return url.length > 0 ? <img src={url} className="image" /> : <NoPicture />;
However, in your main component you are passing a concatenated string to the Image component:
<Image url={SITE_URL + imageURL} />
According to your comment, SITE_URL is the full URL, which will never be empty, so inside your Image component, url will always contain something, no matter what the value of imageURL is in the main component. Thus, url.length will always be greater than 0, and the img tag will render every time.
You will either need to pass the individual parts of the path down to the Image component separately, or move your check up into the main component.