You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React useState状态异常求助:初始加载时tags数组为空,热更新后自动填充的问题解析

React useState not populating tags on initial load unless console.log is added

Problem Description

I'm building a React card grid component with filter functionality, where data comes from a custom AWS MySQL API. Each card object has a .tags property that's a JSON-formatted array storing associated tags. In App.jsx, I wrote logic to parse this JSON tag data, extract all unique tags, and store them in a React useState state.

Here's my code:

// App.jsx
import { useEffect, useState } from 'react';
import '../assets/css/App.css';
import Card from './Card';
import Filter from './Filter'
import {motion, AnimatePresence} from 'framer-motion'

function App() {
  const [cards, setCards] = useState([]);
  const [filter, setFilter] = useState([]);
  const [activeFilter, setActiveFilter] = useState("all");
  const [tags,setTags] = useState([]);

  useEffect(() => {
    fetchData();
  }, []);

  /*useEffect(() => {
    console.log(tags);
    console.log(activeFilter);
  }, [activeFilter,tags]); */

  const getTags = () => {
    let tags = [];
    cards.forEach((card) => {
      let obj = JSON.parse(card.tags);
      obj.forEach((tag) => {
        if (!tags.includes(tag)) {
          tags.push(tag);
        }
      });
    });
    setTags(tags);
  }

  const fetchData = async () => {
    const data = await fetch("<<api>>");
    const cards = await data.json();
    setCards(cards);
    setFilter((cards));
    getTags();
  }

  return (
    <div className="App">
      <Filter cards={cards} setFilter={setFilter} activeFilter={activeFilter} setActiveFilter={setActiveFilter} />
      <motion.div layout className="Cards">
        <AnimatePresence>
          {filter.map((card) => {
            return <Card key={card.id} card={card}/>;
          })}
        </AnimatePresence>
      </motion.div>
    </div>
  );
}

export default App;

Issue

When the app first loads, React DevTools shows the tags state is empty. But if I add a console.log(tags); line right before setTags(tags) in the getTags() function while the app is running, the tags state suddenly populates with data.

I'm confused: why does the state update seem to fail on initial render, but work when I add a console log?


Answer

Let's break down what's happening here:

1. The Root Cause: Asynchronous State Updates

React's setState (including your setCards function) is asynchronous. When you call setCards(cards) in fetchData, React doesn't immediately update the cards state in your component. Instead, it queues the state update to be processed later in the render cycle.

So when you call getTags() right after setCards(cards), the cards variable inside getTags() is still the initial empty array [] (from your useState([]) declaration). That means getTags() iterates over nothing, builds an empty tags array, and setTags(tags) keeps your state empty.

2. Why the Console Log Seems to Fix It

This is a quirk of browser developer tools, not actual "fixing" the code:

  • When you log a reference type (like an array) to the console, most browsers don't display the exact value at the time of the log. Instead, they show a live reference to the array.
  • After your initial getTags() runs (with empty cards), React eventually processes the setCards update and re-renders the component. If you're in React's Strict Mode (enabled by default in Create React App), it may also re-run your getTags() function during this re-render—this time with the updated cards data, so tags gets populated correctly.
  • The console then shows the updated array value, making it look like the console log caused the state to populate, when it's just displaying the later, correct state.

Fixes to Solve This

You have two straightforward ways to fix the initial empty tags state:

Option 1: Pass the fetched cards directly to getTags

Since you already have the raw cards data from your API response, you don't need to rely on the asynchronous component state. Modify your code to use this direct value:

const getTags = (cardData) => { // Accept the fetched cards as a parameter
  let tags = [];
  cardData.forEach((card) => {
    let obj = JSON.parse(card.tags);
    obj.forEach((tag) => {
      if (!tags.includes(tag)) {
        tags.push(tag);
      }
    });
  });
  setTags(tags);
}

const fetchData = async () => {
  const data = await fetch("<<api>>");
  const cards = await data.json();
  setCards(cards);
  setFilter(cards);
  getTags(cards); // Pass the fetched cards directly
}

Option 2: Use useEffect to trigger getTags when cards state updates

Add a separate useEffect that watches for changes to the cards state, and runs getTags once cards has data:

useEffect(() => {
  if (cards.length > 0) { // Only run if cards isn't empty
    getTags();
  }
}, [cards]); // Trigger whenever cards state changes

const fetchData = async () => {
  const data = await fetch("<<api>>");
  const cards = await data.json();
  setCards(cards);
  setFilter(cards);
  // Remove the getTags() call here
}

Either approach ensures getTags() runs only when you have valid card data to process, so your tags state will populate correctly on initial load.


内容的提问来源于stack exchange,提问作者masonjacob

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 21:22:34