如何修复CryptoExchange组件的React Hooks调用顺序错误?
问题
我开发了如下的CryptoExchange组件:
import { useEffect, useState } from "react"; import { Col, Row, Space, Select } from "antd"; import styles from "./CryptoExchange.module.css"; import bitcoin from "../../../assets/Bitcoin-3.svg"; import Image from "next/image"; import { DUMMY_TOKENS } from "@/src/api/api"; import { ICrypto } from "@/src/models/tokens"; import useTranslation from "next-translate/useTranslation"; import { useQuery } from "@tanstack/react-query"; import numeral from "numeral"; import Api from "@/src/api/real-api"; const numFormat = "0,0.00"; const crypto = "crypto"; const CryptoExchange = () => { const [selectedToken, setSelectedToken] = useState<ICrypto>(DUMMY_TOKENS[0]); const [currency, setCurrency] = useState<string>("USD"); const [price, setPrice] = useState(0); const [high_24h, setHigh_24h] = useState(0); const [low_24h, setLow_24h] = useState(0); const [volume_24h, setVolume_24h] = useState(0); const [price_24h, setPrice_24h] = useState(0); const { data, isError, isLoading, error } = useQuery( [crypto], Api.fetchTokens ); useEffect(() => { if (!isLoading && !isError && data) { exchangeHandler(currency, selectedToken); } }, [currency, selectedToken, data, isError, isLoading]); const exchangeHandler = (currency: string, token: ICrypto) => { if (currency === "USD") { setPrice(+token.tokenStat.price * 1); setPrice_24h(+token.tokenStat.price_24h * 1); setHigh_24h(+token.tokenStat.high_24h * 1); setLow_24h(+token.tokenStat.low_24h * 1); setVolume_24h(+token.tokenStat.volume_24h * 1); } else if (currency === "EUR") { setPrice(+token.tokenStat.price * 0.92); setPrice_24h(+token.tokenStat.price_24h * 0.92); setHigh_24h(+token.tokenStat.high_24h * 0.92); setLow_24h(+token.tokenStat.low_24h * 0.92); setVolume_24h(+token.tokenStat.volume_24h * 0.92); } else if (currency === "RSD") { setPrice(+token.tokenStat.price * 107.66); setPrice_24h(+token.tokenStat.price_24h * 107.66); setHigh_24h(+token.tokenStat.high_24h * 107.66); setLow_24h(+token.tokenStat.low_24h * 107.66); setVolume_24h(+token.tokenStat.volume_24h * 107.66); } }; if (isLoading) return <h2>Loading...</h2>; if (isError) return ( <> <h2>Something went wrong...</h2> <p>{error.toString()}</p> </> ); const { t } = useTranslation("common"); const tokensSelectOptions = data.map((el) => { return ( <Select.Option key={el.id} value={JSON.stringify(el)} label={el.name}> {el.name} </Select.Option> ); }); const currencySelectOptions = [ <Select.Option value="USD" label={`${selectedToken.symbol}/USD`} key={`${selectedToken.symbol}/USD`} > {`${selectedToken.symbol}/USD`} </Select.Option>, <Select.Option value="EUR" label={`${selectedToken.symbol}/EUR`} key={`${selectedToken.symbol}/EUR`} > {`${selectedToken.symbol}/EUR`} </Select.Option>, <Select.Option value="RSD" label={`${selectedToken.symbol}/RSD`} key={`${selectedToken.symbol}/RSD`} > {`${selectedToken.symbol}/RSD`} </Select.Option>, ]; const handleSelectedToken = (value: any) => { const token = value && JSON.parse(value.value); setSelectedToken(token); }; return ( <div> <Row className={styles.gutterRow}> <Col md={19} sm={19} xs={15} className={styles.gutterCol}> <div className={styles.select}> <Image src={bitcoin} alt="bitcoin" className={styles.image} /> <Space wrap> <Select defaultValue={{ value: data[0].name, label: data[0].name, }} labelInValue={true} style={{ width: 100 }} onChange={(value) => handleSelectedToken(value)} > {tokensSelectOptions} </Select> <Select style={{ width: 100 }} onChange={(value) => setCurrency(value.value)} defaultValue={{ value: "USD", label: `${selectedToken.symbol}/USD`, }} labelInValue={true} > {currencySelectOptions} </Select> </Space> </div> <div> <p className={styles.orangeP}> {numeral(price).format(numFormat)} {currency} </p> </div> <div> <div className={styles.changes}> <p className={styles.orangeP}> {numeral(price_24h).format(numFormat)} </p> <p className={styles.orangeP}> {numeral(selectedToken.tokenStat.change_24h).format(numFormat)} </p> </div> <h6>24h {t("exchange.changes")}</h6> </div> <div> <p>{numeral(high_24h).format(numFormat)}</p> <h6>24h {t("exchange.high")}</h6> </div> <div> <p>{numeral(low_24h).format(numFormat)}</p> <h6>24h {t("exchange.low")} </h6> </div> <div> <p>{numeral(volume_24h).format(numFormat)}</p> <h6> 24h {t("exchange.volume")}({selectedToken.symbol}) </h6> </div> </Col> <Col md={1} sm={1} xs={2} offset={1}> <button className={styles.btn}>{t("exchange.buy")}</button> </Col> <Col md={2} sm={2} xs={5} offset={1}> <button className={styles.btn}>{t("exchange.add")}</button> </Col> </Row> </div> ); }; export default CryptoExchange;
运行时控制台出现警告:
Warning: React has detected a change in the order of Hooks called by CryptoExchange. This will lead to bugs and errors if not fixed.
我尝试调整Hooks调用顺序但未解决问题,请问该如何修复这个错误?
解决方案
这个警告的核心原因是你在组件渲染流程中提前返回JSX,导致后续的Hooks在某些渲染周期中没有被调用,违反了React Hooks的核心规则:Hooks必须在每次渲染中按完全相同的顺序执行。
具体问题点
你在useTranslation Hook调用之前,就加入了两个条件返回语句:
if (isLoading) return <h2>Loading...</h2>; if (isError) return ( <> <h2>Something went wrong...</h2> <p>{error.toString()}</p> </> );
当isLoading或isError为true时,组件会直接返回,useTranslation不会被执行;而当加载完成后,这个Hook又会被正常调用。前后两次渲染的Hooks调用顺序不一致,直接触发了警告。
修复步骤
把所有Hooks调用提前到所有条件返回语句之前,确保每次渲染都按相同顺序执行所有Hooks:
修改后的核心代码片段:
const CryptoExchange = () => { // 所有Hooks调用都放在组件最顶部 const [selectedToken, setSelectedToken] = useState<ICrypto>(DUMMY_TOKENS[0]); const [currency, setCurrency] = useState<string>("USD"); const [price, setPrice] = useState(0); const [high_24h, setHigh_24h] = useState(0); const [low_24h, setLow_24h] = useState(0); const [volume_24h, setVolume_24h] = useState(0); const [price_24h, setPrice_24h] = useState(0); const { data, isError, isLoading, error } = useQuery( [crypto], Api.fetchTokens ); // 提前调用useTranslation,放在条件判断之前 const { t } = useTranslation("common"); useEffect(() => { if (!isLoading && !isError && data) { exchangeHandler(currency, selectedToken); } }, [currency, selectedToken, data, isError, isLoading]); // exchangeHandler定义... // 条件判断放在所有Hooks之后 if (isLoading) return <h2>Loading...</h2>; if (isError) return ( <> <h2>Something went wrong...</h2> <p>{error.toString()}</p> </> ); // 剩余组件逻辑和JSX返回... };
额外优化建议
你目前在exchangeHandler中硬编码了汇率,建议把汇率数据抽成常量对象,让代码更易维护:
const EXCHANGE_RATES = { USD: 1, EUR: 0.92, RSD: 107.66 }; const exchangeHandler = (currency: string, token: ICrypto) => { const rate = EXCHANGE_RATES[currency as keyof typeof EXCHANGE_RATES] || 1; setPrice(+token.tokenStat.price * rate); setPrice_24h(+token.tokenStat.price_24h * rate); setHigh_24h(+token.tokenStat.high_24h * rate); setLow_24h(+token.tokenStat.low_24h * rate); setVolume_24h(+token.tokenStat.volume_24h * rate); };
内容的提问来源于stack exchange,提问作者SerbianDeveloper
相关产品推荐
相关产品推荐

