react-querybuilder 的额外添加规则按钮

IT技术 javascript reactjs typescript
2022-08-01 01:47:54

我正在使用react-querybuilder我需要的是Add Rule在原始按钮旁边添加另一个按钮,并希望在使用新按钮时添加不同的字段和运算符集。这是我的代码的一部分:

import { HBButton, HBIcon } from '@hasty-bazar/core'
import { FC } from 'react'
import { useIntl } from 'react-intl'
import queryBuilderMessages from '../HBQueryBuilder.messages'

interface AddRuleActionProps {
  handleOnClick: (event: React.MouseEvent<HTMLButtonElement, MouseEvent>) => void
}

const AddGroupAction: FC<AddRuleActionProps> = ({ handleOnClick }) => {
  const { formatMessage } = useIntl()
  return (
    <>
    <HBButton
      onClick={handleOnClick}
      size="small"
      leftIcon={<HBIcon type="plus" />}
      sx={{ marginRight: 2, minWidth: 50 }}
    >
      {formatMessage(queryBuilderMessages.rule)}
    </HBButton>
   // >>> ANOTHER HBButton with different implementation to be added here 
    </>
  )
}

export default AddGroupAction
2个回答

根据您的反馈添加新答案,因为这个答案与另一个答案非常不同。我即将发布的 v5.0react-querybuilder具有我在另一个答案的第一段中提到的功能。这使得实现期望的结果更加直接,并且还消除了对外部状态管理(即 Redux)的需要。

TL;DR:此处的工作代码和框示例(使用react-querybuilder@5.0.0-alpha.2)。

React Query Builder 只需要一个fieldsprops,但您可以将字段组织成一个选项组数组,而不是一个平面数组。我将operators每个字段的属性设置为默认运算符,并针对字段类型(文本与数字)进行适当过滤。

import { Field, OptionGroup } from 'react-querybuilder';
import { nameOperators, numberOperators } from './operators';

export const fields: OptionGroup<Field>[] = [
  {
    label: 'Names',
    options: [
      { name: 'firstName', label: 'First Name', operators: nameOperators },
      { name: 'lastName', label: 'Last Name', operators: nameOperators },
    ],
  },
  {
    label: 'Numbers',
    options: [
      { name: 'height', label: 'Height', operators: numberOperators },
      { name: 'weight', label: 'Weight', operators: numberOperators },
    ],
  },
];

接下来,我设置了一个自定义字段选择器组件,以仅允许属于同一选项组的字段。因此,如果选择了“名称”字段,则用户只能选择其他“名称”字段。

const FilteredFieldSelector = (props: FieldSelectorProps) => {
  const filteredFields = fields.find((optGroup) =>
    optGroup.options.map((og) => og.name).includes(props.value!)
  )!.options;

  return <ValueSelector {...{ ...props, options: filteredFields }} />;
};

这个自定义的添加规则按钮为每个调用带有选项组作为上下文的handleOnClickprops的选项组呈现一个单独的按钮。label

const AddRuleButtons = (props: ActionWithRulesAndAddersProps) => (
  <>
    {fields
      .map((og) => og.label)
      .map((lbl) => (
        <button onClick={(e) => props.handleOnClick(e, lbl)}>
          +Rule ({lbl})
        </button>
      ))}
  </>
);

然后context将 传递给onAddRule回调,该回调根据上下文值确定field要分配的内容。

const onAddRule = (
  rule: RuleType,
  _pP: number[],
  _q: RuleGroupType,
  context: string
) => ({
  ...rule,
  context,
  field: fields.find((optGroup) => optGroup.label === context)!.options[0].name,
});

把它们放在QueryBuilderprops中,

export default function App() {
  const [query, setQuery] = useState(initialQuery);

  return (
    <div>
      <QueryBuilder
        fields={fields}
        query={query}
        onQueryChange={(q) => setQuery(q)}
        controlElements={{
          addRuleAction: AddRuleButtons,
          fieldSelector: FilteredFieldSelector,
        }}
        onAddRule={onAddRule}
      />
      <pre>{formatQuery(query, 'json')}</pre>
    </div>
  );
}

更新:见我的另一个答案

这有点棘手,因为onAddRule回调函数只接受要添加的规则(始终是默认规则)和父路径。如果我们可以将自定义数据传递给它,这个问题会更容易回答。

我今天能想到的最好方法是将查询更新方法外部化到QueryBuilder组件之外并自己管理它们(大部分情况下)。在下面的示例中,我使用 Redux Toolkit(对于这个用例来说有点过分了,但这是我熟悉的)来管理查询,并将 Add Rule 按钮替换为呈现两个按钮的自定义组件,一个用于添加新的名字规则和一个为姓氏添加新规则的规则。

工作 CodeSandbox 示例

redux 商店:

import { configureStore, createSlice, PayloadAction } from '@reduxjs/toolkit';
import { RuleGroupType } from 'react-querybuilder';
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';

interface State {
  query: RuleGroupType;
}

export const getQuery = (state: State) => state.query;

const initialState: State = {
  query: {
    combinator: 'and',
    rules: [],
  },
};

const querySlice = createSlice({
  name: 'query',
  initialState,
  reducers: {
    setQuery(state: State, action: PayloadAction<RuleGroupWithAggregation>) {
      state.query = action.payload;
    },
  },
});

const { reducer } = querySlice;

export const { setQuery } = querySlice.actions;

export const store = configureStore({ reducer });

export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;

export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

应用程序组件:

import {
  ActionWithRulesProps,
  add,
  Field,
  formatQuery,
  QueryBuilder,
} from 'react-querybuilder';
import 'react-querybuilder/dist/query-builder.scss';
import { getQuery, setQuery, useAppDispatch, useAppSelector } from './store';

const fields: Field[] = [
  { name: 'firstName', label: 'First Name' },
  { name: 'lastName', label: 'Last Name' },
];

const AddRuleButtons = (props: ActionWithRulesProps) => {
  const dispatch = useAppDispatch();
  const query = useAppSelector(getQuery);

  const onClickFirst = () =>
    dispatch(
      setQuery(
        add(
          query,
          { field: 'firstName', operator: '=', value: 'First' },
          props.path
        )
      )
    );
  const onClickLast = () =>
    dispatch(
      setQuery(
        add(
          query,
          { field: 'lastName', operator: '=', value: 'Last' },
          props.path
        )
      )
    );

  return (
    <>
      <button onClick={onClickFirst}>+Rule (First Name)</button>
      <button onClick={onClickLast}>+Rule (Last Name)</button>
    </>
  );
};

export default function App() {
  const dispatch = useAppDispatch();
  const query = useAppSelector(getQuery);

  return (
    <div>
      <QueryBuilder
        fields={fields}
        query={query}
        onQueryChange={(q) => dispatch(setQuery(q))}
        controlElements={{
          addRuleAction: AddRuleButtons,
        }}
      />
      <pre>{formatQuery(query, 'json')}</pre>
    </div>
  );
}