如何使用 AsyncSelect 设置“react-select”以正确处理服务器端数据?

mgP*_*ePe 4 reactjs react-select

我想设置一个组件react-select来处理服务器端数据并进行服务器端过滤,但由于多种原因它不起作用。

您能解释一下并显示工作代码吗?

mgP*_*ePe 7

让我们首先表达看起来react-select很棒但没有很清楚记录的观点。就我个人而言,我并不喜欢该文档,原因如下:

  • 没有搜索
  • 所有的道具都放在一个页面上。如果我做某事CTRL+F,一切都会亮起来。很没用
  • 大多数描述都很简短,没有描述重要的边缘情况,有些甚至缺失
  • 有一些例子,但不足以展示不同的品种,所以你必须做猜测

因此,我将尝试通过提供逐步的步骤、代码和问题+解决方案来为本文提供一些帮助。

第 1 步:最简单的反应选择形式:

const [options, setOptions] = useState([
    { id: 'b72a1060-a472-4355-87d4-4c82a257b8b8', name: 'illy' },
    { id: 'c166c9c8-a245-48f8-abf0-0fa8e8b934d2', name: 'Whiskas' },
    { id: 'cb612d76-a59e-4fba-8085-c9682ba2818c', name: 'KitKat' },
  ]);
 <Select
        defaultValue={options[0]}
        isClearable
        options={options}
        getOptionLabel={(option) => option.name}
        getOptionValue={(option) => option.id}
      />
Run Code Online (Sandbox Code Playgroud)

它通常有效,但您会注意到,如果我键入的字母d与任何地方的任何选项都不匹配,选项会保留,而不是显示“无选项”。

在此输入图像描述

我会忽略这个问题,因为它很小而且似乎无法解决。

到目前为止一切顺利,我们可以忍受这个小问题。

步骤2:将静态数据转换为服务器数据

我们现在的目标是简单地将静态数据与服务器加载的数据交换。嗯,这能有多难呢?

我们首先需要交换<Select/><AsyncSelect/>. 现在我们如何加载数据?

所以查看文档有多种加载数据的方法:

defaultOptions: The default set of options to show before the user starts searching. When set to true, the results for loadOptions('') will be autoloaded.

and

loadOptions: Function that returns a promise, which is the set of options to be used once the promise resolves.
Run Code Online (Sandbox Code Playgroud)

仔细阅读它,您会明白defaultOptions需要是一个布尔值 true 并且loadOptions应该有一个返回选择的函数:

      <AsyncSelect
        defaultValue={options[0]}
        isClearable
        getOptionLabel={(option) => option.name}
        getOptionValue={(option) => option.id}
        defaultOptions
        loadOptions={loadData}
      />
Run Code Online (Sandbox Code Playgroud)

看起来很棒,我们已经加载了远程数据。但我们现在想预设默认值。我们必须通过 来匹配它Id,而不是选择第一个。我们的第一个问题来了:

问题:你不能defaultValue从一开始就设置 ,因为你没有数据可以匹配它。如果您尝试设置defaultValue组件加载后,则它不起作用。

为了解决这个问题,我们需要提前加载数据,匹配我们拥有的初始值,一旦我们拥有这两个值,我们就可以初始化组件。有点难看,但考虑到限制,这是我能弄清楚的唯一方法:

const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);

const getInitial = async () => {
    // make your request, once you receive data:
    // Set initial object
    const init= res.data.find((item)=>item.id=ourInitialId);
    setInitialObject(init); 
    // Set data so component initializes
     setData(res.data);
  };
useEffect(() => {
    getInitial();
  }, []);

return (
     <>
      {data!== null && initialObject !== null ? (
        <AsyncSelect
          isClearable
          getOptionLabel={(option) => option.name}
          getOptionValue={(option) => option.id}
          defaultValue={initialObject}
          defaultOptions={options}
          // loadOptions={loadData} // we don't need this anymore
        />
      ) : null}
     </>
    )

Run Code Online (Sandbox Code Playgroud)

由于我们自己加载数据,所以不需要,loadOptions所以我们将其取出。到目前为止,一切都很好。

步骤3:使用服务器端过滤调用来制作过滤器

所以现在我们需要一个可用于获取数据的回调。我们回顾一下文档:

onChange: (no description, from section "StateManager Props")

onInputChange: Same behaviour as for Select
Run Code Online (Sandbox Code Playgroud)

所以我们听文档并返回“选择道具”部分找到:

onInputChange: Handle change events on the input`
Run Code Online (Sandbox Code Playgroud)

富有洞察力...不是。

我们看到一个函数类型定义似乎有一些线索:

在此输入图像描述

我想,该字符串必须是我的文本/查询。显然,变化的类型有所下降。我们出发——

const [data, setData] = useState(null);
const [initialObject, setInitialObject] = useState(null);

const getInitial = async () => {
    // make your request, once you receive data:
    // Set initial object
    const init= res.data.find((item)=>item.id=ourInitialId);
    setInitialObject(init); 
    // Set data so component initializes
     setData(res.data);
  };
useEffect(() => {
    getInitial();
  }, []);

  const loadData = async (query) => {
   // fetch your data, using `query`
   return res.data;
  };

return (
     <>
      {data!== null && initialObject !== null ? (
        <AsyncSelect
          isClearable
          getOptionLabel={(option) => option.name}
          getOptionValue={(option) => option.id}
          defaultValue={initialObject}
          defaultOptions={options}
          onInputChange={loadData} // +
        />
      ) : null}
     </>
    )
Run Code Online (Sandbox Code Playgroud)

使用正确的查询获取数据,但选项不会根据我们的服务器数据结果进行更新。我们无法更新 ,defaultOptions因为它仅在初始化期间使用,因此唯一的方法就是返回loadOptions. 但一旦我们这样做了,每次击键都会有 2 次调用。布莱克。经过无数个小时和艰苦实验的奇迹,我们现在发现:

有用的启示:loadOptions实际上在 inputChange 上触发,所以我们实际上不需要onInputChange.

<AsyncSelect
  isClearable
  getOptionLabel={(option) => option.name}
  getOptionValue={(option) => option.id}
  defaultValue={initialObject}
  defaultOptions={options}
  // onInputChange={loadData} // remove that
  loadOptions={loadData} // add back that
/>
Run Code Online (Sandbox Code Playgroud)

事情看起来不错。甚至我们的d搜索也以某种方式自动修复了:

在此输入图像描述

第 4 步:更新formik或您拥有的任何表单值

为此,我们需要一些在选择时触发的东西:

onChange: (no explanation or description)
Run Code Online (Sandbox Code Playgroud)

富有洞察力...不是。我们再次有了一个漂亮而丰富多彩的定义来拯救我们,我们找到了一些线索:

在此输入图像描述

所以我们看到第一个参数(我们不知道它是什么,可以是、、 或 的object数组。然后我们就有了动作的类型。因此,经过一些猜测,我们发现,它必须传递选定的对象:arraynullundefined

我们将setFieldValue函数作为 prop 传递给组件:

onChange={(selectedItem) => {
  setFieldValue(fieldName, selectedItem?.id); // fieldName is also passed as a prop
}}
Run Code Online (Sandbox Code Playgroud)

注意:小心,如果你清除选择,它将通过,null并且selectedItem你的 JS 将因寻找.id未定义而爆炸。要么使用可选链接,要么像我的例子一样将其有条件地设置为''(空字符串,以便 formik 工作)。

第5步:最终代码:

这样我们就准备好了一个功能齐全、可重用的自动完成下拉菜单,选择服务器获取异步过滤、可清除的东西。

import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types';
import AsyncSelect from 'react-select/async';


export default function AutocompleteComponent({
  fieldName,
  initialValue,
  setFieldValue,
  getOptionLabel,
  queryField,
}) {
  const [options, setOptions] = useState(null);
  const [initialObject, setInitialObject] = useState(null);

  // this function only finds the item from all the data that has the same id
  // that comes from the parent component (my case - formik initial)
  const findByValue = (fullData, specificValue) => {
    return fullData.find((e) => e.id === specificValue);
  };

  const loadData = async (query) => {
    // load your data using query HERE
    return res.data;
  };

  const getInitial = async () => {
     // load your data using query HERE
      const fetchedData = res.data;
      // match by id your initial value
      const initialItem = findByValue(fetchedData, initialValue); 
      // Set both initialItem and data options so component is initialized
      setInitialObject(initialItem);
      setOptions(fetchedData);
    }
  };

  // Hit this once in the beginning
  useEffect(() => {
    getInitial();
  }, []);

  return (
    <>
      {options !== null && initialObject !== null ? (
        <AsyncSelect
          isClearable
          getOptionLabel={getOptionLabel}
          getOptionValue={(option) => option.id}
          defaultValue={initialObject}
          defaultOptions={options}
          loadOptions={loadData}
          onChange={(selectedItem) => {
            const val = (selectedItem === null?'':selectedItem?.id);
            setFieldValue(fieldName, val)
          }}
        />
      ) : null}
    </>
  );
}

AutocompleteComponent.propTypes = {
  fieldName: PropTypes.string.isRequired,
  initialValue: PropTypes.string,
  setFieldValue: PropTypes.func.isRequired,
  getOptionLabel: PropTypes.func.isRequired,
  queryField: PropTypes.string.isRequired,
};

AutocompleteComponent.defaultProps = {
  initialValue: '',
};

Run Code Online (Sandbox Code Playgroud)

我希望这可以为您节省一些时间。

  • @BornReady 我无意以任何方式冒犯,只是对我来说很难让它发挥作用,而且我是典型的普通乔非高级开发人员。我必须通过反复试验才能使其发挥作用...... (3认同)