reactjstypescriptag-gridag-grid-react

Retain edited cell value onblur or click away ag-grid reactjs using typescript


I have grid using ag-grid reactjs typescript that has 3 columns. The 3rd column named "Value" is editable. I was able to edit the value but when onblur event, I lost the edited value and the value is reverted to original. I want to update the value based on I have entered. For example the original value is Initial Value 1 and I edited it to Initial Value 1 Edited, when I leave my mouse or click away, the value of that cell should be Initial Value 1 Edited. However that's not what's happening. It always returns to Initial Value 1. Below is my code. Please help

import React, { useState, useRef, useEffect } from 'react';
import { AgGridReact,ColDef } from 'ag-grid-react';
import 'ag-grid-community/styles/ag-grid.css';
import 'ag-grid-community/styles/ag-theme-alpine.css';
import { ICellEditorParams } from 'ag-grid-community';

interface RowData {
  id: number;
  name: string;
  value: string;
}

const InputCellEditor = (props: ICellEditorParams<RowData, string>) => {
  const inputRef = useRef<HTMLInputElement>(null);
  const [value, setValue] = useState(props.value);

  useEffect(() => {
    if (inputRef.current) {
      inputRef.current.focus();
    }
  }, []);

  const onChange = (event: React.ChangeEvent<HTMLInputElement>) => {
    setValue(event.target.value);
  };

  const onKeyDown = (event: React.KeyboardEvent) => {
    if (event.key === 'Enter' || event.key === 'Escape') {
      event.stopPropagation();
    }
  };

  const getValue = () => {
    return value;
  };

  return (
    <input
      type="text"
      ref={inputRef}
      style={{ width: '100%' }}
      value={value}
      onChange={onChange}
      onKeyDown={onKeyDown}
    />
  );
};

const GridExample: React.FC = () => {
  const gridRef = useRef<AgGridReact>(null);
  const [rowData, setRowData] = useState<RowData[]>([
    { id: 1, name: 'Item 1', value: 'Initial Value 1' },
    { id: 2, name: 'Item 2', value: 'Initial Value 2' },
    { id: 3, name: 'Item 3', value: 'Initial Value 3' },
  ]);
const [colDefs] = useState<ColDef<any>[]>([
    { field:"id", editable:false},
    { field:"name", editable:false},
    { field:"value",
          editable:true,
          cellEditor:InputCellEditor}
  ]);
  const onCellValueChanged = (params: any) => {
    const updatedRowData = rowData.map((item) =>
      item.id === params.data.id ? { ...item, [params.colDef.field]: params.newValue } : item
    );
    setRowData(updatedRowData);
  };

  return (
    <div className="ag-theme-alpine" style={{ height: 300, width: 600 }}>
      <AgGridReact
        ref={gridRef}
        rowData={rowData}
        onCellValueChanged={onCellValueChanged}
        editType='fullRow'
        columnDefs={colDefs}
      >
       
      </AgGridReact>
    </div>
  );
};

export default GridExample;

Solution

  • You need to use forwardRef and useImperativeHandle in your custom cell editor to expose the getValue method to ag-Grid. This way, ag-Grid can call getValue to retrieve the updated value after editing, preventing the value from reverting to the original.

    import React, { useState, useRef, useEffect, forwardRef, useImperativeHandle } from 'react';
    import { AgGridReact } from 'ag-grid-react';
    import 'ag-grid-community/dist/styles/ag-grid.css';
    import 'ag-grid-community/dist/styles/ag-theme-alpine.css';
    
    const InputCellEditor = forwardRef((props, ref) => {
      const inputRef = useRef(null);
      const [value, setValue] = useState(props.value);
    
      useEffect(() => {
        if (inputRef.current) {
          inputRef.current.focus();
        }
      }, []);
    
      useImperativeHandle(ref, () => ({
        getValue() {
          return value;
        }
      }));
    
      const onChange = (event) => {
        setValue(event.target.value);
      };
    
      const onKeyDown = (event) => {
        if (event.key === 'Enter' || event.key === 'Escape') {
          event.stopPropagation();
        }
      };
    
      return (
        <input
          type="text"
          ref={inputRef}
          style={{ width: '100%' }}
          value={value}
          onChange={onChange}
          onKeyDown={onKeyDown}
        />
      );
    });
    
    const App = () => {
      const gridRef = useRef(null);
      const [rowData, setRowData] = useState([
        { id: 1, name: 'Item 1', value: 'Initial Value 1' },
        { id: 2, name: 'Item 2', value: 'Initial Value 2' },
        { id: 3, name: 'Item 3', value: 'Initial Value 3' },
      ]);
      const [columnDefs] = useState([
        { field: 'id', editable: false },
        { field: 'name', editable: false },
        { field: 'value', editable: true, cellEditor: InputCellEditor },
      ]);
    
      const onCellValueChanged = (params) => {
        const updatedRowData = rowData.map((item) =>
          item.id === params.data.id
            ? { ...item, [params.colDef.field]: params.newValue }
            : item
        );
        setRowData(updatedRowData);
      };
    
      return (
        <div className="ag-theme-alpine" style={{ height: 300, width: 600 }}>
          <AgGridReact
            ref={gridRef}
            rowData={rowData}
            columnDefs={columnDefs}
            onCellValueChanged={onCellValueChanged}
          />
        </div>
      );
    };
    
    export default App;
    

    Update:

    I realized that the above code works correctly with these versions: "ag-grid-community": "^30.0.0", "ag-grid-react": "^30.0.0" Here’s how you can update your custom cell editor to work with version 32:

      const InputCellEditor = (props, ref) => {
      const inputRef = useRef(null);
      const [value, setValue] = useState(props.value);
    
      useEffect(() => {
        if (inputRef.current) {
          inputRef.current.focus();
        }
      }, []);
    
      const onChange = (e) => {
        const newValue = e.target.value;
        setValue(newValue);
        props.onValueChange?.(newValue);
      };
    
      const onKeyDown = (e) => {
        if (e.key === 'Enter') {
          props.stopEditing(false);
          e.stopPropagation();
        } else if (e.key === 'Escape') {
          props.stopEditing(true);
          e.stopPropagation();
        }
      };
    
      const handleBlur = () => {
        props.stopEditing(false);
      };
    
      return (
        <input
          type="text"
          ref={inputRef}
          style={{ width: '100%' }}
          value={value}
          onChange={onChange}
          onKeyDown={onKeyDown}
          onBlur={handleBlur}
        />
      );
    };