Search code examples
reactjsreact-hooksreact-routerreact-router-domuse-effect

React router Link changes URL, but doesn't render the component


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>
    </>

Solution

  • 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 (...);
    }
    

    Edit react-router-link-changes-url-but-doesnt-render-the-component