React-Select : How to display 'Loading...' while loading options

Viewed 8979

I'm tring to use 'React-Select' for my WordPress block component to select post categories.
What I want is to display 'Loading...' while loading categories and then display the categories list or 'No Categories Found' when loading is complete.
With my code, it displays 'No Categories Found' while loading, then displays categories list.

export default class Categories extends Component {
    constructor() {
        super( ...arguments );
        this.state = {cats: []};
    }
    componentDidMount () {
        return apiFetch({path: '/myoriginal-blocks/v1/categories'})
        .then(data => {
            this.setState({cats:data});
        })
    }
    onChangeCategories = newCategories => {
        this.props.setAttributes({ category: newCategories == null ? [] : newCategories });
    }
    render() {
        const { attributes } = this.props;
        const { category } = attributes;
        return (
            <>
                <div>
                    <label>Categories</label>
                    <Select
                        isMulti
                        isClearable
                        placeholder='Search categories...'
                        onChange={ this.onChangeCategories }
                        options={ this.state.cats }
                        value={ category && category }
                        noOptionsMessage={() => 'No Categories Found'}
                    />
                </div>
            </>
        );
    }
}

enter image description here

Or maybe I have to use 'Async-Select' instead of 'React-Select' but I could not understand well it's documentation.
https://react-select.com/async
Hope someone help me. Thank you.

3 Answers

You can you isLoading prop of react-select component.

add a property in your state to manage loading

this.state = {cats: [], isLoading: true};

and then set isLoading false

return apiFetch({ path: "/myoriginal-blocks/v1/categories" }).then(
      (data) => {
        this.setState({ cats: data, isLoading: false });
      }
    );

and in Select component pass the isLoading prop

<Select
    isMulti
    isClearable
    placeholder='Search categories...'
    onChange={ this.onChangeCategories }
    options={ this.state.cats }
    value={ category && category }
    noOptionsMessage={() => 'No Categories Found'}
    isLoading={this.state.isLoading}
/>

You can achieve this by creating a variable in your state that will hold the status of the loading. You then set this state variable before you fetch the data and set it once more when the data has been fetched. Finally, you simply use it to render a different message in your render method.

export default class Categories extends Component {
  constructor() {
    super(...arguments);
    this.state = {
      cats: [],
      isLoading: false // create a new state variable for loading
    };
  }
  componentDidMount() {
    this.setState((prevState) => ({
      ...prevState,
      isLoading: true
    }));

    return apiFetch({ path: "/myoriginal-blocks/v1/categories" }).then(
      (data) => {
        this.setState({ cats: data, isLoading: false });
      }
    );
  }

  onChangeCategories = (newCategories) => {
    this.props.setAttributes({
      category: newCategories == null ? [] : newCategories
    });
  };
  render() {
    const { isLoading } = this.state;
    const { attributes } = this.props;
    const { category } = attributes;
    return (
      <>
        {isLoading ? (
          <div>Loading...</div>
        ) : (
          <>
            <label>Categories</label>
            <Select
              isMulti
              isClearable
              placeholder="Search categories..."
              onChange={this.onChangeCategories}
              options={this.state.cats}
              value={category && category}
              noOptionsMessage={() => "No Categories Found"}
            />
          </>
        )}
      </>
    );
  }
}

I found this page because the list was still showing the list open with the old values while loading new list data... Setting isLoading and ALSO options to empty array like this made the list disappear after I selected an option and show loading indicator properly. This has added benefit because it doesn't require changing a state var and you can handle your data at render as opposed to saving a local state (as referenced above) which in most cases would result in having to use getDerivedStateFromProps or a useEffect which cause unnecessary re-rendering.

  <Select
    arrowRenderer={() => <FaChevronDown />}
    className={!isEmpty(mutationError) ? 'validation-error' : undefined}
    id={cellId}
    isDisabled={disableLOVField}
    isLoading={lovLoading} // here <---<<<
    matchPos="any"
    maxMenuHeight={180}
    menuPlacement="auto"
    menuPortalTarget={document.body}
    name={cellId}
    onChange={handleOnChange}
    onMenuOpen={() => {
      refetchRowConfig({
        ...configVariables,
        skipGetRowConfig: false,
      });
    }}
    options={lovLoading ? [] : listOptions} // and here <---<<<
    placeholder="Please Select"
    resetValue=""
    styles={{
      group: provided => ({
        ...provided,
        borderBottom: '1px solid #ccc',
      }),
      menuPortal: base => {
        const { zIndex, ...rest } = base;

        return { ...rest, zIndex: 9999 };
      },
    }}
    value={selectedValue}
  />;
Related