React hooks & Context: ошибка при использовании контекста внутри дочернего компонента с useEffect - PullRequest
0 голосов
/ 08 февраля 2020

Я создал компонент функции react для контекста следующим образом:

const ItemContext = createContext()

const ItemProvider = (props) => {
    const [item, setItem] = useState(null)

    const findById = (args = {}) => {
        fetch('http://....', { method: 'POST' })
          .then((newItem) => {
            setItem(newItem)
          })
    }

    let value = {
        actions: {
            findById
        },
        state: {
            item
        }
    }

    return <ItemContext.Provider value={value}>
        {props.children}
    </ItemContext.Provider>
}

Таким образом, у меня есть мой контекст, который обрабатывает все API вызывает и сохраняет состояние для этого item. (Аналогично redux и другим)

Затем в моем дочернем компоненте далее по строке, которая использует вышеуказанный контекст ...

const smallComponent = () =>{
    const {id } = useParams()
    const itemContext = useContext(ItemContext)

    useEffect(()=>{
        itemContext.actions.findById(id)
    },[id])

    return <div>info here</div>
}

Таким образом, компонент должен выполнить вызов API на изменение id. Но я получаю эту ошибку в консоли:

React Hook У useEffect отсутствует зависимость: 'itemContext.actions'. Либо включите его, либо удалите массив зависимых реагирующих хуков / исчерпывающего действия

Если я добавлю его в массив зависимостей, то получу бесконечный l oop вызовов API на моем сервере. Так что я не уверен, что делать. Или если я пойду на это неправильно. Спасибо.

=== ОБНОВЛЕНИЕ ==== Вот jsfiddle, чтобы попробовать его: https://jsfiddle.net/zx5t76w2/ (к вашему сведению, предупреждение не в консоли, поскольку оно не горит)

Ответы [ 2 ]

2 голосов
/ 08 февраля 2020

Вы можете просто использовать useCallback для метода выборки, который возвращает запомненную функцию:

const findById = useCallback((args = {}) => {
  fetch("http://....", { method: "POST" }).then(newItem => {
    setItem(newItem);
  });
}, []);

... и поместите его в useEffect:

...
const { actions, state } = useContext(ItemContext)

useEffect(() => {
    actions.findById(id)
}, [id, actions.findById])
...

Рабочий пример: https://jsfiddle.net/6r5jx1h7/1/

Ваша проблема связана с useEffect повторным и повторным вызовом пользовательского хука, потому что это обычная функция, которой React не является "экономия" на протяжении всего рендера.


ОБНОВЛЕНИЕ

Мой первоначальный ответ исправил бесконечность l oop. Ваша проблема была также связана с тем, как вы используете контекст, так как он снова и снова воссоздает доменные объекты вашего контекста (actions, state, ..) ( См. Предупреждения в официальной документации ).

Вот ваш пример в Кент C. замечательный способ Доддса разделить контекст на state и dispatch, который я не могу рекомендовать достаточно. Это исправит ваш бесконечный l oop и обеспечит более чистую структуру использования контекста. Обратите внимание, что я все еще использую useCallback для функции выборки, основываясь на моем исходном ответе:

Полные коды и окно https://codesandbox.io/s/fancy-sea-bw70b

Приложение. js

import React, { useEffect, useCallback } from "react";
import "./styles.css";
import { useItemState, ItemProvider, useItemDispatch } from "./item-context";

const SmallComponent = () => {
  const id = 5;
  const { username } = useItemState();
  const dispatch = useItemDispatch();

  const fetchUsername = useCallback(async () => {
    const response = await fetch(
      "https://jsonplaceholder.typicode.com/users/" + id
    );
    const user = await response.json();
    dispatch({ type: "setUsername", usernameUpdated: user.name });
  }, [dispatch]);

  useEffect(() => {
    fetchUsername();
  }, [fetchUsername]);

  return (
    <div>
      <h4>Username from fetch:</h4>
      <p>{username || "not set"}</p>
    </div>
  );
};

export default function App() {
  return (
    <div className="App">
      <ItemProvider>
        <SmallComponent />
      </ItemProvider>
    </div>
  );
}

item-context. js

import React from "react";

const ItemStateContext = React.createContext();
const ItemDispatchContext = React.createContext();

function itemReducer(state, action) {
  switch (action.type) {
    case "setUsername": {
      return { ...state, username: action.usernameUpdated };
    }
    default: {
      throw new Error(`Unhandled action type: ${action.type}`);
    }
  }
}

function ItemProvider({ children }) {
  const [state, dispatch] = React.useReducer(itemReducer, {
    username: "initial username"
  });
  return (
    <ItemStateContext.Provider value={state}>
      <ItemDispatchContext.Provider value={dispatch}>
        {children}
      </ItemDispatchContext.Provider>
    </ItemStateContext.Provider>
  );
}

function useItemState() {
  const context = React.useContext(ItemStateContext);
  if (context === undefined) {
    throw new Error("useItemState must be used within a CountProvider");
  }
  return context;
}

function useItemDispatch() {
  const context = React.useContext(ItemDispatchContext);
  if (context === undefined) {
    throw new Error("useItemDispatch must be used within a CountProvider");
  }
  return context;
}

export { ItemProvider, useItemState, useItemDispatch };

Обе эти записи блога мне очень помогли, когда я начал использовать контекст с Первоначально крючки:

https://kentcdodds.com/blog/application-state-management-with-react https://kentcdodds.com/blog/how-to-use-react-context-effectively

0 голосов
/ 08 февраля 2020

ОК, я не хотел писать ответ, так как Беннетт в основном дал вам исправление, но я думаю, что в нем отсутствует деталь в компоненте, поэтому здесь вы go:

const ItemProvider = ({ children }) => {
   const [item, setItem] = useState(null)

   const findById = useCallback((args = {}) => {
      fetch('http://....', { method: 'POST' }).then((newItem) => setItem(newItem))
   }, []);

   return (
      <ItemContext.Provider value={{ actions: { findById }, state: { item } }}>
         {children}
      </ItemContext.Provider>
   )
}

const smallComponent = () => {
   const { id } = useParams()
   const { actions } = useContext(ItemContext)

   useEffect(() => {
     itemContext.actions.findById(id)
   }, [actions.findById, id])

   return <div>info here</div>
}

Продлен от комментариев, вот рабочий JSFiddle

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...