0
votes

I'm using Material-UI to create some Selects for my dashboard. The MenuItems (options) in the Select are generated using conditional rendering. I use map to return MenuItems based on a set list of options from an array that is retrieved from a remote API. The value of the Select is also determined from a value retrieved from the remote API. Upon API response the defaultValue is updated to the actual value.

Here's a quick look at the relevant portion of the component:

            <Select
            MenuProps={{
                className: classes.selectMenu,
            }}
            classes={{
                select: classes.select,
            }}
            id={id}
            name={name}
            defaultValue={defaultValue}
            value={value}
            onChange={onChange}
        >
            <MenuItem
                disabled
                classes={{
                    root: classes.selectMenuItem,
                }}
                value={0}
            >
                {labelText}
            </MenuItem>
            {selectOptions.map(opt =>
                <MenuItem
                classes={{
                    root: classes.selectMenuItem,
                    selected: classes.selectMenuItemSelected,
                }}
                value={opt.IDX}
                key={`${name}${opt.IDX}`}
            >
                {opt.Name}
            </MenuItem>)}
        </Select>

Each one of the options has a IDX property and a name property. So for instance a list of users in the dropdown would show Bob, Sally, Jeff and the values would be 1,2,3,etc relative to Bob, Sally, and Jeff's ID numbers. As you can see there is a default option with value 0 specified and a defaultValue prop.

I receive the following warning in the JS console:

You have provided an out-of-range value `1` for the select component.
Consider providing a value that matches one of the available options or ''.
The available values are `0`.

which I can only assume is because React doesn't know about the conditionally rendered MenuItems until after they are rendered, and at that point it is possible that the remote API has already responded and the value of the Select component is already set to something besides the defaultValue.

This does not seem to affect code functionality in any way but it is a race condition and I don't like it. Is there a way to perform a check to ensure my component has all MenuItems available and their values set before setting the parent select component's value?

I was thinking about setting something like an isFullyProcessed boolean within the JSX somehow and then working with that.

EDIT:

Here is how I'm fetching my data on the parent page. The schedules and groups are the options data and the user is where the value comes from.

   useEffect(() => {
    axios.get(`${server}${uPath}/data`).then((response) => {
      const parsed = JSON.parse(response.data);
      setSchedules(parsed.schedules);
      setGroups(parsed.groups);
      setUser(parsed.user);
1
Either don't render the select until your api response is received and the menu items can be rendered, or don't assign the value to the select until your api response has been received. This should be achievable with some kind of "is loading" flag.Jacob Smit
@JacobSmit I think the problem is that I'm setting the value too early. Is there a way to create an isRendered flag in the JSX and prevent any change to value before that flag flips to true? Because I think I have the opposite problem of what most people have with slow APIs. I think my API is responding before the initial render finishes.TheFunk
This happens when the value is set before you have the data to run through the map, you don't need to wait for initial render, you need to wait for your API to return the data you are mapping out. If the components are initially mapped out in the same render cycle as the Select is initially rendered or the value is initially set then this warning should not be received.Jacob Smit
Are they both stored in the same useState() or are they using different useState()s?Jacob Smit
Here is an example using static variables and mapping out the MenuItem components: https://codesandbox.io/s/material-demo-forked-gmir7. Without seeing more of your code I will assume that you have two different setState storing data from the one api call and you are setting the value state before you are setting the options state and as these would be part of an async call they are no longer batched by the useEffect meaning each individual set causes a rerender.Jacob Smit

1 Answers

0
votes

As was commented, this was solved by waiting to render until all data was retrieved from the API. Apparently, even though the API call for setting value and setting the menuoptions was done in the same function, because I use two separate pieces of state to store this info, it is possible for value to be set in a different render cycle than the menuoptions are rendered.

I added an isLoading boolean and waited for the useeffect to return the data from the API and this resolved the issue. Quick tutorial here.