avatar

ShīnChvën ✨

Effective Accelerationism

Powered by Druid

Simple String Selector with Ant Design Mobile

Thu Apr 13 2023

Introduction

Ant Design Mobile provides a Picker component to select a value from a list. However, it is not easy to use it to select a string value.

In this post, I will create a SimpleStringSelector component with the Picker. It provides a simple way to select a string value from a list, it can fetch options asynchronously, and it is a Form.Item by default.

You can use it like this:

import React from 'react';
import { Form } from 'antd-mobile';

const Demo = () => {
  const [form] = Form.useForm();

  return (
    <Form form={form}>
      <SimpleStringSelector
        form={form}
        label="Name"
        formName="name"
        placeholder="Please select name"
        rules={[{ required: true, message: 'Please select name' }]}
        fetchOptions={async () => {
          // simulate a network request
          return ['name1', 'name2'];
        }}
      />
    </Form>
  );
};

Code

import React from 'react';
import {
  Form,
  Picker,
} from 'antd-mobile';

type SimpleStringSelectorProps = {
  label: string;
  formName: string;
  placeholder?: string;
  rules?: Rule[];
  initialValue?: string;
  /**
   * fetch data asynchronously
   */
  fetchOptions: () => Promise<string[]>;
  /**
   * form instance, it is used to reload initial value when changed
   */
  form?: FormInstance;
};

const SimpleStringSelector = ({
                             label,
                             formName,
                             placeholder,
                             rules,
                             initialValue,
                             fetchOptions,
                             form,
                           }: SimpleStringSelectorProps) => {
  const [loading, setLoading] = useState(false);
  const [options, setOptions] = useState<{ label: string; value: string }[]>([]);
  const [visible, setVisible] = useState(false);
  const [value, setValue] = useState<string[] | undefined>()

  useEffect(() => {
    const _fetchOptions = async () => {
      if (loading) {
        return;
      }
      setLoading(true);
      try {
        const data = await fetchOptions();
        const options = data.map((option) => ({
          value: option,
          label: option,
        }));
        setOptions(options);
      } catch (e) {
        console.error(e);
      }
      setLoading(false);
    };
    _fetchOptions().then();
  }, [0]);

  // update initial value when it is changed
  useEffect(() => {
    if (initialValue) {
      form?.setFieldsValue({[formName]: value})
    }
  }, [initialValue]);

  // prevent rendering while loading
  if (loading) {
    return <></>;
  }

  return (
    <Form.Item
      name={formName}
      trigger='onConfirm'
      label={label}
      rules={rules}
      initialValue={initialValue ? [initialValue] : []}
      onClick={() => setVisible(true)}
    >
      <Picker
        columns={[options]}
        visible={visible}
        onClose={() => setVisible(false)}
        value={value}
        onConfirm={(v) => {
          setValue(v as string[]);
        }}
      >
        {(value) => {
          return value[0]?.value || placeholder || `Please select ${label};
        }}
      </Picker>
    </Form.Item>
  );
};