DEV Community

Bayu Syaits
Bayu Syaits

Posted on • Edited on

Using Ant Design and React for Editable Cells Implementation and Table

Image description

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',
      },
    ]
  }
Enter fullscreen mode Exit fullscreen mode

*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',
    },
  ]
Enter fullscreen mode Exit fullscreen mode

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>;
};

Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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

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.

Feel free to ask questions

Thank You

Top comments (2)

Collapse
 
schemetastic profile image
Schemetastic (Rodrigo)

Hello! Interesting tutorial.

Welcome to the DEV community!

One little advice, you can highlight your code by placing the name of the code language after placing the initial first 3 backticks when opening a code block. e.g. ` ` `js, ` ` `css

Hope this helps!

Collapse
 
bayusyaits profile image
Bayu Syaits

Hello, thank you for your advice.