Почему реквизиты React не обновляются в обработчике событий

#reactjs #react-functional-component

Вопрос:

У меня есть функциональный компонент, использующий крючок useEffect для подписки на внешнее событие, и это внешнее событие использует значение в реквизитах компонентов. Я не понимаю, почему сам компонент при рендеринге использует обновленное значение реквизита, но мой обратный вызов использует исходное значение.

Я подумал, что, возможно, мне нужно изменить второй параметр useEffect, чтобы указать реквизиты, однако это не имеет никакого значения.

В приведенном ниже примере обратный вызов OnSave вызывается и пытается использовать текущее значение props.ModelName, однако, даже если сам компонент имеет обновленное значение, обратный вызов, похоже, видит только исходное значение этого свойства.

Это почему? Это как-то связано с закрытием или я упускаю что-то еще?

 useEffect(() => {
    EventBus.on(EventIds._designerSaveCommand, onSave);

    return () => {
      EventBus.remove(EventIds._designerSaveCommand, onSave);
    };
  }, [reactFlowInstance,props]);
 

И у меня есть такой обработчик событий:

 const onSave = () => {
    try {
      const object = reactFlowInstance?.toObject();

      if(object) {
        const exportedModel: IExportedModel = {
          modelName: props.modelName,  <---- This is not the current value in the component props
          model: object
        };

        const json = JSON.stringify(exportedModel);
        var blob = new Blob([json], { type: "application/json" });
        FileSaver.saveAs(blob, `${props.modelName}.json`);
      }
    }
    catch(e) {
      setMessage({text: e.message, type: MessageBarType.error});
    }
  };
 

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

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

Ответ №1:

У вас есть правильная идея, добавив prop в массив зависимостей. Вам также нужно переместить функцию onSave() внутрь крючка. Затем он будет ссылаться на последнюю modelName версию .

 useEffect(() => {
  const onSave = () => {
    try {
      const object = reactFlowInstance?.toObject();
      if(object) {
        const exportedModel: IExportedModel = {
          modelName: modelName,
          model: object
        };

      const json = JSON.stringify(exportedModel);
        var blob = new Blob([json], { type: "application/json" });
        FileSaver.saveAs(blob, `${modelName}.json`);
      }
    }
    catch(e) {
      setMessage({text: e.message, type: MessageBarType.error});
    }
  };

  EventBus.on(EventIds._designerSaveCommand, onSave);

  return () => {
    EventBus.remove(EventIds._designerSaveCommand, onSave);
  };
}, [reactFlowInstance, modelName]);

 

Если вам не нравится useEffect so big, вы можете отделить только логику файла от новой функции и вызвать ее внутри крючка следующим образом:

   useEffect(() => {
    const onSave = () => {
      saveFile(reactFlowInstance, modelName)
    };
    // ... handlers go here
  }, [reactFlowInstance, modelName]);