MUI: You have provided an out-of-range value `3` for the select (name="surface_id") componen

Viewed 184

I have a problem and I know what is but I can't solve it

I have the following select

<Select
    defaultValue=""
    labelId="surfaces-label"
    id="surface_id"
    name="surface_id"
    value={surfaceId}
    onChange={handleSurfaceChange}
    label={translation.word.surface}
  >
    {surfaces.length > 0 ? (
      surfaces.map((surface) => {
        return (
          <MenuItem value={surface.id} key={surface.created_at}>
            {surface.name}
          </MenuItem>
        );
      })
    ) : (
      <MenuItem>-</MenuItem>
    )}
  </Select>

and this is loaded with data that I get from the API using the useEffect

useEffect(() => {
    if (!surfaces.length) {
      handleGetSurfaces();
    }
}, []); 

The problem is when I want to edit and I add the following code to useEffect

  useEffect(() => {
    if (!surfaces.length) {
      handleGetSurfaces();
    }

    if (surfaceEditing) {
      setSurfaceId(surfaceEditing.id);
      validator.setFieldValue("surface_id", surfaceEditing.id);
    }
  }, []);

When is executed setSurfaceId(surfaceEditing.id); the data in the select doesn't exist yet because is getting from API and here is when I receive:

MUI: You have provided an out-of-range value 3 for the select (name="surface_id") component. Consider providing a value that matches one of the available options or ''. The available values are "".

I need to setSurfaceId(surfaceEditing.id); after surface is loaded with .map but it doesn't work

1 Answers

Loading the Data

You are going to want to use a Promise to call handleGetSurfaces() asynchronously so that you can wait for the result before continuing into your if (surfaceEditing){...

This can be achieved with async/await, but if you try to use async/await inside of useEffect() you'll get this error: https://i.stack.imgur.com/YFRR5.png

This happens because async makes the function return a Promise and useEffect is not expecting the callback function to return a Promise.

An easy solution is to do this:

const handleInitialize async () => {
    if (!surfaces.length) {
      await handleGetSurfaces();
    }

    if (surfaceEditing) {
      setSurfaceId(surfaceEditing.id);
      validator.setFieldValue("surface_id", surfaceEditing.id);
    }
}

useEffect(() => {
      handleInitialize()
}, []);

What we did in the block above is that we called another function inside of useEffect and made that function asyncronous instead of making the useEffect callback function asyncronous.

You'll have to make sure that wherever you define your handleGetSurfaces() function that you make it asyncronous as well, so that we are able to await it.


Using the Data

That covers the data loading, but we still have to handle everything property while we are waiting for that data to load. When the page first loads, the <Select> will be trying to use data that hasn't been loaded yet, so we need to fix that.

One way to handle this is to conditionally render the <Select> like this:

{
surfaceId !== undefined &&
    <Select
        defaultValue=""
        labelId="surfaces-label"
        id="surface_id"
        name="surface_id"
        value={surfaceId}
        onChange={handleSurfaceChange}
        label={translation.word.surface}
      >
        {surfaces.length > 0 ? (
          surfaces.map((surface) => {
            return (
              <MenuItem value={surface.id} key={surface.created_at}>
                {surface.name}
              </MenuItem>
            );
          })
        ) : (
          <MenuItem>-</MenuItem>
        )}
    </Select>
}

The surfaceId !== undefined && says that we will check if surfaceId is not undefined, and if it that is true (if it is not undefined) then we will render the <Select>. This is called short circuit evaluation since if the condition is false then the code will "short circuit" and not even read the next statement.

Related