Состояние из настраиваемого хука с использованием обновлений использования редуктора в родительском, но не дочернем - PullRequest
0 голосов
/ 28 марта 2020

У меня есть пользовательский хук, использующий 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. Мне действительно нужно сохранять состояние только для отдельных компонентов страницы, и это на самом деле просто локальное состояние пользовательского интерфейса, поскольку я обрабатываю большую часть данных с помощью аполлон-хуков.

1 Ответ

2 голосов
/ 28 марта 2020

Функция отправки, которую вы вызываете в дочернем компоненте, отличается от функции родительского компонента и не обновляет то же состояние. Различное использование хука useDashboard возвращает разные пары (состояние, отправка), и они не влияют друг на друга.

Если вы хотите, чтобы состояние родительского компонента могло изменяться от дочернего компонента, но вы не хотите использовать контекстный API, вам нужно передать функцию диспетчеризации родительского компонента (или обратный вызов, который его использует) дочернему компоненту в качестве опоры.

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

  return (
    <Child
      updateFoodSearch={(listItems) =>
        dispatch({ component: "foodSearchBox", listItems })
      }
    />
  );
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...