React TanStack

使用React Tanstack验证表格的行和字段

了解如何使用React Tanstack验证表格的输入字段、选择字段和实现自定义验证。

2024年2月15日
React-Table-Validation
分享

上一部分中,我们学习了如何使用 React Tanstack 添加和删除表格的行。在本文中,我们将学习如何验证表格的行和字段,例如必填的输入、日期、选择和其他自定义验证。

下面展示了我们希望在本教程结束时表格的外观和行为:

tanstack3-1

输入验证

在本节中,我们将利用原生的 HTML5 输入框验证。我们将使用required属性来验证必填字段。我们还将使用pattern属性来验证更复杂的场景。

必填字段

添加required属性是验证必填字段的简单方法。该属性是布尔值,这意味着它不需要值。它只需要出现在元素中即可验证该字段。

我们可以直接将其添加到TableCell组件中的输入元素中。

<input
  value={value}
  onChange={e => setValue(e.target.value)}
  onBlur={onBlur}
  type={columnMeta?.type || "text"}
  required
/>

此外,当触发验证时,:invalid伪类将被添加到输入元素。我们可以使用这个伪类来设置输入元素的样式。

input:invalid {
  border: 2px solid red;
}

现在,如果我们尝试下面的演示,我们将看到空字段用红色边框突出显示。

tanstack3-2

我们上面使用的方法很简单。然而,它有一个局限性。所有字段都将被验证,即使是那些不需要的字段。为了解决这个问题,我们将在column.ts文件中的列定义中添加一个required属性。

...

export const columns = [
  ...

  columnHelper.accessor('name', {
    header: 'Full Name',
    cell: TableCell,
    meta: {
      type: 'text',
      required: true,
    },
  })

...
]

然后,在TableCell组件中,我们将检查required属性是否存在于columnMeta对象中。这样,只有具有该required属性的字段才会被验证。

...

export const TableCell = ({ getValue, row, column, table }) => {
  ...

  const columnMeta = column.columnDef.meta;

  ...

  if (tableMeta?.editedRows[row.id]) {
    return columnMeta?.type === "select" ? (
      <select
        onChange={onSelectChange}
        value={initialValue}
      >
        {columnMeta?.options?.map((option: Option) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
    ) : (
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        onBlur={onBlur}
        type={columnMeta?.type || "text"}
        required={columnMeta?.required}
      />
    );
  }
  return <span>{value}</span>;
};
tanstack3-3

上面的演示显示了如何仅在“name”字段上触发required属性。

选择验证

选择验证与之前的验证类似。我们将使用required属性来验证select字段,但我们必须向 select 元素添加一个空选项才能使其正常工作。此外,我们将使用:invalid伪类来设置select元素的样式。

...

export const columns = [
  ...

  columnHelper.accessor('major', {
    header: 'Major',
    cell: TableCell,
    meta: {
      type: 'select',
      required: true,
      options: [
        { value: '', label: 'Select' },
        { value: 'Computer Science', label: 'Computer Science' },
        { value: 'Communications', label: 'Communications' },
        { value: 'Business', label: 'Business' },
        { value: 'Psychology', label: 'Psychology' },
      ],
    },
  }),
  columnHelper.display({
    id: 'edit',
    cell: EditCell,
  }),
]
...

export const TableCell = ({ getValue, row, column, table }) => {
  ...

  const columnMeta = column.columnDef.meta;

  ...

  if (tableMeta?.editedRows[row.id]) {
    return columnMeta?.type === "select" ? (
      <select
        onChange={onSelectChange}
        value={initialValue}
        required={columnMeta?.required}
      >
        {columnMeta?.options?.map((option: Option) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
    ) : (
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        onBlur={onBlur}
        type={columnMeta?.type || "text"}
        required={columnMeta?.required}
      />
    );
  }
  return <span>{value}</span>;
};
select:invalid, input:invalid {
  border: 2px solid red;
}

让我们在下面的演示中测试一下。当将“major”字段更改为“Select”时,我们将看到该字段以红色边框突出显示。

tanstack3-4

模式验证

pattern验证使用正则表达式来验证输入字段。“name”字段就是一个很好的例子。我们想要验证名称仅包含字母和空格。

让我们引入一个在column.ts文件中的新属性pattern,它有正则表达式值。

...

export const columns = [
  ...

  columnHelper.accessor('name', {
    header: 'Full Name',
    cell: TableCell,
    meta: {
      type: 'text',
      required: true,
      pattern: '^[a-zA-Z ]+$',
    },
  }),

  ...
]

然后,在TableCell组件中,我们将pattern属性添加到输入元素。该值将来自columnMeta对象。

...

export const TableCell = ({ getValue, row, column, table }) => {
  ...

  const columnMeta = column.columnDef.meta;

  ...

  if (tableMeta?.editedRows[row.id]) {
    return columnMeta?.type === "select" ? (
      <select
        onChange={onSelectChange}
        value={initialValue}
        required={columnMeta?.required}
      >
        {columnMeta?.options?.map((option: Option) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
    ) : (
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        onBlur={onBlur}
        type={columnMeta?.type || "text"}
        required={columnMeta?.required}
        pattern={columnMeta?.pattern}
      />
    );
  }
  return <span>{value}</span>;
};

现在,在下面的演示中,我们将看到“name”字段使用正则表达式模式进行验证,并且仅接受字母和空格。

tanstack3-5

显示验证消息

前面,我们在触发验证时为输入元素添加了红色边框,但没有显示任何验证消息。

要显示验证消息,我们可以使用输入元素的title属性来在用户将鼠标悬停在输入元素上时显示验证消息。

然后,我们需要向TableCell组件添加一个新状态来存储验证消息和一个新函数来设置值。onBluronChange事件都会触发该函数。

以下是TableCell组件的完整代码。

import { useState, useEffect, ChangeEvent } from "react";
import "./table.css";

type Option = {
  label: string;
  value: string;
};

export const TableCell = ({ getValue, row, column, table }) => {
  const initialValue = getValue();
  const columnMeta = column.columnDef.meta;
  const tableMeta = table.options.meta;
  const [value, setValue] = useState(initialValue);
  const [validationMessage, setValidationMessage] = useState("");

  useEffect(() => {
    setValue(initialValue);
  }, [initialValue]);

  const onBlur = (e: ChangeEvent<HTMLInputElement>) => {
    displayValidationMessage(e);
    tableMeta?.updateData(row.index, column.id, value);
  };

  const onSelectChange = (e: ChangeEvent<HTMLSelectElement>) => {
    displayValidationMessage(e);
    setValue(e.target.value);
    tableMeta?.updateData(row.index, column.id, e.target.value);
  };

  const displayValidationMessage = <
    T extends HTMLInputElement | HTMLSelectElement
  >(
    e: ChangeEvent<T>
  ) => {
    if (e.target.validity.valid) {
      setValidationMessage("");
    } else {
      setValidationMessage(e.target.validationMessage);
    }
  };

  if (tableMeta?.editedRows[row.id]) {
    return columnMeta?.type === "select" ? (
      <select
        onChange={onSelectChange}
        value={initialValue}
        required={columnMeta?.required}
        title={validationMessage}
      >
        {columnMeta?.options?.map((option: Option) => (
          <option key={option.value} value={option.value}>
            {option.label}
          </option>
        ))}
      </select>
    ) : (
      <input
        value={value}
        onChange={(e) => setValue(e.target.value)}
        onBlur={onBlur}
        type={columnMeta?.type || "text"}
        required={columnMeta?.required}
        pattern={columnMeta?.pattern}
        title={validationMessage}
      />
    );
  }
  return <span>{value}</span>;
};

现在,在下面的演示中,如果我们在“name”字段中输入无效值并将鼠标悬停在其上,我们将看到验证消息。

tanstack3-6

请注意,由于样式选项有限,使用title属性可能不是显示验证消息的最佳方式。我们可以创建自定义工具提示或使用第三方库(例如react-tooltip),但这超出了本教程的范围。

自定义验证

当我们想要使用更复杂的逻辑来验证字段并且使用正则表达式可能会增加代码的复杂性时,自定义验证非常有用。我们以日期为例。我们想要验证该日期不是将来的日期。

为此,我们将添加一个在column.ts文件中的新属性validate,其中包含具有验证逻辑的回调函数。该函数将返回一个布尔值。此外,我们将添加一个名为validationMessage的新属性来显示自定义验证消息。

...

export const columns = [
  ...

  columnHelper.accessor('date', {
    header: 'Date',
    cell: TableCell,
    meta: {
      type: 'date',
      required: true,
      validate: (value: string) => {
        const date = new Date(value);
        const today = new Date();
        return date <= today;
      },
      validationMessage: 'Date cannot be in the future',
    },
  }),

  ...
]

然后,在displayValidationMessage函数中,我们将检查validate属性是否存在于columnMeta对象中。如果是,我们将调用该validate函数并设置新的validationMessage状态。

import { useState, useEffect, ChangeEvent } from "react";
import "./table.css";

type Option = {
  label: string;
  value: string;
};

export const TableCell = ({ getValue, row, column, table }) => {
  ...

  const displayValidationMessage = <
    T extends HTMLInputElement | HTMLSelectElement
  >(
    e: ChangeEvent<T>
  ) => {
    if (columnMeta?.validate) {
      const isValid = columnMeta.validate(e.target.value);
      if (isValid) {
        e.target.setCustomValidity("");
        setValidationMessage("");
      } else {
        e.target.setCustomValidity(columnMeta.validationMessage);
        setValidationMessage(columnMeta.validationMessage);
      }
    } else if (e.target.validity.valid) {
      setValidationMessage("");
    } else {
      setValidationMessage(e.target.validationMessage);
    }
  };

  ...
  }
  return <span>{value}</span>;
};

请注意,我们使用了setCustomValidity函数。由于我们使用的是原生 HTML5 验证,因此我们需要使用setCustomValidity函数来触发样式的:invalid伪类。

在下面的演示中,我们将看到如果日期是将来的日期,则使用自定义验证来验证“date”字段。

tanstack3-7

行验证

当我们想要验证整个行时,行验证非常有用。例如,如果行中存在无效字段,我们希望禁用保存按钮,以避免保存无效数据。

由于保存按钮位于不同的组件中,因此我们需要在Table组件中添加一个新的validRows状态来存储有效行并将其传递给表的元信息。此外,我们将更新updateData函数中的validRows状态,该函数将有一个新的isValid参数。

import { useState } from "react";
import { Student, data as defaultData } from "./data";
import "./table.css";

import {
  flexRender,
  getCoreRowModel,
  useReactTable,
} from "@tanstack/react-table";
import { columns } from "./columns";
import { FooterCell } from "./FooterCell";

export const Table = () => {
  ...

  const [validRows, setValidRows] = useState({});

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    enableRowSelection: true,
    meta: {
      ...
      validRows,
      setValidRows,
      ...
      updateData: (rowIndex: number, columnId: string, value: string, isValid: boolean) => {
        setData((old) =>
          old.map((row, index) => {
            if (index === rowIndex) {
              return {
                ...old[rowIndex],
                [columnId]: value,
              };
            }
            return row;
          })
        );
        setValidRows((old) => ({
          ...old,
          [rowIndex]: { ...old[rowIndex], [columnId]: isValid },
        }));
      },
      ...
    },
  });
  ...
};

快速说明下setValidRows正在做什么,它通过向对象添加新的键值对来更新validRows状态。键是行索引,值是以列 id 为键、验证状态为值的对象。

下面是validRows状态的 JSON 表示。

{
  "0": {
    "name": true,
    "major": true,
    "date": false
  },
  "1": {
    "name": true,
    "major": false,
    "date": true
  },
  ...
}

现在updateData有了新的isValid参数,我们需要更新TableCell组件以将isValid参数传递给updateData函数。

...

export const TableCell = ({ getValue, row, column, table }) => {
  ...

  const onBlur = (e: ChangeEvent<HTMLInputElement>) => {
    displayValidationMessage(e);
    tableMeta?.updateData(row.index, column.id, value, e.target.validity.valid);
  };

  const onSelectChange = (e: ChangeEvent<HTMLSelectElement>) => {
    displayValidationMessage(e);
    setValue(e.target.value);
    tableMeta?.updateData(row.index, column.id, e.target.value, e.target.validity.valid);
  };

  ...
};

最后,如果当前行中有无效字段,我们将更新EditCell组件以禁用保存按钮。我们还将设置保存按钮的样式以提示它已被禁用。

import { MouseEvent } from "react";

export const EditCell = ({ row, table }) => {
  const meta = table.options.meta;
  const validRow = meta?.validRows[row.id];
  const disableSubmit = validRow ? Object.values(validRow)?.some(item => !item) : false;

  ...

  return (
    <div className="edit-cell-container">
      {meta?.editedRows[row.id] ? (
        <div className="edit-cell-action">
          <button onClick={setEditedRows} name="cancel">
            ⚊
          </button>{" "}
          <button onClick={setEditedRows} name="done" disabled={disableSubmit}>
            ✔
          </button>
        </div>
      ) : (
        <div className="edit-cell-action">
          <button onClick={setEditedRows} name="edit">
            ✐
          </button>
          <button onClick={removeRow} name="remove">
            X
          </button>
        </div>
      )}
      <input
        type="checkbox"
        checked={row.getIsSelected()}
        onChange={row.getToggleSelectedHandler()}
      />
    </div>
  );
};
table .edit-cell-action button[name='done']:disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

这是表格的最终演示,当我们尝试保存无效行时,我们将看到保存按钮被禁用。

tanstack3-8

完整代码

完整的代码可以在这个仓库中找到。如果您喜欢本教程,请为仓库加星,并随时提出新功能的需求!

总结

在这一部分中,我们学习了如何使用 React Tanstack 验证表格的行和字段。我们学习了如何验证必填字段、选择字段和自定义验证。我们还学习了如何显示验证消息并在行中存在无效字段时禁用保存按钮。

来自:https://muhimasri.com/blogs/validate-react-table/

更多文章

使用React TanStack实现有多行选择的表格的添加和删除行功能的指南。

2024年2月15日 · React TanStack
React-Editable-Table
使用TanStack的动态列模式创建React可编辑表格单元格和行的综合指南。
2024年2月14日 · React TanStack
refine
用于构建内部工具、管理面板、仪表板和B2B应用程序的React框架,具有无与伦比的灵活性。
2024年2月4日 · React
indexed-pages
48小时内让您的网站在谷歌上建立索引的脚本。
2024年2月2日 · SEO 谷歌