使用在父而不是子中使用 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 })
}
/>
);
};
我有一个使用 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 })
}
/>
);
};