skip to Main Content

I have a multi select box implemented using this package – https://github.com/srigar/multiselect-react-dropdown.

The options for this box are retrieved dynamically from an API call. The issue I have is the box is loaded and then the API call is made – so the box is rendered before the options are loaded, and so the options are never loaded into the box. I want to fix this but I am unsure how to do so. I tried to use the useEffect method as I thought this would delay the display of the box until the function has completed but it has not worked out as I had hoped.

Here is the code for my Multi select box:

import React, { useState } from 'react';
import { Form, Row } from 'react-bootstrap';
import Multiselect from 'multiselect-react-dropdown';

function MultiselectBox({ businessUnits }) {


    const [selectedValues] = useState([]);

    return (
        <>
            <Form.Group as={Row} className="me-3" controlId="title">
                <Form.Label>Business Units: </Form.Label>
                <Multiselect
                        options={businessUnits} // Options to display in the dropdown
                        selectedValues={selectedValues} // Preselected value to persist in dropdown
                        displayValue="name" // Property name to display in the dropdown options
                        loading="true"
                />
            </Form.Group>
        </>
    );
}

export default MultiselectBox;

And the main page where it is called:

import React, { useState, useEffect } from 'react';
import { Container, Row, Col, Form } from 'react-bootstrap';
import DateRange from '../DateRange';
import SiteComponentLoader from '../SiteComponentLoader';
import MultiselectBox from '../Utilities/MultiselectBox';
import { fetchDataAuthenticated } from '../Utilities/HttpUtils';
import useAlert from '../Alerts/useAlert';

function LotList({ status }) {
    status ??= "";

    const defaultDateRange = {
        from: null, //new Date().toISOString().split('T')[0],
        to: null
    };

    const { setAlert } = useAlert();
    const [businessUnits, setBusinessUnits] = useState([]);
    const option = item => ({ label: item.name, value: item.code });

    useEffect(() => {
        async function getBusinessUnits() {
            fetchDataAuthenticated("api/v1/businessunit")
                .then(response => {
                    if (response.ok) {
                        return response.json();
                    }
                    throw new Error("Failed to get business units");
                })
                .then(data => {
                    setBusinessUnits(data.map(option));
                })
                .catch(error => {
                    setAlert("danger", "Error", error.message);
                })
        }

        getBusinessUnits();
    }, []);

    const [dateFilter, setDateFilter] = useState(defaultDateRange);
    const [includeGraded, setIncludeGraded] = useState(false);

    const handleIncludeGraded = (e) => {
        setIncludeGraded(e.target.checked);
    }


    return (
        <Container fluid>
            <Row className="d-flex align-items-center mb-3">
                <Col className="d-flex justify-content-start">
                    <DateRange dateFilter={dateFilter} setDateFilter={setDateFilter} />
                </Col>
                <Col className="d-flex justify-content-middle">
                    {businessUnits &&
                        <MultiselectBox businessUnits={businessUnits}></MultiselectBox>
                    }
                </Col>
                <Col className="d-flex justify-content-end pt-2">
                    <Form.Check type="checkbox" onChange={handleIncludeGraded} className="me-2" style={{ marginTop: "-0.5rem" }} />
                        <Form.Label>Include graded items?</Form.Label>
                </Col>
            </Row>
            <Row>
                {/* TODO: Get the site code from configuration in some way */}
                <SiteComponentLoader component="./Pages/LotListTable" site="SCP" tableFilters={{ status: status, dateFilter: dateFilter, includeGraded: includeGraded }} />
            </Row>
        </Container>
    );
}

export default LotList;

So what I want is the async function getBusinessUnits to fire off in the useEffect method – LotList.js. This should load the values from my API and map these to a set of names and values. The names should be available as options in the checkbox.

However, when I use console.log to see what happens, I can see it prints the array businessUnits as empty twice, and then prints it twice with the values inside the array. I am not sure why it is showing as empty the first two times, but I suspect the fact it is empty means the Multiselect box is rendered with no options.

To get around this I added the line businessUnits && before the component Multiselect, which to my understanding would stop it from loading until there was data inside this array. However, this has also not worked so I am lost as to how to fix this.

Simplified code sandbox available here: https://codesandbox.io/s/modest-clarke-dpvj5h?file=/src/App.js

3

Answers


  1. in useEffect set the whole data array setBusinessUnits(data); assuming data holds the array with values you want for checkbox.

    You map through the values within the component

    {businessUnits.map(b => 
      <MultiselectBox businessUnits={b}></MultiselectBox>}
    
    Login or Signup to reply.
  2. {businessUnits && <MultiselectBox businessUnits={businessUnits}></MultiselectBox> }

    will always be true because of how you have defined the state.

    const [businessUnits, setBusinessUnits] = useState([]);

    This check will never return a null or undefined value because if even the data is not fetched the state still holds an empty array.

    You need to modify your check like this

    {businessUnits.length > 0 && <MultiselectBox businessUnits={businessUnits}></MultiselectBox>
    }

    Login or Signup to reply.
  3. Your code seems fine. Just in your child component(MultiSelectBox) add a useEffect hook with dependency

    function MultiselectBox({ options }) {
      const [selectedValue] = useState(null);
    
      useEffect(() => {}, [options]);
      ...
    

    What this does is listen to your options, when they are loaded from api call they automatically get rendered instead of user clicking the dropdown two or three times.

    And if you want to show the dropdown only when options have loaded, then show the component on condition:

      {businessUnits && businessUnits.length > 0 && (
                      <MultiselectBox options={businessUnits} />
                    )}
    

    Working demo in sandbox here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search