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

React useReducer无法获取更新后状态的技术求助

问题:React中useReducer跨组件同步状态异常

我在React里仅使用useReducer,未用到Context API,现有三个组件:App、TogglePosts、ShowPosts,还有一个store。
当前流程:

  1. TogglePosts组件更新自身reducer的toggle状态,同时调用父组件App的回调函数;
  2. App将更新后的toggle值传递给ShowPosts组件;
  3. 我知道ShowPosts无法直接获取变更后的状态,因此用useEffect钩子同步,但点击Fetch Posts按钮时,ShowPosts始终拿到初始状态(toggle值为false)。

代码示例

App组件

import { useState } from "react";
import "./App.css";
import ShowPosts from "./components/ShowPosts";
import TogglePosts from "./components/TogglePosts";

function App() {
  const [appData, setAppData] = useState({ toggle: false });

  function settingAppData(value) {
    value ? setAppData({ toggle: true }) : setAppData({ toggle: false });
  }
  return (
    <>
      <h3>Test Reducer</h3>
      <TogglePosts toggleFunc={settingAppData} />
      <ShowPosts toggle={appData.toggle} />
    </>
  );
}

export default App;

TogglePosts组件

import { useReducer, useRef } from "react";
import { initialState, reducerFunc } from "../reducers/Store";

function TogglePosts({ toggleFunc }) {
  const inputToggle = useRef();
  const [currState, dispatch] = useReducer(reducerFunc, initialState);

  function checkValue() {
    console.log("Checking Curr State value in toggle component :");
    console.log(currState);
  }
  function handleToggleOncChange() {
    const toggleValue = inputToggle.current.checked;
    if (toggleValue) {
      const data = {
        type: "TOGGLE ON",
        payload: [],
      };
      dispatch(data);
      console.log("Current state Value :");
      console.log(currState);
      toggleFunc(true);
    } else {
      const data = {
        type: "TOGGLE OFF",
        payload: [],
      };
      dispatch(data);
      console.log("Current state Value :");
      console.log(currState);
      toggleFunc(false);
    }
  }

  return (
    <>
      <input
        type="checkbox"
        className="btn-check"
        id="btn-check-2-outlined"
        ref={inputToggle}
        onChange={handleToggleOncChange}
      />
      <label
        className={`btn ${currState.toggle ? "btn-primary" : "btn-warning"}`}
        htmlFor="btn-check-2-outlined"
      >
        {currState.toggle ? "Toggle is On" : "Toggle is Off"}
      </label>
      <br></br>
      <button onClick={checkValue} type="button" className="btn btn-primary">
        Check Value
      </button>
    </>
  );
}

export default TogglePosts;

ShowPosts组件

import { useEffect, useReducer } from "react";
import { initialState, reducerFunc } from "../reducers/Store";
import Post from "./Post";

function ShowPosts({ toggle }) {
  const [currState, dispatch] = useReducer(reducerFunc, initialState);
  useEffect(() => {
    if (toggle) {
      dispatch("TOGGLE ON");
    } else {
      dispatch("TOGGLE OFF");
    }
  }, [toggle]);

  function fetchPosts() {
    console.log("Checking Curr State value in Show component :");
    console.log(currState);
    // fetch("https://dummyjson.com/posts")
    //   .then((res) => res.json())
    //   .then((obj) => dispatch({ type: "DATA Fetch", payload: obj.posts }));
  }
  return (
    <>
      <button
        type="button"
        style={{ margin: "10px" }}
        onClick={fetchPosts}
        className="btn btn-info"
      >
        Fetch Posts
      </button>
      <div className="container text-center">
        <div className="row">
          {currState.fetchData ? (
            currState.fetchData.map((post) => (
              <Post key={post.id} title={post.title} body={post.body} />
            ))
          ) : (
            <center> {"No Data to display"} </center>
          )}
        </div>
      </div>
    </>
  );
}

export default ShowPosts;

Store

export const initialState = {
  toggle: false,
  fetchData: "",
};

export const reducerFunc = (initialState, action) => {
  switch (action.type) {
    case "TOGGLE ON":
      console.log("In the on loop");
      return { ...initialState, toggle: true };
    case "TOGGLE OFF":
      console.log("In the off loop");
      return { ...initialState, toggle: false };
    case "DATA Fetch":
      return { ...initialState, fetchData: action.payload };
    default:
      return initialState;
  }
};

问题根源

  1. ShowPosts中dispatch参数格式错误:reducer期望接收包含type属性的对象,但你在useEffect里直接传递了字符串"TOGGLE ON"/"TOGGLE OFF",导致reducer进入default分支,完全没更新状态。
  2. TogglePosts存在状态冗余:App已经维护了toggle状态,TogglePosts自己再用useReducer存一份,既多余又容易导致状态不一致。
  3. dispatch异步更新的闭包陷阱:TogglePosts里调用dispatch后立刻打印currState,此时状态还没更新,因为dispatch是异步的,但这不是核心问题。

修复方案

1. 修正ShowPosts的dispatch参数格式

在useEffect里传递符合reducer要求的对象格式:

useEffect(() => {
  if (toggle) {
    dispatch({ type: "TOGGLE ON" });
  } else {
    dispatch({ type: "TOGGLE OFF" });
  }
}, [toggle]);

2. 移除TogglePosts的冗余状态

直接使用App传递的toggle状态,简化组件逻辑:

// TogglePosts组件修改后
import { useRef } from "react";

function TogglePosts({ toggle, toggleFunc }) {
  const inputToggle = useRef();

  function checkValue() {
    console.log("Checking Curr State value in toggle component :", toggle);
  }

  function handleToggleOncChange() {
    const toggleValue = inputToggle.current.checked;
    toggleFunc(toggleValue);
  }

  return (
    <>
      <input
        type="checkbox"
        className="btn-check"
        id="btn-check-2-outlined"
        ref={inputToggle}
        onChange={handleToggleOncChange}
        checked={toggle} // 同步checkbox与App状态
      />
      <label
        className={`btn ${toggle ? "btn-primary" : "btn-warning"}`}
        htmlFor="btn-check-2-outlined"
      >
        {toggle ? "Toggle is On" : "Toggle is Off"}
      </label>
      <br /><br />
      <button onClick={checkValue} type="button" className="btn btn-primary">
        Check Value
      </button>
    </>
  );
}

export default TogglePosts;

3. 简化App的状态更新逻辑

把appData拆成单独的toggle状态,让代码更直观:

// App组件修改后
import { useState } from "react";
import "./App.css";
import ShowPosts from "./components/ShowPosts";
import TogglePosts from "./components/TogglePosts";

function App() {
  const [toggle, setToggle] = useState(false);

  const settingAppData = (value) => {
    setToggle(value);
  };

  return (
    <>
      <h3>Test Reducer</h3>
      <TogglePosts toggle={toggle} toggleFunc={settingAppData} />
      <ShowPosts toggle={toggle} />
    </>
  );
}

export default App;

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 00:02:19