Search code examples
javascriptreact-class-based-component

How do I use an asynchronous function to pass data to a Select component in a React class component?


I am trying to use MenuItem components with a React class component to display an array of strings as dropdown options in a Select component. In this snippet of code, I'm trying to retrieve these MenuItem components from the getGitRepoBranches(url) function call on line 11 and render them inside of my Select component:

1 {c.type === "git" ? <FormControl variant="outlined" size="small" style={{width: "20%", marginTop: "8px", marginRight: "0px"}}>
2      <InputLabel id="branch">Branch</InputLabel>
3      <Select
4          //error={!this.isGitRepoValid(c.url) && this.state.hasUserClickedSave}
5          //helperText={!this.isGitRepoValid(c.url) && this.state.hasUserClickedSave? INVALID_GIT_REPO_ERROR_MSG : ''}
6          labelId="branch-label"
7          id="demo-simple-select-helper"
8          label="Branch"
9          //onChange={(e) => this.editComponentHandler(e, i, "branch")}
10     >
11         {this.getGitRepoBranches(c.url)}
12     </Select>
13     {this.state.hasUserClickedSave ? (<FormHelperText style={{marginLeft: "0px"}} error>{INVALID_GIT_REPO_ERROR_MSG}</FormHelperText>) : null}
14 </FormControl> : null}

getGitRepoBranches() is retrieving data from an asynchronous API call fetchBranches, mapping the data to MenuItem components and returning them. It is defined like this:

getGitRepoBranches(url) {
    fetchBranches(url)
        .then((branches) => {
        if (branches.length > 0) {
            branches.map(branch => {
              return (
                <MenuItem value={branch}>
                  {branch}
                </MenuItem>
              )
            })
        }
         else
            return null
         })
    }

And fetchBranches is defined here:

1 export const fetchBranches = async (url: string): Promise<string[]> => {
2     let branches = <string[]>[];
3   ...
4         const URL: string = '/api/get_branches?' +
5             new URLSearchParams(queryParams).toString();
6 
7         try {
8             const response = await fetch(URL);
9             if (!response.ok) throw response.statusText;
10
11            const json = await response.json();
12            console.log("branches json:", json)
13            branches = createBranchesFromJSON(json);
14  ...
15    console.log("branches end of Api:", branches)
16    return Promise.resolve(branches);
17}

In fetchBranches, the console.log on line 15 shows that branches is an array of strings. As per this post's suggestion: Why is then not working on this function, I tried returning a promise in the fetchBranches function to use in conjunction with .then in getGitRepoBranches(). After I fetch this promise's data (the array of strings) and map it to MenuItem components in getGitRepoBranches(), I want to render the components on line 11 in the first code snippet. Why am I incorrectly using Promise.then to use data and render it to my Select component?


Solution

  • Move your asynchronous data fetching out of the render. Kick it off when your component (or a parent component) mounts, and then update state (or props) when you get the data.

    function MySelect ({ dataUrl }) {
      const [items, setItems] = useState([]);
    
      useEffect(() => {
        fetchData(dataUrl)
          .then(data => setItems(data));
      }, [dataUrl])
    
      return (
        <Select>
          { items.map(item => (
              <MenuItem value={item} key={item}>{item}</MenuItem>
          ))}
        </Select>
      );
    }
    
    <MySelect dataUrl="/some/api/endpoint" />