react, web development, table, crud, font end, axios, api,

MUIDataTable实现CRUD增删改查并使用axios请求api

DolorHunter DolorHunter Follow May 09, 2021 · 15 mins read
MUIDataTable实现CRUD增删改查并使用axios请求api
Share this

之前做 课设项目 时,因为DDL突然提前而又找不到数据表格的模板,于是就自己用纯HTML手写了一个表格页面,实现CRUD增删改查。实现过程记录于 Django框架下SQLite3数据库CRUD增删改查的编写

这次因为是做 毕设项目,因此还是比较认真的找了个比较完整的模板 flatlogic/react-material-admin,再根据这个模板进行修改。

写在前面:

如果你只是想找到一个MUITable的CRUD实现模板,请直接参考 Medium - Material-Table AutoFocus on Row Add 中的 App.js,实现简单效果也更好。MUI的文档很乱,尤其DataTable资料非常少,请用Table实现。

表格模板 中只从前端页面实现了数据的查看与删除,因此修改和添加都要加入,并且还要连接这些功能与后端接口的连接。

demo

MUITable/MUIDataTable/MUIGrid 如果只用来实现查看功能调用的代码还是比较简单的。如果不打算做些进阶操作,MUI标签中写入data,columns属性就行了。

const datatableData = [
  ["Joe James", "Example Inc.", "Yonkers", "NY"],
  ["John Walsh", "Example Inc.", "Hartford", "CT"],
  ["Bob Herm", "Example Inc.", "Tampa", "FL"],
];

export default function Tables() {
  const classes = useStyles();
  return (
    <MUIDataTable
      title="Employee List"
      data={datatableData}
      columns={["Name", "Company", "City", "State"]}
      options={{
        filterType: "checkbox",
      }}
    />
  );
}

但是我查了下资料,并没有看到其他人有讲到MUIDataTable 的CRUD实现的示例,有人有在Issue提过要一个CRUD的example但是被踩了回来。既然没找到,那我就自己写好了,而且MUIDataTable/MUIGrid都是MUITable变种,因此有些接口也许还能互通。

查看数据

查看的实现比较简单,因为模板内也有这个功能。因此只要把datatableData的内容设置为请求接口返回的结果,再渲染一下就大功告成。

MUI大部分模板都是使用Hook,使用useState声明变量(相当于Component的state),useEffect调用接口(相当于componentDidMount)。获得数据后简单判断下是否请求成功,成功则使用set方法设置数据。「使用set(包括setState)都会自动触发渲染。」

export default function Tables() {
  const classes = useStyles();

  // new line start
  var [datatableData, setDatatableData] = useState([]);

  useEffect(() => {
    axios.post('/api/url', { data })
    .then(res => {
      if (res.status === 200 && Object.keys(res.request.response).length > 0) {
      const data = JSON.parse(res.request.response);
      setDatatableData(data);
      }
    })
  }, [])
  // new line end

  return (
    <MUIDataTable
      title="Employee List"
      data={datatableData}
      columns={["Name", "Company", "City", "State"]}
      options={{
        filterType: "checkbox",
      }}
    />
  );
}

删除数据

模板在选中行后会出现删除按钮,并且可以成功删除。但是一旦刷新页面,数据就又恢复了。因此还需要连接接口,让数据真的被删除。

查找资料后在option中,发现有个名为onRowsDelete的属性,刚好可以满足此需求。data参数内的data.Index属性为网页列表的位置,通过index找到此行数据的id,进行删除。如果批量选中,则data.data为多个Object。

export default function Tables() {
  const classes = useStyles();

  var [datatableData, setDatatableData] = useState([]);

  useEffect(() => {
    axios.post('/api/url', { data })
      .then(res => {
        if (res.status === 200 && Object.keys(res.request.response).length > 0) {
          const data = JSON.parse(res.request.response);
          setDatatableData(data);
        }
      })
  }, [])

  return (
    <MUIDataTable
      title="Employee List"
      data={datatableData}
      // new line start
      // ID段数据对于删除数据来说很重要
      columns={["id", "Name", "Company", "City", "State"]}
      // new line end
      options={{
        filterType: "checkbox",
        // new line start
        onRowsDelete: (data) => {
          for (var i = 0; i < data.data.length; ++i) {
            var index = data.data[i].dataIndex;
            var id = datatableData[index].id;
            axios.post('/api/url', { id: id })
              .then(res => {
                if (res.request.response !== "Succeed.") {
                  console.log(res.request.response);
                }
              })
          }
        }
        // new line end
      }}
    />
  );
}

修改数据

修改数据就没找到比较好的方案。我设想是可以类似删除,在选中后出现编辑按钮,点击编辑变为可编辑状态,之后再点保存。就像 Medium - Material-Table AutoFocus on Row Add 实现的那样。

不过mui table的example里面是要你再新建一列来写交互按钮,包括修改/保存/添加/删除。这些功能的按钮,有些已经能在Toolbar上看到了,因此感觉mui的文档也是挺迷的。新功能倒是开发了很多,文档却没怎么维护,翻来翻去就是拿几篇基础文。

那我只能把每个可修改的column全都变为可编辑状态,并且随着内容的变化动态更新,然后在点击保存后传修改命令给后端接口。

P.s.「懒得自己写column点击编辑按钮状态再变可编辑,因为column option的重写量比较大,因此就没有这么操作(虽然在添加数据时发现还是跑不掉)。在option内写customBodyRender/customBodyRenderLite就能完成这个操作。」

P.s. 我是直接在OnChange修改state内的值(因为这么写比较短),这个用法不被推荐会报warning,推荐还是用set。

export default function Tables() {
  const classes = useStyles();

  var [datatableData, setDatatableData] = useState([]);
  // new line start
  var [columns, setColumns] = useState([
    // ID段数据对于修改数据来说很重要
    {
      name: "id",
      label: "ID",
      options: {
        display: "excluded",
      },
    },
    // 如果是可修改数据,需要重写customBodyRender变为可编辑
    {
      name: "name",
      label: "Name",
      options: {
        customBodyRender: (value, tableMeta, updateValue) => {
          const rowId = tableMeta.rowIndex;
          return (
            <FormControlLabel
              value={value}
              control={<TextField value={value} />}
              onChange={e => {
                updateValue(e.target.value);
                columns.datatableData[rowId].username = e.target.value;
              }}
            />
          )
        }
      }        
    },
    // 如果不可修改,不用写customBodyRender
    {
      name: "company",
      label: "Company",
    }
    // City, State类似就不写了
    // 编辑按钮
    {
        name: "edit",
        label: "编辑",
        options: {
          filter: false,
          sort: false,
          empty: true,
          customBodyRenderLite: (dataIndex) => {
            return (
              <Button
                variant="contained"
                color="primary"
                size="large"
                startIcon={<SaveIcon />}
                // updateRow是调用axios更新数据的函数,函数在代码段结尾
                onClick={() => updateRow(datatableData[dataIndex])}
              >
                保存
              </Button>
            );
          }
        }
      },
  ]);
  // new line end

  useEffect(() => {
    axios.post('/api/url', { data })
      .then(res => {
        if (res.status === 200 && Object.keys(res.request.response).length > 0) {
        const data = JSON.parse(res.request.response);
        setDatatableData(data);
        }
      })
  }, [])

  return (
    <MUIDataTable
      title="Employee List"
      data={datatableData}
      columns={columns}
      options={{
        filterType: "checkbox",
        onRowsDelete: (data) => {
          for (var i = 0; i < data.data.length; ++i) {
            var index = data.data[i].dataIndex;
            var id = datatableData[index].id;
            axios.post('/api/url', { id: id })
              .then(res => {
                if (res.request.response !== "Succeed.") {
                  console.log(res.request.response);
                }
              })
          }
        }
      }}
    />
  );
}
// new line start
// #################################################################
function updateRow(row) {
  var data;
  if (row.name !== null) {
    data = {
      id: row.id,
      name: row.name
    }
    axios.post('/api/url', data)
      .then(res => {
        if (res.request.response !== "Succeed.") {
          console.log(res.request.response);
        }
      })
  }
}
// new line end

添加数据

在上一部使用重写column的customBodyRender,实现了编辑数据并且请求修改功能。添加数据则设想在Toolbar上加入一个添加按钮,点击在首行出现空行用于填写数据,填写内容后点击添加。

P.s. 有些不可修改的数据段需要具备写功能用于添加,因此这边需要写一个if来区分是调用更新还是添加,判断条件定为是否有ID。(还没被添加到数据库的数据无ID)。

export default function Tables() {
  const classes = useStyles();

  var [datatableData, setDatatableData] = useState([]);
  var [columns, setColumns] = useState([
    {
      name: "id",
      label: "ID",
      options: {
        display: "excluded",
      },
    },
    // 原本可编辑的数据段不用修改
    {
      name: "name",
      label: "Name",
      options: {
        customBodyRender: (value, tableMeta, updateValue) => {
          const rowId = tableMeta.rowIndex;
          return (
            <FormControlLabel
              value={value}
              control={<TextField value={value} />}
              onChange={e => {
                updateValue(e.target.value);
                columns.datatableData[rowId].username = e.target.value;
              }}
            />
          )
        }
      }        
    },
    // 不可编辑的数据段需要判断是否为添加,是则可编辑。条件为是否有ID
    {
      name: "company",
      label: "Company",
      // new line start
      options: {
        customBodyRender: (value, tableMeta, updateValue) => {
          const rowId = tableMeta.rowIndex;
          if ('id' in datatableData[rowId]) {
            return (
              <FormLabel>
                {value}
              </FormLabel>
            )
          } else {
            return (
              <FormControlLabel
                value={value}
                control={<TextField value={value} />}
                onChange={e => {
                  updateValue(e.target.value);
                  datatableData[rowId].company = e.target.value;
                }}
              />
            )
          }
        }
      }
      // new line end
    }
    // City, State类似就不写了
    // 编辑按钮也要做判断,判断是修改功能还是添加功能。
    {
        name: "edit",
        label: "编辑",
        options: {
          filter: false,
          sort: false,
          empty: true,
          customBodyRenderLite: (dataIndex) => {
            // new line start
            if ('id' in datatableData[dataIndex]) {
              return (
                <Button
                  variant="contained"
                  color="primary"
                  size="large"
                  startIcon={<SaveIcon />}
                  onClick={() => updateRow(datatableData[dataIndex])}
                >
                  保存
                </Button>
              );
            } else {
              return (
                <Button
                  variant="contained"
                  color="secondary"
                  size="large"
                  startIcon={<PublishIcon />}
                  onClick={() => {
                    // appendRow是调用axios添加数据的函数,函数在代码段结尾
                    appendRow(datatableData[dataIndex]);
                    // 添加数据后用set重新渲染效果不好,因此强制重载刷新表格(不推荐)
                    window.location.reload();
                  }}
                >
                  添加
                </Button>
              );
            }
            // new line end
          }
        }
      },
  ]);

  useEffect(() => {
    axios.post('/api/url', { data })
      .then(res => {
        if (res.status === 200 && Object.keys(res.request.response).length > 0) {
        const data = JSON.parse(res.request.response);
        setDatatableData(data);
        }
      })
  }, [])

  return (
    <MUIDataTable
      title="Employee List"
      data={datatableData}
      columns={columns}
      options={{
        filterType: "checkbox",
        onRowsDelete: (data) => {
          for (var i = 0; i < data.data.length; ++i) {
            var index = data.data[i].dataIndex;
            var id = datatableData[index].id;
            axios.post('/api/url', { id: id })
              .then(res => {
                if (res.request.response !== "Succeed.") {
                  console.log(res.request.response);
                }
              })
          }
        }
      }}
    />
  );
}
// #################################################################
function updateRow(row) {
  var data;
  if (row.name !== null) {
    data = {
      id: row.id,
      name: row.name,
      company: row.company,
      ...
    }
    axios.post('/api/url', data)
      .then(res => {
        if (res.request.response !== "Succeed.") {
          console.log(res.request.response);
        }
      })
  }
}
// new line start
function appendRow(row) {
  var data = {
    name: row.name,
    company: row.company,
    ...
  }
  axios.post('/api/url', data)
    .then(res => {
      if (res.request.response !== "Succeed.") {
        alert(res.request.response);
      }
    })
}
// new line end

总结

这个MUIDataTable实现CRUD增删改查的写法也不是最好的写法,其中有很多问题,如:

  1. 手动修改数据内容而不用set修改;
  2. 无编辑按钮让文本从标签变为编辑状态,而是直接可编辑;
  3. 添加数据后强制刷新(数据量大情况下重载耗时很长);
  4. 因为没用set渲染时已经删掉的数据可能还会跑出来;
  5. 点击按钮反馈不强(无加载或是其他动画);
  6. 代码写的也不优雅(一开始还行,发现要重写那么多customBodyRender后就破罐破摔了)。

但是总算是完成MUIDataTable CRUD功能的基本实现了。

参考资料:

Join Newsletter
Get the latest news right in your inbox. We never spam!
DolorHunter
Written by DolorHunter
Developer & Independenet Blogger