Using Ant Design and React for Editable Cells Implementation and Table
Published: 2023-11-02 09:26:07


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3xgw9q0viie3g1zapsgi.png)

Hello developer, here I'm going to share how to make editable cells using Ant design (antd). Antd was developed by the Alibaba Group. It's one of the most popular UI frameworks for web applications and React-based applications. Antd provides many UI components that developers can use when building web applications. It helps developers to make the UI they create consistent and interesting. We can read the documentation on the official website of ant.design.

**Prerequisite**
There are necessary requirements to create this application.


1. ReactJS (here I am using version 18.2.0) 
2. Antd (when writing this, I was using version 4) Defining Dummy Data Code below is an example of the data to display

Defining Dummy Data 
Code below is an example of the data to be displayed

```
const data = {
    content: [
      {
        id: 1,
        name: 'John Sr',
        age: 60,
        address: 'Sudirman No 1, Jakarta Selatan',
      },
      {
        id: 2,
        name: 'Lennon Jr',
        age: 32,
        address: 'Kemanggisan No 3, Jakarta Barat',
      },
    ]
  }
```

**Defining Column **
After knowing the response data to be displayed, then we need to define the column according to the data parameters.

```
const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
    },
    {
      title: 'Age',
      dataIndex: 'age',
    },
    {
      title: 'Address',
      dataIndex: 'address',
    },
  ]
```

**Implement EditableCell & EditableContext**
Next we can take advantage of some hooks in ReactJs on the `Context.tsx` file.

```
// src/components/home/table/Context.tsx

import { createContext, useContext, useEffect, useRef, useState } from 'react';
import { validateInput } from '../../../utils/validate';
import { Form, Input } from 'antd';

export const EditableContext: any = createContext(null);
export const EditableCell = (props: any) => {
  const {
    title,
    editable,
    children,
    dataIndex,
    record,
    index,
    suffix,
    validate,
    handleSave,
    success,
    setSuccess,
    handleValidate,
    ...restProps
  } = props
  const [editing, setEditing] = useState(false);
  const inputRef: any = useRef(null);
  const form: any = useContext(EditableContext);
  const content = restProps?.data?.content || [];
  useEffect(() => {
    if (editing) {
      inputRef.current.focus();
    }
  }, [editing]);
  const toggleEdit = (val: any) => {
    setEditing(!editing);
    const value = val.target.value;
    form.setFieldsValue({
      [dataIndex]: value,
    });
  };
  const save = async (e: any) => {
    try {
      toggleEdit(e);
      if (e.target.value) {
        const val = await form.validateFields();
        await handleSave({
          ...record,
          ...val,
        });
      }
      const suc = success;
      const bool = await handleValidate(
        index,
        record,
        dataIndex,
        e.target.value
      );
      if (index !== undefined) {
        suc[index] = bool;
        setSuccess(suc);
      }
    } catch (errInfo) {
      //
    }
  };
  let errorMsg = null;
  let validateStatus: '' | 'success' | 'warning' | 'error' | 'validating' = '';
  if (
    validate &&
    validate.length &&
    validate[index] &&
    validate[index]?.[dataIndex]?.errorMsg
  ) {
    errorMsg = validate[index]?.[dataIndex]?.errorMsg;
    validateStatus = validate[index]?.[dataIndex]?.validateStatus;
  }
  let childNode = children;
  const value =
    children && Array.isArray(children) && children.length
      ? children[children.length - 1]
      : '';
  if (editable) {
    childNode = editing ? (
      <Form.Item
        style={{
          margin: 0,
        }}
        name={dataIndex}
        rules={[
          {
            required: true,
            message: `${title} is required.`,
          },
        ]}
      >
        <Input
          style={{
            width: '100%',
          }}
          max={suffix === 'th' ? 100 : undefined}
          placeholder="0"
          suffix={suffix}
          ref={inputRef}
          disabled={restProps?.disabled}
          onBlur={save}
          onKeyDown={(e) =>
            validateInput(e, () => {}, {
              minLength: 1,
              maxLength: suffix === 'th' ? 2 : undefined,
              allowDecimal: suffix === 'th',
              isNumber: true,
            })
          }
        />
      </Form.Item>
    ) : (
      <Form.Item
        style={{
          margin: 0,
        }}
        help={errorMsg}
        validateStatus={validateStatus}
      >
        <Input
          className="editable-cell-value-wrap"
          style={{
            paddingRight: 24,
          }}
          onFocus={toggleEdit}
          value={value}
          disabled={restProps?.isDisabled}
        />
      </Form.Item>
    );
  }

  if (dataIndex === 'rangeOmzetUpper' && index === content.length - 1) {
    childNode = <></>;
  }

  return <td {...restProps}>{childNode}</td>;
};

```


There are 5 hooks that I use to make this application, namely:

1. `createContext` is used to create a context object that allows components in the React application to share data without having to pass the prop from component to component. In the above code, I use it on the variable `EditableContext`.
2. `useContext` is used for accessing the data provided by the context object created with `createContext`. It allows the components to take values from the context. I place it on a variable form to access the values of `EditableContext`. 
3. `useEffect` is used in dealing with side effects in the component React. In my example, it's used to detect changes in the variable editing.
4. `useRef` is used to make references to the DOM element or other values in the component. I use useRef to access and manipulate the input element directly, triggering it if there are changes to the editing variable.
5. `useState` is used to create state variables in functional components. With `useState`, developers can store and modify data that affects the display of components, and any change will trigger the component rerender.

**Implementation of the HomeTableContainer** 
Now I'm going to create a `HomeTableContainer`, here's the logic code that will be used in the `HomeTableView`. I also called the Functions `EditableContext` and `EditableCell` from the `Context.tsx` file. On the function `handleValidate` I created an age limit condition that can be entered by the user, if the age exceeds 60 years then there will be error. 

```
// src/components/home/table/index.tsx

import { useEffect, useState } from 'react';
import HomeTableView from './Main'
import { Form } from 'antd';
import { cloneDeep, isEmpty } from 'lodash';
import { EditableContext, EditableCell } from './Context';

const HomeTableContainer = ({
  data,
  isLoading,
  isDisabled,
}: any) => {
  const [validate, setValidate]: any = useState([]);
  const [mutableData, setMutableData] = useState(data)
  const [success, setSuccess] = useState<any>([]);
  const handleSave = async (row: any) => {
    if (isDisabled) {
      return;
    }
    const newData = cloneDeep(data);
    const index = newData.content.findIndex(
      (item: any) => Number(row.id) === Number(item.id)
    );
    if (newData.content[index]) {
      newData.content[index] = row;
      setMutableData({ ...newData });
    }
  };
  const handleValidate: any = (
    index: number,
    record: any,
    dataIndex: string,
    value: string | number
  ) => {
    let bool = true;
    if (isDisabled) {
      return false;
    }
    const val = cloneDeep(validate);
    const obj: any = {}
    if ((dataIndex === 'age' &&
        Number(value) > 60)) {
        obj.age = {
        validateStatus: 'error',
        errorMsg: `Maksimal usia ${
          record.name
        } adalah 60th`,
      };
      bool = false;
    } 
    if (!val[index]) {
      val[index] = {
        [dataIndex]: {}
      }
    }
    if (obj && !isEmpty(obj) && val[index]) {
      val[index] = {...obj}
    } else {
      val[index] = {}
    }
    setValidate([...val])
    return bool;
  };
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
  const EditableRow: any = ({ index, ...obj }: any) => {
    const [form] = Form.useForm();
    return (
      <Form initialValues={data.content} form={form} component={false}>
        <EditableContext.Provider value={form}>
          <tr {...obj} />
        </EditableContext.Provider>
      </Form>
    );
  };

  const components = {
    body: {
      row: EditableRow,
      cell: EditableCell,
    },
  };
  const sharedOnCell = () => {
    return {};
  };
  const handleSetSuccess = (val: any) => {
    setSuccess(val);
  };
  useEffect(() => {
    return () => {
      setSuccess([]);
    };
  }, []);
  return (
    <HomeTableView
      data={mutableData}
      components={components}
      isLoading={isLoading}
      rowKey={'id'}
      handleSave={handleSave}
      success={success}
      setSuccess={handleSetSuccess}
      validate={validate !== undefined ? validate : []}
      isDisabled={false}
      handleValidate={handleValidate}
      sharedOnCell={sharedOnCell}
    />
  )
}

export default HomeTableContainer
```

**Implement HomeTableView**
After creating logic on `HomeTableContainer`, in the `HomeTableView` file I defined and modified the column by placing the function that we have created in the `HomeTableContainers` file so that the rows on column age can be edited.

```
// src/components/home/table/Main.tsx

import {  Col, Row, Table  } from 'antd';

const HomeTableView = ({
  data,
  components,
  isLoading,
  onTableChange,
  rowKey,
  handleSave,
  handleValidate,
  sharedOnCell,
  validate,
  success,
  setSuccess,
  isDisabled,
}: any) => {
  const columns = [
    {
      title: 'Name',
      dataIndex: 'name',
      key: 'name',
      width: '100px',
    },
    {
      title: 'Age',
      dataIndex: 'age',
      key: 'age',
      onCell: sharedOnCell,
      editable: true,
      suffix: 'th',
      width: '50px',
    },
    {
      title: 'Address',
      dataIndex: 'address',
      width: '100px',
      key: 'address',
    },
  ];
  const mutableColumns = columns.map((col: any) => {
    if (!col.editable) {
      return col;
    }
    return {
      ...col,
      onCell: (record: any, index: number) => ({
        record,
        suffix: col.suffix,
        editable: col.editable,
        dataIndex: col.dataIndex,
        title: col.title,
        handleSave,
        data,
        disabled: isDisabled,
        handleValidate,
        validate,
        success,
        setSuccess,
        index,
      }),
    };
  });
  return (
    <>
      <Row style={{ marginTop: '12px' }}>
        <Col flex={'auto'}>
          <Row style={{ marginBottom: '12px', marginTop: '12px' }}>
            <Table
              bordered
              columns={mutableColumns}
              components={components}
              dataSource={data?.content || []}
              loading={isLoading}
              rowClassName={() => 'editable-row'}
              onChange={onTableChange}
              pagination={false}
              rowKey={rowKey}
              sticky
              scroll={{ x: 1500 }}
              style={{ width: '100%' }}
            />
          </Row>
        </Col>
      </Row>
    </>
  );
};
export default HomeTableView

```

**Output**
Below this is the result of the code above, the input form will display an error if the age is over 60 years.


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4crcnisv5qvwz3niceol.png)

I hope my sharing this time can be useful for the developer friends to create editable cells and tables using Antd-ReactJs. If you're inspired and think this can be implemented in your projects, please do it. For article with Indonesian Language you can read in [this article](https://bayusyaits.medium.com/menggunakan-antd-dan-react-untuk-implement-editable-cells-dan-table-88e6992e127d).

Feel free to ask questions

**Thank You**