Search code examples
reactjsfetchrefresh

Data not consistently loading in React


I have really confusing problem where data is not consistently loading in the UI for react even though it consistently loading in console log.

I'm trying to dynamically generate a form in React using ui specification data stored in my database for how the form elements should look and behave. If the element is a select dropdown menu, I want to dynamically pull data for the list options from different tables and fields from my db based on what is specified in the data for the select element. I'm trying an approach where I store all the lists in an array and then loop through this array when I'm creating the form in React (not sure if this is the best way).

When loading the general form data, everything works fine and consistent. When trying to dynamically create the drop down menus by pulling the data for each list, that's where sometimes it works, sometimes it doesn't. For example, the array containing my dropdown menus will sometimes show 2 lists, sometimes 1, even though console log will always show 2 lists being stored in the array, which is correct.

Any insights or suggestions is much appreciated.

This is my code:

const App = (props) => {

const formName="user_form"
const [formElements, setFormElements] = useState([]);
const [dropdownLists, setDropdownLists] = useState([])

const getFormData = async () => {
  try {
    const response = await axios.get([myurl]); 
    const data = await response.data;
    setFormElements(data); //<--This works consistently

    let tempDropdownLists = []
    await data.map(item=> {
      if(item.ui_component_type === "select"){
        const getListItems = async (req, res)=>{
          try{
            const query = `SELECT DISTINCT ${item.ui_data_field} from 
${item.ui_data_table}`
            const response = await axios.post([myurl], 
{query}); 
            const listItems  = await response.data
            console.log(listItems) //<--This always works consistently.  I have 2 dropdown lists that I am storing for this form.

            let listData = {name:`${item.ui_id}_list`, listItems: listItems}   //<--This is how i'm trying to store each dropdown list for each drop down menu. I want to later dynamically filter this array and lookup up the list based on the select drop down menu that is being rendered

            tempDropdownLists.push(listData)
            console.log(tempDropdownLists) //<--this consistently shows the right lists on every refresh
            setDropdownLists(tempDropdownLists) //<--This should work but when rendering the page, I don't consistently see the dropdownlists

          }catch(error){
            console.log(error)
          }
        }
        getListItems();
      }
    })
  } catch (error) {
    console.log(error)
  }
};

useEffect(() => {
  getFormData();
}, [props]);


   return(
      <div className="d-flex flex-column">

        <p>{JSON.stringify(dropdownLists.length)}</p> //<--This my check but doesn't always show the right number of elements in the array.  Sometimes it shows 1 element (list), sometimes it shows 2, even though there are always 2 elements in the array in console.log.  Why?

       <form>
       {formElements.map((item,index)=>(
        <div key={index} className="form-floating m-3">
         {item.ui_component_type == "input" && 
              <><input id={item.ui_id} name={item.ui_name} className={item.ui_classname </input>
             <label className="form-label">{item.ui_label}</label></>
         }
          {item.ui_component_type == "select" && 
          <>
             <select id={item.ui_id} name={item.ui_name} className={item.ui_classname}>

    //Code breaks here below.  React doesn't see all of the dropdown list in the array even though they exist in console log.

            
   {(dropdownLists.filter(list=>list.name===`${item.ui_id}_list`) [0].listItems).map((listItem, listItemIndex)=>(
            <option 
              key={listItemIndex} 
              value={listItem}
             >
                {listItem}
                </option>)
                </select>
                <label className="form-label">{item.ui_label}</label>
                </>}
             </div>
          ))}
        </form>
    </div>
    )

    }

    export default App
 

Solution

  • Try using the functional form of the state update to ensure you're working with the latest state. Here is an example, you can replace your setDropdownLists with this

      setDropdownLists(prevDropdownLists => [...prevDropdownLists, ...tempDropdownLists]);
    

    Furthermore, to improve the readability, may I suggest you move getListItems outside with useCallback

    For starters, I will rearrange the code to be more readable. e.g

    const App = (props) => {
      const formName = 'user_form';
      const [formElements, setFormElements] = useState([]);
      const [dropdownLists, setDropdownLists] = useState([]);
    
      useEffect(() => {
        getFormData();
      }, [props]);
    
      const getListItems = async (req, res) => {
        try {
          const tempDropdownLists = [];
          const query = `SELECT DISTINCT ${item.ui_data_field} from ${item.ui_data_table}`;
          const response = await axios.post([myurl], { query });
          const listItems = await response.data;
          let listData = { name: `${item.ui_id}_list`, listItems: listItems };
          tempDropdownLists.push(listData);
          return tempDropdownLists;
        } catch (error) {
          console.log(error);
          return [];
        }
      };
    
      const getFormData = async () => {
        try {
          const response = await axios.get([myurl]);
          const data = await response.data;
          setFormElements(data);
    
          let dataItems = [];
          await data.forEach(async (item) => {
            if (item.ui_component_type === 'select') {
              const items = await getListItems();
              dataItems = [...dataItems, ...items];
            }
          });
    
          setDropdownLists(dataItems);
        } catch (error) {
          console.log(error);
        }
      };
    
      return <p>{dropdownLists.length}</p>;
    };