How to fix map typerror when a promise object is returned before the array?

Viewed 39

The code below returns "TypeError: options.map is not a function" which I'm assuming is happening because it's not an array but a promise object as mentioned in the title. Code below:

function DownloadMenus() { 
   async function grabIDs(){

     const response = await fetch("http://localhost:5000/obviusmetersID");
     const jsonData = await response.json();
     let ids = [];

     for(var i = 0; i< jsonData.length; i++){
         var obj = jsonData[i];
         ids.push(obj.id);
     }

     return ids;

    }

    const[options, setOptionsData] = React.useState(grabIDs);
    const [selectedIndex, setSelectedIndex] = React.useState(0);

    const handleMenuItemClick = (event, index) => {
    setSelectedIndex(index);
    setOpen(false);
    };
...
...

    return(
...
...
    <ClickAwayListener onClickAway={handleClose}>
                                    <MenuList id="split-button-menu">
                                        {options.map((option, index) => (
                                            <MenuItem
                                                key={option}
                                                disabled={index === 2}
                                                selected={index === selectedIndex}
                                                onClick={(event) => handleMenuItemClick(event, index)}
                                            >
                                                hi
                                                {option}
                                            </MenuItem>
                                        ))}
                                    </MenuList>
    </ClickAwayListener>

I tried to do the following:

                                <MenuList id="split-button-menu">
                                    {async () => {await options.map((option, index) => (
                                        <MenuItem
                                            key={option}
                                            disabled={index === 2}
                                            selected={index === selectedIndex}
                                            onClick={(event) => handleMenuItemClick(event, index)}
                                        >
                                            hi
                                            {option}
                                        </MenuItem>
                                    ))}}
                                </MenuList>

But when I do that, I get a warning saying I have an invalid prop type and nothing appears in the listing.

I want to assume that I'm missing a step that allows the promise to resolve before being passed but I'm not sure where exactly to put that in.

I can edit the code blocks as necessary but I think these are all the relevant pieces.

1 Answers

I prefer setting state in grabIDs Promise.then callback

 async function grabIDs() {

     const response = await fetch("http://localhost:5000/obviusmetersID");
     const jsonData = await response.json();
     const ids = jsonData.map(pr => pr.id);
     return ids;

}

function DownloadMenus() { 
    const[options, setOptionsData] = React.useState([]);

    useEffect(() => {
      grabIDs().then(data => {
        setOptionsData(data)
      })
    }, [])

    return <ClickAwayListener onClickAway={handleClose}>
      <MenuList id="split-button-menu">
        {options.map((option, index) => (
           <MenuItem
             key={option}
             disabled={index === 2}
             selected={index === selectedIndex}
             onClick={(event) => handleMenuItemClick(event, index)}>hi{option}
           </MenuItem>
       ))}
     </MenuList>
  </ClickAwayListener>

--Edit

Can you elaborate a bit on why I should set the state outside of DownloadMenus instead of inside? Also one of my attempts to solve this was: ...code... What's the reasoning behind this approach being wrong?

It is not wrong. UseEffect callback function return value should be function in which you can cleanup resource ( perhaps you are switching between component and request can be aborted )

const[options, setOptionsData] = React.useState([]);

useEffect(() => {
  (async () => {
    setOptionsData(await grabIDs());
  })()
  
  return () => {
    // you can refactor `grabIDs` and store AbortController somewhere
    // const controller = new AbortController();
    // const signal = controller.signal;
    // fetch(url, {signal}).then(...
    // and then call here
    // controller.abort();
  }
}, [])
Related