Не получает обновленного значения хуков внутри функции

#reactjs #react-native #react-hooks #setstate

#reactjs #реагирующий-собственный #реагирующие перехваты #setstate

Вопрос:

Не получает обновленное значение состояния в функции, которое запускается EventListener. Даже пробовал с фоновым таймером. В чем может быть проблема?

 displayIncomingCall() // this function gets called from UI.
  
 const callApp = () => {
  const [calls, setCalls] = useState({});

  const addCall = (key, value) => {
    setCalls({ ...calls, [key]: value });
  };

  const displayIncomingCall = number => {
    const callUUID = getCurrentCallId();
    addCall(callUUID, number);
    ...
  };

  const onAnswerCall = () => {
    console.log('=== onAnswerCall ::: ===', calls); <--- always initial value. Not getting updated one.
  }

  useEffect(() => {
    console.log('=== useEffect ::: ===', calls); // getting updated value
  }, [calls]);

  useEffect(() => {
    RNCallKeep.addEventListener('answerCall', onAnswerCall);
    return () => {
      RNCallKeep.removeEventListener('answerCall', onAnswerCall);
    };
  }, []);

  console.log('=== parent ::: ===', calls); // getting updated value

  return (...)
}
  

Комментарии:

1. Это потому, что вы только addEventListener один раз, когда компонент впервые смонтирован, и не обновляете его при повторной визуализации компонента. Оно всегда закрывается над начальным значением calls .

2. Пожалуйста, попробуйте добавить возврат в setState

Ответ №1:

onAnswerCall ссылается только в useEffect обратном вызове, а useEffect обратный вызов имеет пустой массив зависимостей, поэтому он выполняется только при первоначальном монтировании. Всякий раз, когда onAnswerCall вызывается привязка calls , которую он может видеть, находится в старом закрытии.

Используйте ссылку вместо состояния:

 const callsRef = useRef({});
const addCall = (key, value) => {
  callsRef.current[key] = value;
};
const onAnswerCall = () => {
  console.log('=== onAnswerCall ::: ===', callsRef.current);
}
  

Если calls необходимо находиться в состоянии, чтобы его изменение привело к повторному рендерингу, то вы можете либо использовать как состояние, так и ссылку и присваивать ссылку при вызове setCalls , либо вы можете удалить массив зависимостей из useEffect , таким образом, добавляя и удаляя слушателей RNCallKeep при каждом рендеринге.

Комментарии:

1. Это сработает, но не приведет к повторному отображению компонента при обновлении вызовов, которые, как я полагаю, используются при рендеринге компонента, поэтому я полагаю, что ему необходимо запустить повторный рендеринг.

2. Поскольку возвращаемое значение компонента не отображается, неясно, нужно ли компоненту повторно отображать при выполнении нового вызова. Если требуется повторная визуализация, то, как я уже сказал, одним из методов является использование как ссылки, так и состояния. Другой способ заключается в добавлении и удалении прослушивателя при каждом вызове.

3.Смысл использования state вместо обычных переменных заключается в том, чтобы React запускал повторный рендеринг компонента. Если вы проверите мой ответ, слушатель обновляется (удаляется / добавляется) при каждом onAnswerCall useCallback обновлении, что, я считаю, является наилучшим подходом, в конце концов, это цель этих хуков.

4. Если повторный рендеринг необходим при изменении переменной, да, конечно, используйте state — но из кода в вопросе мне не ясно, нужен ли такой повторный рендеринг. К сожалению, возвращенный JSX компонента не отображается

5. На данный момент не используется при рендеринге. Было бы лучше, если бы у нас также была гибкость повторного рендеринга. Это решение работает

Ответ №2:

Состояние React hooks не сохраняет никаких данных о состоянии, вы должны использовать useCallback и предыдущее состояние, указанное в set* функциях состояния, чтобы не отставать от вашего состояния:

 const callApp = () => {
  const [calls, setCalls] = useState({});

  const addCall = (key, value) => {
    // Use previous state provided to update the state
    setCalls(calls => ({ ...calls, [key]: value }));
  };

  const displayIncomingCall = number => {
    const callUUID = getCurrentCallId();
    addCall(callUUID, number);
    ...
  };

  //  Use useCallback to keep up  with the  state
  const onAnswerCall = useCallback(() => {
    console.log('=== onAnswerCall ::: ===', calls); <--- Will get updated
  }, [calls]);

  useEffect(() => {
    console.log('=== useEffect ::: ===', calls); // getting updated value
  }, [calls]);

  useEffect(() => {
    RNCallKeep.addEventListener('answerCall', onAnswerCall);
    return () => {
      RNCallKeep.removeEventListener('answerCall', onAnswerCall);
    };
  }, [onAnswerCall]);

  console.log('=== parent ::: ===', calls); // getting updated value

  return (...)
}
  

Комментарии:

1. но onAnswerCall запускается несколько раз (прослушиватель вызывается только один раз)

2. Прослушиватель отменит регистрацию старого и зарегистрирует новый при каждом calls обновлении, это не повлияет на то, сколько раз будет запускаться answerCall событие и, следовательно, сколько раз onAnswerCall будет вызываться прослушивателем событий.

3. как onAnswerCall у меня есть похожие прослушиватели событий состояния вызова 4-5, о которых я не упомянул во фрагменте. Как я прочитал kentcdodds.com/blog/usememo-and-usecallback Повлияет ли это на производительность, если мы будем использовать useCallback для всех прослушивателей событий?

4. Каждая строка кода, которая выполняется, сопряжена с затратами Снижение производительности, которое вы получите, незначительно — один дополнительный вызов функции за событие для всех слушателей. Вполне возможно, что когда вы вызываете 2 функции вместо 1, предстоит проделать большую работу, не так ли? Если вы просто используете эти функции для прослушивателей, поместите их непосредственно внутри эффекта монтирования, иначе у вас нет выбора, если вы используете calls состояние в своем методе рендеринга, которое вам придется использовать useCallback .

Ответ №3:

Ваш обратный вызов прослушивателя событий закрыт по сравнению с начальным состоянием calls . Добавьте его в качестве зависимости к эффекту, и вы должны регистрировать нового слушателя (и удалять старые) каждый раз, когда изменяется его значение.

РЕДАКТИРОВАТЬ: На самом деле, вы должны переместить всю onAnswerCall функцию в эффект, поскольку она используется только там:

 useEffect(() => {
  const onAnswerCall = () => {
    console.log('=== onAnswerCall ::: ===', calls);
  };

  RNCallKeep.addEventListener('answerCall', onAnswerCall);
  return () => {
    RNCallKeep.removeEventListener('answerCall', onAnswerCall);
  };
}, [calls]);
  

Комментарии:

1. Зачем вам это делать? Вы думаете, onAnswerCall что будет по-другому после calls изменения?

2. @ChristosLytras это другая функция для каждого рендеринга, так что да! Но возвращаемое значение на самом деле отличается только при calls изменениях. Вот ручка, которая иллюстрирует это (посмотрите на консоль): codesandbox.io/s/laughing-lehmann-4xe52?file=/src/App.js

3.Мой вопрос был немного ошибочным, и я ссылался на calls изменение состояния, а не на рендеринг. Я считаю, что правильный способ — использовать useCallback и получать новую функцию только тогда, calls когда она изменяется, а затем повторно применять прослушиватели. Проверьте свой пример с этой логикой здесьcodesandbox.io/s/still-fast-x41jt?file=/src/App.js

4. Я не думаю, что мы не согласны — оба решения делают одно и то же (в вашем меньше ненужных созданий функций, но в любом случае это действительно быстро в JS). Я пытался указать на непонимание OP того, как работают прослушиватели событий.

5. Тогда соглашайтесь не соглашаться. Определенно есть время и место для useCallback , и я абсолютно использую его ежедневно. Я просто не думаю, что вам это нужно здесь, поскольку и эффект, и функция зависят от одного и того же изменения.