1

I am using Fluent UI Details List and trying to make row editable on icon click. My code is as below. On first run, it shows grid with empty data. Then it goes to useEffect (I tried both useEffect and useLayoutEffect, same behaviour) and data is fetched and stored in state. It also fires the render and the grid shows all the rows per the data. All good so far. When row > cell 1 is double-clicked on the grid, I turned the row to editable mode. That is also working.

For each editable column, I have a different onChange event attached. So, when any input text box/dropdown cell value changes, it fires the respective onChange event callback function.

Within this cell change callback event, it gets item id and final changed value as input aurguments. And using them, data array will be updated and stored in state. This is my expectation.

But when I read the current data array from state within the callback function, it is empty.

So, basically, problem is that state value stored from useEffect is not retained. There is no other code line where data array state is updated. So, no chance that the data array is reset by code. If anyone has any idea or faced, solved such issue, let me know. Thanks in advance.


Adding few things which I tried,

  1. I tried using the class component and it worked. only difference is that instead of useEffect, I used componentDidMount and instead of useState, I used this.setState. Not sure what is other difference within class and function component? The same code works in class component but not in function component.
  2. I tried using the same function component and instead of async call within useEffect, I made direct sync fetch call before render and loaded data in state as initial value. Then, it worked. So, it fails only when the data is fetched async mode within useEffect and set to state from there.

My problem is resolved after converting to class component. but want to understand what is the issue within my function component code.

/** function component */
const [dataItems, setDataItems] = useState<IScriptStep[]>([]);
const [groups, setGroups] = useState<IGroup[]>([]);
/** Component Did Mount */
  useLayoutEffect(() => {
    props.telemetry.log(`useEffect - []`, LogLevel.Debug);
    (async () => {
      let scriptSteps = await props.dataHelper.retrieveScriptSteps();
      setDataItems(scriptSteps);
      let groups = getGroups(scriptSteps);
      setGroups(groups);
      props.telemetry.log(`Data updated in state`, LogLevel.Debug);
    })();
  }, []);
/** Render */
  return (
    <div className="SubgridMain">
      {props.telemetry.log(`render`, LogLevel.Debug)}
      <div className="List">
        <DetailsList
          componentRef={_root}
          items={dataItems}
          groups={groups}
          columns={columns}
          ariaLabelForSelectAllCheckbox="Toggle selection for all items"
          ariaLabelForSelectionColumn="Toggle selection"
          checkButtonAriaLabel="select row"
          checkButtonGroupAriaLabel="select section"
          onRenderDetailsHeader={_onRenderDetailsHeader}
          onRenderRow={_onRenderRow}
          groupProps={{ showEmptyGroups: true }}
          onRenderItemColumn={_onRenderItemColumn}
          onItemInvoked={_onItemInvoked}
          compact={false}
        />
      </div>
    </div>
  );
/** on change cell value callback function */
const _onChangeCellName = (entityId : string, fieldName:string, finalValue:string) => {
    let currentItems = dataItems;
    // create new data array
    let toUpdateState: boolean = false;
    let newItems = currentItems.map((item) => {
      if (item.key === entityId) {
        if (item.name !== finalValue) {
          toUpdateState = true;
          item.name = finalValue ?? null;
        }
      }
      return item;
    });
    if (toUpdateState) setDataItems(newItems);
  };
/** columns configuration is set as below */
    let columns : IColumn[] = [
    {
        key: 'name',
        name: 'Name',
        fieldName: 'name',
        minWidth: 300,
        isResizable: true,
        onRender: this._onRenderCellName,
    },
    ..
    ..
    ]
/** Render Name cell */ 
    private _onRenderCellName(item?: IScriptStep, index?: number, column?: IColumn) {
        if (item) {
            let stepName = item?.name ?? '';
            if (item.isEditable) {
                let propsNameTextEditor: ITextEditorWrapperProps = {
                    entityId: item.key,
                    fieldName: 'name',
                    initialText: stepName,
                    multiline: true,
                    required: true,
                    setFinalValue: this._onChangeCellName,
                };
                return <TextEditorWrapper {...propsNameTextEditor} />;
            } else {
                return (
                    <div className="ReadOnly">
                        <p>{stepName}</p>
                    </div>
                );
            }
        } else return <></>;
    };
Sagar
  • 137
  • 1
  • 12
  • Where do you pass your onChange handler? – Dean Jul 16 '22 at 08:05
  • onChange handler is passed within "columns" props (passed to DetailsList). – Sagar Jul 16 '22 at 14:27
  • It would make sense if you made a minimal reproducable example. It looks here like your component returns before you define `onChangeCellName` (which would become unreachable code). Unless you of course defined it outside the scope of the function component. Then the question becomes: Where does `setDataItems` come from in your callback? – Dean Jul 16 '22 at 14:34

0 Answers0