I have a simple routing system using hash router in my react app. I have a MainDisplay
that Links to the route /assets/id
. I also have a component called Trending
that Links to the same route /assets/id
. The link in MainDisplay
works perfectly, but the link in Trending
does not. The link for Trending
changes the URL when I click on it but doesn't send me to the proper page until I refresh.
The trending component is being rendered by another component called CryptoInfo
, which holds information based on the id
I got from MainDisplay
. Can someone help me understand why my routing isn't working in the Trending
component?
// MainDisplay.js
<Link key={index} to={`/assets/${id}`}>
<span>{name}</span>
<span>${current_price}</span>
</Link>
// CryptoInfo
import React, { useState, useEffect } from 'react'
import { useParams } from "react-router-dom"
import '../../styles/CryptoInfo.css'
import MainNav from '../nav/MainNav'
import Chart from './Chart'
import Description from './Description'
import Footer from '../main/Footer'
import Exchanges from './Exchanges'
import News from './News'
import Trending from './Trending'
export default function CryptoInfo() {
const [currentData, setCurrentData] = useState([])
const [image, setImage] = useState("")
const [currentPrice, setCurrentPrice] = useState("")
const [marketCap, setMarketCap] = useState("")
const [marketData, setMarketData] = useState([])
const [days, setDays] = useState(1)
const [usd, setUsd] = useState('')
const [currency, setCurrency] = useState('')
const { id } = useParams()
const api = `https://api.coingecko.com/api/v3/coins/${id}`
async function fetchApi() {
const data = await fetch(api)
const response = await data.json()
setCurrentData(response)
setImage(response.image.small)
setCurrentPrice(response.market_data.current_price.usd)
setMarketData(response.market_data)
setMarketCap(response.market_data.market_cap.usd)
}
useEffect(() => {
fetchApi()
}, [days])
const currentDataDisplay = (
<>
<div className='top-div'>
<div >
<span className='market-rank'>Rank: {market_cap_rank}</span>
</div>
<div className='name-div'>
<img className='coin-image' src={image} alt="" />
<span className='crypto-name'>{name}</span>
<span className="c-info-symbol">{SYMBOL}</span>
</div>
</div>
{/* <div className='todays-range'>
<div>
<progress className='progress-bar' min="0" max="0.14" value="0.1"></progress>
</div>
<div>
<span>{todayLow}</span>
<span>
-
</span>
<span> {todayHigh}</span>
</div>
</div> */}
<div className='price-div'>
<span className='current-price'>${current_price}</span>
<span className='price-change-24' style={{ color: priceChange24h > 0 ? 'green' : 'red' }} > {priceChange24h} %</span>
</div>
</>
)
return (
<>
<MainNav />
<div className="crypto-info-container">
<div className="crypto-info-top-container">
<div>
{currentDataDisplay}
</div>
<Chart />
</div>
<div className='crypto-info-bottom-container'>
<div className='des-container'>
<Description symbol={SYMBOL} />
<Exchanges />
</div>
<News />
</div>
<Trending />
</div>
<Footer />
</>
)
}
// Trending.js
import React, { useEffect, useState } from 'react'
import { Paper } from '@mui/material'
import { Link } from 'react-router-dom'
export default function Trending() {
const [trendingData, setTrendingData] = useState([])
const [trendingStats, setTrendingStats] = useState([])
const api = 'https://api.coingecko.com/api/v3/search/trending'
useEffect(() => {
fetchData()
}, [])
const fetchData = async () => {
const data = await fetch(api)
const response = await data.json()
setTrendingData(response.coins)
}
const fetchMarketData = async () => {
const data = await fetch(fetchAll)
const response = await data.json()
setTrendingStats(response)
}
let coinsArray = []
trendingData && trendingData.forEach((item) => {
const { id } = item.item
coinsArray.push(id + '%2C%20')
})
const allIds = coinsArray.join('').replace(',', '').slice(0, -6)
const fetchAll = `https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=${allIds}&order=market_cap_desc&per_page=100&page=1&sparkline=true`
useEffect(() => {
fetchMarketData()
}, [allIds, trendingStats])
const trendingDivv1 = (
trendingStats && trendingStats.slice(0, 4).map((coin, index) => {
const { name, price_change_percentage_24h, image, id, current_price } = coin
const sparkline = coin.sparkline_in_7d.price
let borderColor
let changePercent = price_change_percentage_24h.toFixed(2)
return (
<Link key={index} to={`/assets/${id}`}>
<Paper className="trending-box" elevation={3}>
<div style={{ display: 'flex' }}>
<div><img style={{ borderRadius: '50%', width: '50px' }} src={image} alt="" /></div>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span>{name}</span>
<span>${current_price}</span>
</div>
<span style={{ color: borderColor, marginLeft: 'auto' }}>{changePercent}%</span>
</div>
<div className='trending-chart'>
<Sparklines data={sparkline}>
<SparklinesLine color={borderColor} />
</Sparklines>
</div>
</Paper >
</Link>
)
})
const trendingDivv2 = (
trendingStats && trendingStats.slice(4, 8).map((coin, index) => {
const { name, price_change_percentage_24h, image, id, current_price } = coin
const sparkline = coin.sparkline_in_7d.price
let borderColor
let changePercent = price_change_percentage_24h.toFixed(2)
return (
<Link to={`/assets/${id}`}>
<Paper className="trending-box" elevation={3}>
<div style={{ display: 'flex' }}>
<div><img style={{ borderRadius: '50%', width: '50px', height: '50px' }} src={image} alt="" /></div>
<div style={{ display: 'flex', flexDirection: 'column' }}>
<span> {name}</span>
<span>${current_price}</span>
</div>
<span style={{ color: borderColor, marginLeft: 'auto' }}>{changePercent}%</span>
</div>
<div className='trending-chart'>
<Sparklines data={sparkline}>
<SparklinesLine color={borderColor} />
</Sparklines>
</div>
</Paper >
</Link >
)
})
)
return (
<>
<div className='trending-div'>
<h1 style={{ margin: '20px 0' }}>Trending Coins 🔥</h1>
<div className='trending-coins-div'>
<div className='trending-flex-box'>
{trendingDivv1}
</div>
<div className='trending-flex-box'>
{trendingDivv2}
<a href={'/'}>
<div className="trending-box trending-image" >
<h2 style={{ color: 'white', fontWeight: '700', fontSize: '1.5em', verticalAlign: 'center' }}>See More Coins</h2>
</div >
</a>
</div>
</div>
</div>
</>
)
}
//App.js
<>
<Router>
<Switch>
<Route exact path="/" >
<div>
<MainNav />
<InfoNav />
<MainDisplay />
<Footer />
</div>
</Route>
<Route path="/assets/:id">
<CryptoInfo />
</Route>
<Route path="/categories">
<CategoryList />
</Route>
</Switch>
</Router>
</>
So it turns out the link in Trending
is working. The issue is that the CryptoInfo
component doesn't respond to the id
route param updating to fetch any new data.
export default function CryptoInfo() {
...
const [days, setDays] = useState(1); // <-- (3) setDays never called, days never updates
...
const { id } = useParams(); // <-- (1) id param updates
const api = `https://api.coingecko.com/api/v3/coins/${id}`; // <-- (2) api value updated
async function fetchApi() {
...
}
useEffect(() => {
fetchApi(); // <-- (5) no refetch
}, [days]); // <-- (4) dependency doesn't update
...
return (....);
}
To resolve, move the api
and fetchApi
function into the useEffect
and use the correct dependencies, just id
in this case. You should also surround any asynchronous code in a try/catch
to handle any rejected promises or any other thrown exceptions.
Example:
function CryptoInfo() {
...
const { id } = useParams();
useEffect(() => {
const api = `https://api.coingecko.com/api/v3/coins/${id}`;
async function fetchApi() {
try {
const data = await fetch(api);
const response = await data.json();
setCurrentData(response);
setImage(response.image.small);
setCurrentPrice(response.market_data.current_price.usd);
setMarketData(response.market_data);
setMarketCap(response.market_data.market_cap.usd);
} catch (error) {
// handle error
console.log(error);
}
}
fetchApi();
}, [id]);
...
return (...);
}