Закрытие Reactjs при передаче состояния компоненту

У меня есть функциональный компонент реакции:

const DataGrid = (props) =>
{          
    const [containerName, setContainerName] = useState("");                                                                   
    const [frameworkComponents, setFrameworkComponents] = useState(
      {customLoadingOverlay: LoadingOverlayTemplate,
      customNoRowsOverlay: UxDataGridCustomNoRows,
      editButton: params => <ViewAndDeleteSetting {...params}  
                                                  openAddConfigurationsWindow={openAddConfigurationsWindow}
                                                  onDeleteSetting={onDeleteSetting}/>,
     });

useEffect(async () =>
    {
      if(props.containerName && props.containerName !== "")
      {
        setContainerName(props.containerName);
      }
    },[props.containerName]);
.
.
.
const onDeleteSetting = async (settingKey) =>
{
  console.log("ON DELETE AND CONTAINER NAME:");
  console.log(containerName); //HERE THE CONTAINER NAME IS EMPTY
   ...
}
return (
  <UxDataGrid 
            frameworkComponents={frameworkComponents}/>
);

Имя контейнера внутри useEffect существует и не является пустым. Как видно из комментария к onDeleteSetting, containerName пусто при вызове этого обратного вызова. Я попытался добавить это к useEffect после setContainerName:

setFrameworkComponents({customLoadingOverlay: LoadingOverlayTemplate,
        customNoRowsOverlay: UxDataGridCustomNoRows,
        editButton: params => <ViewAndDeleteSetting {...params}  
                                                         openAddConfigurationsWindow={openAddConfigurationsWindow}
                                                         onDeleteSetting={onDeleteSetting}/>,
            });

Это не сработало.

Как я могу получить имя внутри обратного вызова? Нет особой необходимости оставлять эту структуру frameworkComponents в состоянии.. ее также можно переместить в другое место, если вы считаете, что это лучше


person Yonatan Nir    schedule 12.01.2021    source источник
comment
containerName в обработчике onDeleteSetting пусто, потому что ваша переменная frameworkComponents установлена ​​только изначально. Даже если вы попытаетесь установить его после вызова setContainerName, React useState обновит не сразу. Если вы действительно хотите сделать что-то подобное, вы можете добавить еще один useEffect с containerName в качестве зависимости и вызвать в нем setFrameworkComponents. Но вся ваша структура кода выглядит как плохая практика, попробуйте провести рефакторинг, чтобы не устанавливать целые компоненты в свой state   -  person Kapobajza    schedule 12.01.2021
comment
@Kapobajza, не могли бы вы добавить ответ о том, как вы предлагаете провести рефакторинг?   -  person Yonatan Nir    schedule 12.01.2021
comment
Проблема, связанная с устаревшим закрытием, stackoverflow.com/questions/63307310/, это может вам помочь.   -  person Siddharth Pachori    schedule 12.01.2021


Ответы (3)


Попробуйте это в своем useEffect, обновите функцию onDeleteSetting новым containerName, когда она будет обновлена.

.....
useEffect(async() => {
  if (props.containerName && props.containerName !== "") {
    setContainerName(props.containerName);
    
    // move this function here
    const onDeleteSetting = async(settingKey) => {
      console.log("ON DELETE AND CONTAINER NAME:");
      // use props.containerName since the state update is async
      console.log(props.containerName);
      ...
    }

    // update your components with the updated functions
    setFrameworkComponents(prevComponents => ({
      ...prevComponents,
      editButton: params => 
              <ViewAndDeleteSetting
                {...params}                                                  
                openAddConfigurationsWindow={openAddConfigurationsWindow}
                onDeleteSetting={onDeleteSetting}
              />,
    }));
  }
}, [props.containerName]);
.....

Это должно предоставить обновленное состояние с обновленной функцией, если это работает, я могу добавить больше деталей.

person Sudhanshu Kumar    schedule 13.01.2021

Вы почти наверняка не должны хранить его в состоянии. Реквизит по существу контролируется родителем. Просто используйте его из реквизита. Копирование реквизита в состояние обычно не лучший вариант. практика.

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

Вот полная цитата из связанной документации:

Как реализовать getDerivedStateFromProps?

Хотя вам, вероятно, это не нужно, в редких случаях (например, при реализации компонента <Transition>) можно обновить состояние прямо во время рендеринга. React повторно запустит компонент с обновленным состоянием сразу после выхода из первого рендеринга, поэтому это не будет дорого.

Здесь мы сохраняем предыдущее значение свойства строки в переменной состояния, чтобы мы могли сравнить:

function ScrollView({row}) {
  const [isScrollingDown, setIsScrollingDown] = useState(false);
  const [prevRow, setPrevRow] = useState(null);

  if (row !== prevRow) {
    // Row changed since last render. Update isScrollingDown.
    setIsScrollingDown(prevRow !== null && row > prevRow);
    setPrevRow(row);
  }

  return `Scrolling down: ${isScrollingDown}`;
}

На первый взгляд это может показаться странным, но обновление во время рендеринга — это именно то, чем концептуально всегда был getDerivedStateFromProps.

Если вы сделаете это так же, как в этом примере, ваш компонент все равно будет отображаться с containerName, установленным в состояние по умолчанию (""), просто он будет почти сразу повторно отображаться с обновленным containerName. Это имеет смысл для их примера перехода, но вы можете избежать этого, сделав значение свойства initial значением состояния initial, например так:

const DataGrid = (props) => {
    const [containerName, setContainerName] = useState(props.containerName); // *** ONLY USES THE INITIAL PROP VALUE
    const [frameworkComponents, setFrameworkComponents] = useState(
        // ...
     });

    // *** Updates the state value (on the next render) if the prop changes
    if (containerName !== props.containerName) {
        setContainerName(props.containerName);
    }

    // ...
};

Однако каждый раз, когда реквизит containerName изменяется, ваш компонент будет рендериться дважды, что возвращает нас к следующему: не хранить его в состоянии, просто использовать его из реквизита. :-)


Отступая назад и глядя на компонент в целом, я не думаю, что вам вообще нужна какая-либо информация о состоянии, но если ваша цель состоит в том, чтобы избежать ненужного изменения frameworkComponents, которое вы передаете UxDataGrid, вы, вероятно, захотите useMemo или React.memo, а не состояние.

Например, с useMemo (но продолжайте читать):

const DataGrid = ({containerName}) => {
    const frameworkComponents = useMemo(() => {
        const onDeleteSetting = async (settingKey) => {
            console.log("ON DELETE AND CONTAINER NAME:");
            console.log(containerName);
            // ...
        };
        return {
            customLoadingOverlay: LoadingOverlayTemplate,
            editButton: params => <ViewAndDeleteSetting {...params}  
                openAddConfigurationsWindow={openAddConfigurationsWindow}
                onDeleteSetting={onDeleteSetting} />,
        };
    }, [containerName]);

    return (
        <UxDataGrid frameworkComponents={frameworkComponents} />
    );
};

Но если componentName — ваша единственная опора, с React.memo может быть еще проще:

const DataGrid = React.memo(({containerName}) => {
    const onDeleteSetting = async (settingKey) => {
        console.log("ON DELETE AND CONTAINER NAME:");
        console.log(containerName);
        // ...
    };
    return (
        <UxDataGrid frameworkComponents={{
            customLoadingOverlay: LoadingOverlayTemplate,
            editButton: params => <ViewAndDeleteSetting {...params}  
                openAddConfigurationsWindow={openAddConfigurationsWindow}
                onDeleteSetting={onDeleteSetting} />,
        }} />
    );
});

React.memo запоминает ваш компонент, так что функция вашего компонента вызывается снова только при изменении реквизита. Поскольку все в компоненте необходимо обновить на основе изменения свойства componentName, это выглядит как хорошее совпадение (но я не знаю, что такое UxDataGrid).

person T.J. Crowder    schedule 12.01.2021
comment
Я пытался использовать useMemo, но все равно получил пустую строку. - person Yonatan Nir; 12.01.2021
comment
@YonatanNir - Если это так, то либо вы не реализовали это правильно, либо UxDataView не использует последнюю версию onDeleteSetting. - person T.J. Crowder; 12.01.2021
comment
Сам useMemo, кажется, работает, так как я поместил туда журнал консоли, и я вижу, что он сначала отображает пустую строку, но когда я запускаю рендеринг с новыми реквизитами, он снова вызывает useMemo. Просто по какой-то причине обратный вызов внутри все еще не использует последние данные - person Yonatan Nir; 12.01.2021
comment
я даже пытался определить структуру без использования useMemo, чтобы она каждый раз создавалась заново, и все равно это не работает - person Yonatan Nir; 12.01.2021
comment
@YonatanNir - похоже на проблему с UxDataView (или таким способом его использования). В частности отмечу, что одним из свойств является функция, создающая кнопку удаления. Похоже, что это может быть использовано, а затем не использовано повторно при повторном рендеринге компонента. - person T.J. Crowder; 12.01.2021

Проблема заключалась в том, как я пытался передать реквизит ViewAndDeleteSetting. Если вы хотите передать свойство компоненту, визуализируемому ячейкой, вы не должны делать это в frameworkComponents, а скорее вам нужно сделать это в определении столбца следующим образом:

useEffect(() =>
{
  let columns = [{headerName: '', cellRenderer: 'editButton', width: 90, editable: false, 
                  cellRendererParams: {
                    openAddConfigurationsWindow: openAddConfigurationsWindow,
                    onDeleteSetting: onDeleteSetting
                }},
                .. other columns
                ]

  setColumnDefinition(columns);
},[props.containerName]);

Столбцы с cellRendererParams воссоздаются в useEffect при изменении имени, а затем компонент может регулярно получать доступ к этим параметрам через свои реквизиты.

person Yonatan Nir    schedule 13.01.2021