Использование определенных реквизитов для запуска ловушки React useEffect (), но обращение к другим в эффекте - PullRequest
1 голос
/ 08 июля 2019

У меня есть сценарий использования React, когда компонент получает пропущенную category опору, а также имеет локальное значение состояния limit.Мое желаемое поведение:

  1. Когда выбирается category, limit сбрасывается на 10, если в настоящее время оно превышает 10.

  2. Когда category очищается, limit сбрасывается до 50, если в настоящее время он ниже 50.

  3. Пока category не изменяется, пользователь может выбрать любойlimit они хотят.

Я установил это поведение в ловушке эффектов, как показано ниже (более полный пример):

  useEffect(() => {
    if (category && limit > 10) {
      setLimit(10);
    } else if (!category && limit < 50) {
      setLimit(50);
    }
  }, [category]);

Это работает нормально,но идет вразрез с правилом реакции-ловушки / исчерпывающего-deps linter , которое говорит, что я должен включить limit в массив зависимостей.Я на самом деле не хочу этого, потому что я не хочу, чтобы инициированное пользователем изменение limit могло что-либо инициировать, и я хочу, чтобы пользователь мог превышать верхний / нижний пороги, пока категория не меняется.

Есть ли "правильный" способ сделать это, который соблюдает правила хуков, но не вводит еще несколько хуков, чтобы манипулировать всеми возможными переходами реквизита / состояния?

Подробнееполный пример:

function App() {
  const [category, setCategory] = useState(true);
  return (
    <div className="App">
      <button onClick={() => setCategory(!category)}>Toggle category</button>
      <List category={category} />
    </div>
  );
}

function List({ category }) {
  const [limit, setLimit] = useState(10);

  // Change the limit when category changes
  // BUT only if limit is above/below a threshold!
  useEffect(() => {
    if (category && limit > 10) {
      setLimit(10);
    } else if (!category && limit < 50) {
      setLimit(50);
    }
  }, [category]);

  return (
    <div>
      <select value={limit} onChange={e => setLimit(e.target.value)}>
        {[5, 10, 25, 50, 100].map(d => (
          <option key={d}>{d}</option>
        ))}
      </select>
      <div>Category is {category ? "on" : "off"}</div>
      <div>Would show {limit} items</div>
    </div>
  );
}

Ответы [ 3 ]

1 голос
/ 08 июля 2019

Как насчет использования ref для отслеживания предыдущей категории? Они эквивалентны переменным экземпляра в компонентах функции.

Таким образом, вы можете проводить сравнение и запускать код эффекта, только если категория изменилась, например,

const prevCat = useRef(null);
useEffect(() => {
  if (prevCat.current === category) return;

  prevCat.current = category;
  ...
}, [category, limit]);
0 голосов
/ 08 июля 2019

Вы можете использовать функциональную версию сеттера:

useEffect(() => {
    setLimit((currentLimit) => {
        if (category && currentLimit > 10) {
          return 10;
        } else if (!category && currentLimit < 50) {
          return 50;
        } else return currentLimit;
    });
  }, [category]);
0 голосов
/ 08 июля 2019

Переключитесь на useReducer() для управления limit вместе с category и передачи обоих компонентов в компонент вместо использования локального состояния.Таким образом, компонент будет отображаться только при изменении значения (или обоих).

const initialState = {
    category: true,
    limit: 10
};

function reducer(state, action) {
    switch (action.type) {
    case 'toggleCategory':
        if (state.category) {
            return {category: false, limit: Math.min(50, state.limit)};
        }
        else {
            return {category: true, limit: Math.max(10, state.limit)};
        }
    case 'setLimit':
        return {...state, limit: action.limit};
    default:
        throw new Error();
    }
}

function App() {
    const [state, dispatch] = useReducer(reducer, initialState);
    // React guarantees that dispatch function identity is stable and won’t change on re-renders.
    // This is why it’s safe to omit from the useEffect or useCallback dependency list.
    const toggleCategory = useCallback(() => dispatch({type: 'toggleCategory'}));
    const setLimit = useCallback(limit => dispatch({type: 'setLimit', limit}));
    return (
        <div className="App">
            <button onClick={toggleCategory}>Toggle category</button>
            <List {...state} setLimit={setLimit} />
        </div>
    );
}

function List({ category, limit, setLimit }) {
    return (
        <div>
            <select value={limit} onChange={e => setLimit(e.target.value)}>
                {[5, 10, 25, 50, 100].map(d => (
                    <option key={d}>{d}</option>
                ))}
            </select>
            <div>Category is {category ? "on" : "off"}</div>
            <div>Would show {limit} items</div>
        </div>
    );
}
...