微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

受控 Fluent UI Checkbox 组件的渲染不正确

如何解决受控 Fluent UI Checkbox 组件的渲染不正确

我有一个通过 REST API 与数据库交互的表单,并提供了几个受控的 Fluent UI 组件。对于多选字段,我构建了一个组件,该组件显示具有任意数量受控复选框组件的 Stack。下面是组件定义。

class MultiChoiceField extends React.Component
{
  static contextType = FormContext;
  static displayName = "MultiChoiceField";

  #handlers = { change: {} };

  /**
   * Initializes the component using the information provided in the {@link Item} provided by the {@link FormContext}.
   * @constructor
   * @param {Object} props The properties provided for this component.
   */
  constructor(props)
  {
    super(props);
    this.state = { value: {} };
  }

  /**
   * Set up the component once it is added to the DOM. Context isn't available in the constructor,so we set up the
   * value here.
   * @function
   * @param {Object} nextProps The value that will be assigned to `this.props`.
   * @param {Object} nextContext The {@link FormContext} that will be assigned to `this.context`.
   * @public
   * @returns {void}
   */
  componentDidMount(nextProps,nextContext)
  {
    const choices = nextProps?.Field?.Choices?.results || [];
    let value = nextContext?.Item?.[nextProps.FieldName] || {};
    value = Array.isArray(value) ? value : (value.results || []);
    this.setState({
      value: choices.reduce((result,choice) => ({ ...result,[choice]: value.indexOf(choice) >= 0 }),{})
    });
  }

  /**
   * Update the component when it receives new props or context information.
   * @function
   * @param {Object} nextProps The value that will be assigned to `this.props`.
   * @param {Object} nextContext The {@link FormContext} that will be assigned to `this.context`.
   * @public
   * @returns {void}
   */
  componentwillReceiveProps(nextProps,nextContext)
  {
    const choices = nextProps?.Field?.Choices?.results;
    let value = nextContext.Item?.[nextProps.FieldName] || {};
    value = Array.isArray(value) ? value : (value.results || []);
    this.setState({
      value: choices.reduce((result,{})
    });
  }

  /**
   * Get an event handler for the specified choice.
   * @function
   * @param {string} name The choice with which this event handler is associated.
   * @public
   * @returns {function} An event handler for the specified choice.
   */
  handleChange = (name) =>
  {
    const bubbleOnChange = (event,value) =>
      (this.props.onChange?.(event,Object.keys(value).filter((choice) => (value[choice]))));
    if (!this.#handlers.change[name])
    {
      this.#handlers.change[name] = (event) =>
      {
        const value = { ...this.state.value,[name]: !this.state.value[name] };
        this.setState({ value },() => (void bubbleOnChange(event,value)));
      };
    }
    return this.#handlers.change[name];
  }

  /**
   * Render the user interface for this component as a
   * [Stack]{@link https://developer.microsoft.com/en-us/fluentui#/controls/web/stack} containing
   * [CheckBox]{@link https://developer.microsoft.com/en-us/fluentui#/controls/web/checkBox} components.
   * @function
   * @public
   * @returns {JSX} The user interface for this component.
   */
  render()
  {
    const choices = this.props.Field.Choices.results;
    return (<>
      <Fabric.Stack {...this.props.stackTokens}>
        {choices.map((choice) => (
          <Fabric.CheckBox label={choice} checked={this.state.value[choice]}
            onChange={this.handleChange(choice)} key={choice} />
        ))}
      </Fabric.Stack>
      <div
        className="errorMessage"
        id={`FormFieldDescription--${this.context.Item?.Id}__${this.props.FieldName}`}
        role="alert"
        style={{ display: this.props.errorMessage ? "" : "none" }}>
        {this.props.errorMessage}
      </div>
    </>);
  }
}

表单通过 REST API 检索数据后,此组件使用该数据更新其状态。虽然状态已正确更新并且正确的值被传递到每个 CheckBox 组件的 props,但 UI 具有误导性。例如,根据 React,下面的 checked 值分别设置为 falsetruefalsefalsefalse Chrome DevTools 中的组件检查器。

Initial presentation of Stack containing one Checkbox with checked set to true; No checkboxes are ticked

显然,虽然 props 设置正确,但用户会看到五个未选中的复选框。当用户点击本应勾选的复选框时,state 会正确更新以反映所有五个复选框都未勾选。这是用户点击第二个复选框后的样子。

Updated presentation of Stack containing no Checkboxes with checked set to true; The second checkbox is ticked

用户与 CheckBox 组件交互,它们的行为与预期一致,但对于初始 checked 属性设置为 true 的任何地方,基础值都完全反转。

解决方法

我在构造函数中添加了 context 但这没有帮助,所以我将这个类组件转换为函数组件。它按预期工作。这是功能组件的代码。

const MultiChoiceField = ({ errorMessage = "",Field,FieldName,onChange,stackTokens = {} } = {}) =>
{
  const context = useContext(FormContext);
  const [value,setValue] = useState({});
  const handleChange = (choice) => (event) =>
  {
    const bubbleOnChange = (event,value) => (void onChange?.(event,value));
    const getValueAsArray = (valueNew) =>
      (Object.entries(valueNew).filter(([,value]) => (value)).map(([key]) => (key)));
    const valueNew = { ...value,[choice]: !value[choice] };
    bubbleOnChange(event,getValueAsArray(valueNew));
  };
  const updateChoices = () =>
  {
    const reduceSelected = (valueContext) => (result,choice) =>
      ({ ...result,[choice]: ~valueContext.indexOf(choice) });
    const choices = Field?.Choices?.results || [];
    let valueContext = context?.Item?.[FieldName] || {};
    valueContext = Array.isArray(valueContext) ? valueContext : (valueContext.results || []);
    setValue(choices.reduce(reduceSelected(valueContext),{}));
  };
  useEffect(updateChoices,[Field?.Choices,context?.Item]);
  const renderChoice = (choice) => (
    <Fabric.Checkbox checked={!!value[choice]} key={choice} label={choice} onChange={handleChange(choice)} />
  );
  return (<>
    <Fabric.Stack {...stackTokens}>{(Field?.Choices?.results || []).map(renderChoice)}</Fabric.Stack>
    <div
      className="errorMessage"
      id={`FormFieldDescription--${context.Item?.Id}__${FieldName}`}
      role="alert"
      style={{ display: errorMessage ? "" : "none" }}>
      {errorMessage}
    </div>
  </>);
};
MultiChoiceField.displayName = "MultiChoiceField";

注意接口是一样的,state的内部存储本质上是一样的。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。