Behnam
Behnam

Reputation: 420

React Router page address change but doesn't refresh

I'm working on a Cryptocurrency project. When I'm on the home page and search for an item in the search box and click on the item I go to the second page and everything is ok. But on the second page When I search for an item and click on it URL changed but the page doesn't change and the data on-page is the same as before. I made a gif image you can understand better.

Github Source

enter image description here

this issue is related to two components searchbarLayout(search box) and coinsLayout(second page as I mentioned above) I added both components code.

I'm using <Link /> for passing id and going to the second page.

<Link to={`coins/${name}`} state={{ id }}>
    {name}
</Link>

And I'm using useLocation() for getting Id and useParams() for getting the name of the coin.

  const location = useLocation();
  const { name } = useParams();
  const [id, setId] = useState(location.state?.id);

import React, { useState, useEffect } from "react";
import { Link } from "react-router-dom";
import { Avatar } from "antd";

import { AutoComplete } from "antd";
import { getRequest } from "utils/api/index";
import { RoundSearchBar } from "./styles.js";
import "./styles.css";
const renderTitle = (title) => <span>{title}</span>;
const renderItem = (name, id, icon) => ({
  value: name,
  label: (
    <div
      style={{
        display: "flex",
        justifyContent: "space-between",
      }}
    >
      <span>
        <Link to={`coins/${name}`} state={{ id }}>
          {name}
          {console.log("search id: ", id, "name coin : ", name)}
        </Link>
      </span>
      <span>
        <Avatar src={icon} />
      </span>
    </div>
  ),
});

export const Complete = () => {
  const [querySuggest, setQuerySuggest] = useState("");
  const [coinSearched, setCoinSearched] = useState([]);

  const handleSearch = (value) => {
    setQuerySuggest(value);
  };

  useEffect(() => {
    const getCoins = async () => {
      await getRequest(`v2/search-suggestions?query=${querySuggest}`)
        .then((response) => {
          console.log(querySuggest);

          const { coins } = response.data.data;
          const selectedCoins = coins.map((c) => {
            return {
              id: c.uuid,
              icon: c.iconUrl,
              name: c.name,
            };
          });
          setCoinSearched(selectedCoins);
        })
        .catch((error) => console.log(error));
    };
    getCoins();
  }, [querySuggest]);
  const options = [
    {
      label: renderTitle("Coins"),
      // options: [renderItem("AntDesign"), renderItem("AntDesign UI")],
      options: coinSearched.map((coin) => {
        return renderItem(coin.name, coin.id, coin.icon);
      }),
    },
  ];

  return (
    <AutoComplete
      dropdownClassName="certain-category-search-dropdown"
      dropdownMatchSelectWidth={500}
      style={{ width: 250 }}
      options={options}
      onChange={handleSearch}
    >
      <RoundSearchBar size="large" placeholder="Search..." />
    </AutoComplete>
  );
};

export default Complete;

import React, { useState, useEffect } from "react";
import { useLocation, useParams } from "react-router-dom";

import { Skeleton, Card, Avatar, Row, Col, Divider, Radio, Button } from "antd";
import { Line } from "@ant-design/plots";
import { getRequest } from "utils/api";
import {
  justUnixTimeStampConvert,
  convertTimeStampToDate,
} from "utils/convertTimestampToDate";
import "./styles.css";
import commaSeparator from "utils/table/commaSeparator";
import NotFound from "screens/errors";
const { Meta } = Card;

export const CoinsLayout = () => {
  const [coinData, setCoinData] = useState({});
  const [totalData, setTotalData] = useState("");
  const [circulatingData, setCirculatingData] = useState("");
  const [chartData, setChartData] = useState([]);
  const [timePeriodHistory, setTimePeriodHistory] = useState("1y");
  const [coinFounded, setCoinFounded] = useState(true);
  const location = useLocation();
  const { name } = useParams();
  const [id, setId] = useState(location.state?.id);
  const [loading, setLoading] = useState(true);
  const handleTimePeriodHistory = (time) => {
    setTimePeriodHistory(time);
  };

  const getCoin = async () => {
    getRequest(`v2/coin/${id}?referenceCurrencyUuid=yhjMzLPhuIDl`)
      .then((response) => {
        const { coin } = response.data.data;
        setTotalData(coin.supply.total);
        setCirculatingData(coin.supply.circulating);
        setCoinData(coin);
        setLoading(false);
      })
      .catch((error) => {
        console.log("error", error);
      });
  };
  const getCoins = async () => {
    getRequest("v2/coins")
      .then((response) => {
        const { coins } = response.data.data;
        console.log(coins);
        const coinFound = coins.find((coin) => coin.name === name);
        if (!coinFound) {
          setCoinFounded(false);
          setLoading(false);
        } else {
          setId(coinFound.uuid);
        }
        setLoading(false);
      })
      .catch((error) => {
        console.log(error);
      });
  };

  useEffect(() => {
    if ((typeof id !== undefined) & (id !== null) & (id !== "")) {
      setCoinFounded(true);
      getCoin();
    } else {
      getCoins();
    }
  }, [id]);

  useEffect(() => {
    if ((typeof id !== "undefined") & (id !== null) & (id !== "")) {
      setCoinFounded(true);
      const getHistoryPrice = async () => {
        await getRequest(
          `v2/coin/${id}/history?timePeriod=${timePeriodHistory}`
        )
          .then((response) => {
            const { data } = response.data;
            const result = convertTimeStampToDate(data.history);
            setChartData(result);
          })
          .catch((error) => console.log(error));
      };
      getHistoryPrice();
    } else {
      getCoins();
    }
  }, [timePeriodHistory, id]);

  const config = {
    data: chartData,
    padding: "auto",
    xField: "date",
    yField: "price",
    xAxis: {
      tickCount: 5,
    },
    smooth: true,
  };
  if (coinFounded === false) {
    console.log("not found coind");
    return <NotFound />;
  } else {
    return (
      <div className="site-card-wrapper">
        <br />
        <br />
        <br />
        <br />
        <br />
        <Row key={id}>
          <Col xs={{ span: 5, offset: 1 }} lg={{ span: 6, offset: 2 }}>
            <Skeleton loading={loading} avatar active>
              <Card
                style={{
                  margin: "5px",
                  borderRadius: "10px",
                  overflow: "hidden",
                }}
                title="Price Information"
                bordered={true}
              >
                <Meta
                  title={coinData.name + ` Price (${coinData.symbol})`}
                  avatar={<Avatar src={coinData.iconUrl} />}
                  description={<h2>{commaSeparator(coinData.price)}</h2>}
                ></Meta>
              </Card>
            </Skeleton>
          </Col>
          <Col xs={{ span: 5, offset: 1 }} lg={{ span: 6 }}>
            <Skeleton loading={loading} avatar active>
              <Card
                style={{
                  margin: "5px",
                  borderRadius: "10px",
                  overflow: "hidden",
                }}
                title="Market Cap & Ciruculating Supply"
                bordered={true}
              >
                <Row>
                  <Col span={8}>
                    <h3>Market Cap</h3>
                    <h3>{commaSeparator(coinData.marketCap)} </h3>
                  </Col>
                  <Col span={8} offset={4}>
                    <h3>Circulating Supply</h3>
                    <h3>{commaSeparator(circulatingData)} </h3>
                  </Col>
                </Row>
              </Card>
            </Skeleton>
          </Col>
          <Col xs={{ span: 5, offset: 1 }} lg={{ span: 6 }}>
            <Skeleton loading={loading} avatar active>
              <Card
                style={{
                  margin: "5px",
                  borderRadius: "10px",
                  overflow: "hidden",
                }}
                title="Volume & Total Supply"
                bordered={true}
              >
                <Row>
                  <Col span={8}>
                    <h3>Volume 24h</h3>
                    <h3>{commaSeparator(coinData["24hVolume"])} </h3>
                  </Col>
                  <Col span={8} offset={4}>
                    <h3>Total Supply</h3>
                    <h3>{commaSeparator(totalData)} </h3>
                  </Col>
                </Row>
              </Card>
            </Skeleton>
          </Col>
        </Row>
        <br />
        <br />
        <br />
        <Divider orientation="center">
          <h2>Charts Data</h2>
        </Divider>
        <Row>
          <Col span={6} offset={2}>
            <div>
              <h1>{coinData.name + ` Price (USD)`}</h1>
            </div>
          </Col>
          <Col span={6} offset={5}>
            <Radio.Group defaultValue="1y" buttonStyle="solid">
              <Radio.Button
                onClick={() => handleTimePeriodHistory("24h")}
                value="24h"
              >
                24h
              </Radio.Button>
              <Radio.Button
                onClick={() => handleTimePeriodHistory("7d")}
                value="7d"
              >
                7d
              </Radio.Button>
              <Radio.Button
                onClick={() => handleTimePeriodHistory("30d")}
                value="30d"
              >
                30d
              </Radio.Button>
              <Radio.Button
                onClick={() => handleTimePeriodHistory("1y")}
                value="1y"
              >
                1y
              </Radio.Button>
            </Radio.Group>
          </Col>
        </Row>
        <Row>
          <Col span={16}>
            <Card
              style={{
                marginTop: "15px",
                marginLeft: "100px",
                borderRadius: "10px",
                overflow: "hidden",
                marginBottom: "40px",
              }}
              span={8}
              offset={2}
            >
              <Line {...config} />
            </Card>
          </Col>
          <Col span={6}>
            <Card
              style={{
                margin: "5px",
                borderRadius: "10px",
                overflow: "hidden",
                marginTop: "15px",
              }}
              title="Coin Info"
              bordered={true}
            >
              <div>
                <Button type="primary" size="large">
                  WebSite
                </Button>
                <Button
                  style={{ marginLeft: "10px", fontWeight: "bold" }}
                  orientation="center"
                  type="primary"
                  ghost
                >
                  {coinData.websiteUrl}
                </Button>
              </div>
              <Divider></Divider>

              <div>
                <Button type="primary" size="large">
                  {coinData.name} Rank
                </Button>

                <Button
                  style={{ marginLeft: "10px", fontWeight: "bold" }}
                  orientation="center"
                  type="primary"
                  ghost
                >
                  {coinData.rank}
                </Button>
              </div>

              <Divider></Divider>
              <div>
                <div>
                  <Button type="primary" size="large">
                    AllTimeHigh
                  </Button>

                  <Button
                    style={{ marginLeft: "10px", fontWeight: "bold" }}
                    orientation="center"
                    type="primary"
                    ghost
                  >
                    {commaSeparator(coinData.allTimeHigh?.price)}
                  </Button>
                </div>
                <br />
                <div>
                  <Button type="primary" size="large">
                    Date
                  </Button>

                  <Button
                    style={{ marginLeft: "10px", fontWeight: "bold" }}
                    type="primary"
                    danger
                    ghost
                  >
                    {justUnixTimeStampConvert(coinData.allTimeHigh?.timestamp)}
                  </Button>
                </div>
              </div>
              <Divider></Divider>
            </Card>
          </Col>
        </Row>
      </div>
    );
  }
};

export default CoinsLayout;

Upvotes: 0

Views: 1678

Answers (1)

Szaman
Szaman

Reputation: 2388

Your component is not re-rendering when the query props are changing. Here's what's happening:

  1. On initial render, you provide the default value of the id state as location.state?.id. This gets the correct coin id.
  2. id state is changed, so the useEffect hook is triggered which calls the getCoin() method.
  3. on subsequent re-renders, the location is modified, but nothing happens to id.
  4. the useEffect hook is thus not triggered again and getCoin() doesn't get called.

You need to modify the useEffect hook to couple it with the location params instead. Then you can set the new id state and update the data. Here's one way to do it:

useEffect(() => {
  const newId = location.state?.id;
  if (!newId) {
    getCoins();
  }
  setId(newId);
  getCoin();
}, [location]);

PS. a few bugs spotted:

  • & is not a logical operator, you should be using && instead.
  • coinFoundedcoinFound

PPS. You're using async functions but within them you're using .then callbacks. You can remove the async keyword, or keep it and rewrite the calls using await.

Upvotes: 2

Related Questions