Search code examples
javascriptreactjsantd

Antd Design EditableRow not changing buttons from "edit" to "save" and "cancel"


Im using Antd library and i can't seem to find where i have the bug.

This is my EditableTableCell component

import React, {Component} from 'react';
import { Form } from '@ant-design/compatible';
import '@ant-design/compatible/assets/index.css';
import { Input, InputNumber, Select, DatePicker } from "antd";
import moment from "moment";
import {EditableContext} from "./EditableTableRow";

const FormItem = Form.Item;
const Option = Select.Option;
class EditableTableCell extends Component {
    getInput = (record, dataIndex, title, getFieldDecorator) => {
        switch (this.props.inputType) {
            case "number":
                return (
                    <FormItem style={{ margin: 0 }}>
                        {getFieldDecorator(dataIndex, {
                            rules: [
                                {
                                    required: true,
                                    message: `Please Input ${title}!`
                                }
                            ],
                            initialValue: record[dataIndex]
                        })(
                            <InputNumber formatter={value => value} parser={value => value} />
                        )}
                    </FormItem>
                );
            case "date":
                return (
                    <FormItem style={{ margin: 0 }}>
                        {getFieldDecorator(dataIndex, {
                            initialValue: moment(record[dataIndex], this.dateFormat)
                        })(<DatePicker format={this.dateFormat} />)}
                    </FormItem>
                );
            case "select":
                return (
                    <FormItem style={{ margin: 0 }}>
                        {getFieldDecorator(dataIndex, {
                            initialValue: record[dataIndex]
                        })(
                            <Select style={{ width: 150 }}>
                                {[...Array(11).keys()]
                                    .filter(x => x > 0)
                                    .map(c => `Product ${c}`)
                                    .map((p, index) => (
                                        <Option value={p} key={index}>
                                            {p}
                                        </Option>
                                    ))}
                            </Select>
                        )}
                    </FormItem>
                );
            default:
                return (
                    <FormItem style={{ margin: 0 }}>
                        {getFieldDecorator(dataIndex, {
                            rules: [
                                {
                                    required: true,
                                    message: `Please Input ${title}!`
                                }
                            ],
                            initialValue: record[dataIndex]
                        })(<Input />)}
                    </FormItem>
                );
        }
    }
    render() {
        const { editing, dataIndex, title, inputType, record, index,...restProps} = this.props;
        return (
           <EditableContext.Consumer>
               {form => {
                   const { getFieldDecorator } = form;
                   return (
                       <td {...restProps}>
                           {editing ?
                               this.getInput(record, dataIndex, title, getFieldDecorator)
                               : restProps.children}
                       </td>
                   );
               }}
           </EditableContext.Consumer>
        );
    }
}

export default EditableTableCell;

This is my EditableTableCell component

import React, {Component} from 'react';
import { Form} from '@ant-design/compatible';

export const EditableContext = React.createContext();
class EditableTableRow extends Component {
    render() {
        return (
            <EditableContext.Provider value={this.props.form}>
                <tr {...this.props} />
            </EditableContext.Provider>
        );
    }
}

export default EditableTableRow=Form.create()(EditableTableRow);

This is my ProductsPage component im having bug in

import React, {Component} from 'react';
import {Button, Layout, notification, Popconfirm, Space, Table,Typography} from "antd";
import {Link} from "react-router-dom";
import {Content} from "antd/es/layout/layout";
import EditableTableRow, {EditableContext} from "../components/EditableTableRow";
import EditableTableCell from "../components/EditableTableCell";
import API from "../server-apis/api";
import {employeesDataColumns} from "../tableColumnsData/employeesDataColumns";
import {CheckCircleFilled, InfoCircleFilled} from "@ant-design/icons";


class ProductsPage extends Component {
    constructor(props) {
        super(props);
        this.state = {
            data: [],
            error: null,
            isLoaded: false,
            editingKey: "",
            errorMessage: "",
         
        }
    }
    columns = [
        ...employeesDataColumns,
        {
            title: "Actions",
            dataIndex: "actions",
            width: "10%",
            render: (text, record) => {
                const editable = this.isEditing(record);
                return editable ? (
                    <span>
                                <EditableContext.Consumer>
                                  {form => (<a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>Save</a>)}
                                </EditableContext.Consumer>
                                <a onClick={this.cancel}>Cancel</a>
                            </span>
                ) : (
                    <Space size="middle">
                        <a onClick={() => this.edit(record.username)}>Edit</a>
                        <Popconfirm title="Are you sure you want to delete this product?"
                                    onConfirm={() => this.remove(record.username)}>
                            <a style={{color:"red"}}>Delete</a>
                        </Popconfirm>
                    </Space>
                );
            },
        }
    ];

    isEditing = (record) => {
        return record.username === this.state.editingKey;
    };

    edit(username) {
    
        this.setState({editingKey:username});
    }

    cancel = () => {
        this.setState({ editingKey: ""});
    };
    componentDidMount() {
        this.setState({ loading: true });
        const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
        API.get(`users/all`,{ headers: { Authorization: token}})
            .then(res => {
                // console.log(res.data._embedded.productList);
                const employees = res.data._embedded.employeeInfoDtoList;
                this.setState({loading: false,data:employees });
            })
    }
    async remove(username) {
        const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
        API.delete(`/users/${username}`,{ headers: { Authorization: token}})
            .then(() => {
                let updatedProducts = [...this.state.data].filter(i => i.username !== username);
                this.setState({data: updatedProducts});
                this.successfullyAdded("Employee is deleted. It wont have any access to the website anymore.")
            }).catch(()=>this.errorHappend("Failed to delete"));
    }

    hasWhiteSpace(s) {
        return /\s/g.test(s);
    }
    saveData(form,username) {
        form.validateFields((error, row) => {
            if (error) {
                return;
            }
            const newData = [...this.state.data];
            const index = newData.findIndex(item => username === item.username);
            const item = newData[index];
            newData.splice(index, 1, {
                ...item,
                ...row
            });
            const token="Bearer "+ JSON.parse(localStorage.getItem("token"));
            const response = API.put(`/users/${username}/update`, row,{ headers: { Authorization: token}})
                .then((response) => {
                    this.setState({ data: newData, editingKey: ""});
                    this.successfullyAdded("Empolyee info is updated")
                })
                .catch(error => {
                    this.setState({ errorMessage: error.message });
                    this.errorHappend("Failed to save changes.")
                    console.error('There was an error!', error);
                });
        });
    }
    successfullyAdded = (message) => {
        notification.info({
            message: `Notification`,
            description:message,
            placement:"bottomRight",
            icon: <CheckCircleFilled style={{ color: '#0AC035' }} />
        });
    };
    errorHappend = (error) => {
        notification.info({
            message: `Notification`,
            description:
                `There was an error! ${error}`,
            placement:"bottomRight",
            icon: <InfoCircleFilled style={{ color: '#f53333' }} />
        });
    };
    render() {
        const components = {
            body: {
                row: EditableTableRow,
                cell: EditableTableCell
            }
        };
        const columns = this.columns.map(col => {
            if (!col.editable) {
                return col;
            }
            return {
                ...col,
                onCell: record => {
                    const checkInput = index => {
                        switch (index) {
                            case "price":
                                return "number";
                            default:
                                return "text";
                        }
                    };
                    return {
                        record,
                        // inputType: col.dataIndex === "age" ? "number" : "text",
                        inputType: checkInput(col.dataIndex),
                        dataIndex: col.dataIndex,
                        title: col.title,
                        editing: this.isEditing(record)
                    };
                }
            };
        });
        const { data, loading } = this.state;
        return (
            <Layout>
                <div>
                    <Link to="/add-product">
                        <Button style={{float:"right", background: "#0AC035",marginBottom:"1em", marginTop:"1em" }}
                                type="primary">New emplyee</Button>
                    </Link>
                </div>
                <Content>
                
                    <Table components={components} bordered dataSource={data} columns={columns} loading={loading} rowKey={data.username} rowClassName="editable-row"/>
                </Content>
            </Layout>
        );
    }
}

export default ProductsPage;

This is the bug I'm having: enter image description here

And i want to have this result like its shown in Antd docs: enter image description here

Id really appreciate if you take a look and help me figure out where im wrong


Solution

  • Updated Solution: I find the issue. In render where you map the columns, you just return the column if it's not an editable column. You can check the code below. I added a check if it's dataIndex === 'actions', then return the following code: Please Follow the link: https://react-ts-v3fbst.stackblitz.io

    Changes:

    1.In columns, i remove the render function from the action object:

    {
          title: 'Actions',
          dataIndex: 'actions',
          width: '10%',
    },
    

    2. In render function where you map the columns, add the following code before this condition if(!col.editable) {,:

    
    
    if (col.dataIndex === 'actions') {
        return {
            ...col,
            render: (text, record) => {
                const editable = this.isEditing(record);
                return editable ? (
                    <span>
                        <EditableContext.Consumer>
                            {(form) => (
                                <a onClick={() => this.saveData(form, record.username)} style={{ marginRight: 8 }}>
                                    Save
                                </a>
                            )}
                        </EditableContext.Consumer>
                        <a onClick={this.cancel}>Cancel</a>
                    </span>
                ) : (
                    <Space size='middle'>
                        <a onClick={() => this.edit(record.username)}>Edit</a>
                        <Popconfirm title='Are you sure you want to delete this product?' onConfirm={() => this.remove(record.username)}>
                            <a style={{ color: 'red' }}>Delete</a>
                        </Popconfirm>
                    </Space>
                );
            }
        };
    }
    

    When you click on edit, you set the username as key for that particular row for editing, make sure you have username in each record. I tested this using the following data:

    const data = [
        { id: 8, name: 'baun', model: '2022', color: 'black', price: 358, quantity: 3, username: 'brvim' },
        { id: 3, name: 'galileo', model: '20221', color: 'white', price: 427, quantity: 7, username: 'john' }
    ];
    

    Most important, you should select that attribute as key that is unique in all records. As you are using username, i don't know what is your business logic or data looks like, but technically each record can have same username. So you must select something that would always be unique in your complete data.