使用在父而不是子中使用 reducer 更新的自定义挂钩状态

State from custom hook using use reducer updates in parent but not child

我有一个使用 useReducer 的自定义挂钩,它控制在仪表板上呈现哪些组件。它在父组件中按预期工作,但当我在子组件中使用它时,useReducer 运行,状态已更改,但它在父组件中没有更改,因此它不会通过适当的更改重新渲染。我在我的 reducer 中使用扩展运算符 return 一个新对象。我已经尝试了几个 hacky 的东西,在钩子内有额外的 useStates 和 useEffects,但其中 none 产生了影响。我尝试了不同级别的解构,也没有效果。我可以看到状态正在改变,但在父对象中 returned 时,它似乎没有被识别为新对象。

自定义挂钩

import { useReducer } from "react"

let dashboard = {}

export const dashboardReducer = (state, action) => {
    console.log("dashboardReducer: state, action=", state, action)
    switch (action.component) {
        case 'lowerNav':
            switch (action.label) {
                case 'journal':
                return { ...state, lowerNav: 'journal'}
                case 'progress':
                return { ...state, lowerNav: 'progress'}
                case 'badges':
                return { ...state, lowerNav: 'badges'}
                case 'challenges':
                return { ...state, lowerNav: 'challenges'}
                case 'searchResults':
                return { ...state, lowerNav: 'searchResults'}
            }
        case 'foodSearchBox' :
            if (action.searchResults) {
                return { ...state, searchResults: action.searchResults, lowerNav: "searchResults"}
            } else {
                return { ...state, searchResults: "NO SEARCH RESULTS"}                
            }
        default:
            throw new Error()
    }
}

export const useDashboard = () => {
    const [ state, dispatch ] = useReducer(dashboardReducer, dashboard)
    //fires on every dispatch no matter which component
    console.log("useDashboard: state=", state)      
    return [ state, dispatch ]
}

export const initDashboard = initialState => {
    dashboard = initialState
}

父组件

const Dashboard = () => {
  initDashboard({ lowerNav: "journal", log: "daily", meal: "breakfast" });
  const [ state, dispatch ] = useDashboard();
  const lowerNav = state.lowerNav

  useEffect(() => {
    console.log("Dashboard: useEffect: state=", state) //Fires when a dispatch is run from this component, but not from any other components
  }, [state])

  const dateOptions = { year: "numeric", month: "long", day: "numeric" }; 
  const currentDate = new Date(Date.now()); 

  return (
    <Layout>
      <div className="flex">
        <DashUser />
        <div className="flex-1"></div>
        <div className="flex-1 px-32 self-center">
          <FoodSearchBox />
        </div>
      </div>
      <nav className="flex bg-mobileFoot">
        <div className="flex-1"></div>
        <ul className="flex-1 flex justify-around text-lg font-medium py-2">
          <li
            className={`${
              lowerNav === "journal" ? "border-b-2 border-pink-500" : ""
            } cursor-pointer`}
            value="journal"
            onClick={() =>
              dispatch({ component: "lowerNav", label: "journal" })
            }
          >
            Food Journal
          </li>
          <li
            className={`${
              lowerNav === "progress" ? "border-b-2 border-pink-500" : ""
            } cursor-pointer`}
            value="progress"
            onClick={() =>
              dispatch({ component: "lowerNav", label: "progress" })
            }
          >
            Progress
          </li>
          <li
            className={`${
              lowerNav === "badges" ? "border-b-2 border-pink-500" : ""
            } cursor-pointer`}
            value="badges"
            onClick={() => dispatch({ component: "lowerNav", label: "badges" })}
          >
            Badges
          </li>
          <li
            className={`${
              lowerNav === "challenges" ? "border-b-2 border-pink-500" : ""
            } cursor-pointer`}
            value="challenges"
            onClick={() =>
              dispatch({ component: "lowerNav", label: "challenges" })
            }
          >
            Challenges
          </li>
        </ul>
        <span className="flex flex-1 text-sm justify-end items-center">
          <time className="pr-32">
            {currentDate.toLocaleString("en-US", dateOptions)}
          </time>
        </span>
      </nav>
      <div className="flex py-4">
        <DailyVibe />         
        <div className="flex-1"></div>
        <div className="border border-black mr-32 ml-6">Macro Charts</div>
      </div>
      <div className="ml-20 mr-32">
        {lowerNav === "journal" ? (
          <DesktopFoodJournal />
        ) : lowerNav === "progress" ? (
          <Progress />
        ) : lowerNav === "badges" ? (
          "Badges"
        ) : lowerNav === "challenges" ? (
          "Challenges"
        ) : lowerNav === "searchResults" ? (
          <FoodSearchResults />
        ) : (
          "Error"
        )}
      </div>
    </Layout>
  );
}

export default withApollo(Dashboard)

子组件

import { useState } from "react";
import { foodDbSearch } from "../../lib/edamam.js";
import { useDashboard } from "../../lib/hooks/useDashboard.js";

export default function FoodSearchBox() {
  const [item, setItem] = useState("");
  const dispatch = useDashboard()[1];

  const handleChange = e => {
    setItem(e.target.value);
  };

  const query = item.replace(" ", "%20"); //  Format the entered food item for the API call

  const handleSubmit = async e => {
    e.preventDefault();
    const list = await foodDbSearch(query); //  Hit the foodDB API
    //  Add the search results to dashboard state 
    dispatch({ component: "foodSearchBox", searchResults: list.hints }); 
    setItem('')  //  Reset input
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        className="w-full border border-purple-400 rounded focus:border-purple-200 px-4 py-2"
        type="text"
        placeholder="Search Food Item"
        name="food"
        value={item}
        onChange={handleChange}
      />
    </form>
  );
}

我正在尝试重构最初大量混乱的 prop 钻孔,使用 useState 挂钩到处传递。我正在使用 Next.js 并且我试图避免使用 Context API 或引入 Redux。我真的只需要在单个页面组件上保留状态,它实际上只是本地 UI 状态,因为我正在使用 apollo-hooks 处理大部分数据。

您在子组件中调用的调度函数与在父组件中调用的函数不同,并且不会更新相同的状态。 useDashboard hook 的不同用法 return 不同的 (state, dispatch) 对,它们不会相互影响。

如果你希望父组件的状态可以从子组件改变,但你不想使用上下文API,你必须传递父组件的调度函数(或回调使用它)到子组件作为道具。

const Parent = () => {
  const [state, dispatch] = useDashboard();

  return (
    <Child
      updateFoodSearch={(listItems) =>
        dispatch({ component: "foodSearchBox", listItems })
      }
    />
  );
};