iT邦幫忙

0

關於React hook useEffect and useState 的應用問題

各位大大您好!最近遇到一個棘手的問題想向各位請教~

當我進去某個指定人物的頁面後,會利用useEffect來執行第一次的render,從資料庫抓取該角色的詳細資料,並將其存入到useState的array中,並將其資料render至該頁面中,但遇到了一個問題,進去頁面時會質性第一次render,在執行useEffect(也就是抓取該人物的詳細資料),但第一次render時,還未抓取到該人物的資料,造成undefined,想請問該怎麼辦QQ

ChampionDetails.js :

const ChampionsDetail = () => {
  const location = useLocation();
  const history = useHistory();
  const { name, id } = useParams();  // name:Iron man ; id:1
  const [champDetails, setChampDetails] = useState({ clothes: {}, skills: {}});
  const championInfo = location.state;

  /* 畫面初始值 */
  useEffect(() => {
    fetchChampionDetail();
    document.title = `${name}'s Detail`;
  },[]);

  /* function fetchDetail 抓取該人物的詳細資料 */
  const fetchChampionDetail = async () => {
    const result = await Axios.get(
      `http://localhost:3001/champions_detail/id=${id}&${name}`
    );
    Axios.all([result])
      .then(
        Axios.spread((result) => {
          setChampDetails({
            clothes: result.data[0],
            skills: result.data[1]
          });
        })
    )
      .catch((errors) => {
        console.error(errors);
      });    
  };


  /* function handleBackClick */
  const handleBackClick = () => {
    history.goBack();
  };

  return (
    <div>
      <h1>
        Champion {name}'s Detail Pages
      </h1>
      <button
        onClick={() => {
          handleBackClick();
        }}
      >Back to the Champion Page</button>
          <p>{champDetials.clothes[0].clothes_name}</p>
    </div>
  );
}
export default ChampionsDetail;

{champDetials.clothes[0].clothes_name}在這裡出錯了
先謝謝各位的幫忙!!,仍在學習中,任何指教都願意聆聽~

圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 個回答

0
Andy Chang
iT邦研究生 4 級 ‧ 2021-04-11 14:27:24
最佳解答
const ChampionsDetail = () => {
  const location = useLocation();
  const history = useHistory();
  const { name, id } = useParams();
  
  // 在獲得後端資料前,先將state值設定為一個可以讓你辨識「還沒獲得後端資料」的值
  const [champDetails, setChampDetails] = useState(undefined);
  
 
  const championInfo = location.state;

  useEffect(() => {
    fetchChampionDetail();
    document.title = `${name}'s Detail`;
  },[]);

  const fetchChampionDetail = async () => {
    const result = await Axios.get(
      `http://localhost:3001/champions_detail/id=${id}&${name}`
    );
    Axios.all([result])
      .then(
        Axios.spread((result) => {
          setChampDetails({
            clothes: result.data[0],
            skills: result.data[1]
          });
        })
    )
      .catch((errors) => {
        console.error(errors);
      });    
  };

  const handleBackClick = () => {
    history.goBack();
  };
  
  /* 
  在JS中,「 A && B 」 代表的是當A為true,則回傳B
  下面<p></p>中的JS寫法你也可以寫成:
  if(champDetials !== undefined)
      return champDetials.clothes[0].clothes_name;
  */
  
  return (
    <div>
      <h1>
        Champion {name}'s Detail Pages
      </h1>
      <button
        onClick={() => {
          handleBackClick();
        }}
      >Back to the Champion Page</button>
          <p>{champDetials && champDetials.clothes[0].clothes_name}</p>
    </div>
  );
}
export default ChampionsDetail;

看更多先前的回應...收起先前的回應...
HaoChen iT邦新手 4 級 ‧ 2021-04-11 14:34:53 檢舉

喔喔!!所以useState是可以先定義為 undefined!!,我在提問前查了網路上很多資料,有些是說可以再用一個useEffect去來看是否更新值,不知道有沒有這種寫法QAQ 另外您的方法,成功讓我render成功了!!!我等等再run一次您跟我說的概念~~謝謝您!

Andy Chang iT邦研究生 4 級 ‧ 2021-04-11 14:36:10 檢舉

對了,你detail拼錯了XD

HaoChen iT邦新手 4 級 ‧ 2021-04-11 14:36:34 檢舉

因為不只有一個值要render,所以如果要的話 是全部都要用您說的A && B 才能在整個頁面使用嗎QAQ

HaoChen iT邦新手 4 級 ‧ 2021-04-11 14:37:17 檢舉

有我看到了哈哈 我剛剛測試發現了~~嘿嘿

Andy Chang iT邦研究生 4 級 ‧ 2021-04-11 14:38:33 檢舉

看你的資料是不是從後端拿的啊~ 一般而言,我們會把需要同一份資料的頁面拆出來成一個component,這樣你只要寫一次下面的code就能解決所有問題囉

資料 && <頁面元件 />
HaoChen iT邦新手 4 級 ‧ 2021-04-11 14:43:21 檢舉

那這樣像您說的 我把它拆成Detail.js 我要把render的畫面放進去是嗎? 不然我不太懂您說的這個方法QQ

Andy Chang iT邦研究生 4 級 ‧ 2021-04-11 14:49:14 檢舉

類似這樣的概念

  return (
    <div>
      <h1>
        Champion {name}'s Detail Pages
      </h1>
      <button
        onClick={() => {
          handleBackClick();
        }}
      >Back to the Champion Page</button>
      {champDetails && <Detail data={champDetails}>}
    </div>
  );
}
Andy Chang iT邦研究生 4 級 ‧ 2021-04-11 14:49:22 檢舉

另外有一種狀況是當你需要call多次api向後端拿不同資料才能呈現單一頁面,這時會比較複雜,我所知道的有兩種做法:

  1. 利用Promise.all()等每隻api都傳回來再初始化,但這個比較複雜也比較容易出錯
  2. 用一個state去做類似作業系統中Semaphore的概念,在每次call api時state++,資料回來後state--,當state等於0的時候就代表所有的request都結束了
HaoChen iT邦新手 4 級 ‧ 2021-04-11 14:59:19 檢舉

那像這樣呢
ChampionDetail.js

 return (
    <div className="detailContainer">
      {champDetails && <Detail data={champDetails}>}
    </div>
  );
}

Detail.js: (ChampionDetails.js會傳送一個props(champDetails)過來)

const Detail = ({champDetails}) => {
return (
    <div>
    <h1>{champDetails.clothes[0].clothes_name}</h1>
    <p>{champDetails.skills[0].skill_name}</p>
    </div>
)
}

這樣對嗎~~?

Andy Chang iT邦研究生 4 級 ‧ 2021-04-11 15:01:03 檢舉

理論上是對的,就看執行結果符不符合你的期望

HaoChen iT邦新手 4 級 ‧ 2021-04-11 15:02:11 檢舉

Andy Chang
下面那個方法,未來我會試做看看!! 另外題外話,想問有方法將資料的資料做成API嗎XD 現在剛接觸這個領域中~抱歉問題很多

HaoChen iT邦新手 4 級 ‧ 2021-04-11 15:02:52 檢舉

好了解了 我試做看看!!! 總之謝謝您!!!

Andy Chang iT邦研究生 4 級 ‧ 2021-04-11 15:05:02 檢舉

將資料的資料做成API

不懂, 你可能要再描述清楚一點

HaoChen iT邦新手 4 級 ‧ 2021-04-11 15:07:45 檢舉

應該說如何做屬於自己網頁的API,畢竟我的資料現在只存在我的MYSQL中,未來若有相關網頁需要,就可以直接抓API了,而不是像現在去跟資料庫撈哈哈

Andy Chang iT邦研究生 4 級 ‧ 2021-04-11 15:15:02 檢舉
  1. 動態資料一定會放在某個地方的資料庫,除非你是要做像購物車那種東西,那你需要的是local storage。
  2. 如果你是要一個寫前端時能夠模擬後端給資料的server,你需要的是mock server。
  3. 如果你需要的是一個遠端的簡易型資料庫,你需要的是Firebase。
HaoChen iT邦新手 4 級 ‧ 2021-04-11 15:30:39 檢舉

Andy Chang了解了 感謝您的解釋~~非常有幫助,Firebase我有聽過,未來我會研究看看的 謝謝~

我要發表回答

立即登入回答