ダッシュボードにレンダリングされるコンポーネントを制御する useReducer を使用するカスタム フックがあります。親では期待どおりに動作しますが、子で使用すると useReducer が実行され、状態が変更されますが、親コンポーネントでは変更されないため、適切な変更で再レンダリングされません。レデューサーでスプレッド演算子を使用して、新しいオブジェクトを返しています。フック内に追加の useStates と useEffects を使用していくつかのハッキーなことを試しましたが、どれも影響がありませんでした。さまざまなレベルの破壊を試みましたが、効果もありません。状態が変化しているのがわかりますが、親オブジェクト内で返されたときに新しいオブジェクトとして認識されていないようです。
カスタムフック
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>
);
}
私は元々、useState フックがいたるところに渡されている厄介なプロップドリルがたくさんあったものからリファクタリングしようとしています。私は Next.js を使用していますが、Context API の使用や Redux の導入を避けようとしています。個々のページ コンポーネントの状態を永続化する必要があるだけであり、ほとんどのデータをアポロ フックで処理しているため、それは実際には単なるローカル UI 状態です。