'First container rendered have no data from api fetch in React

Im fetching an api and the data is being stored in result state. list state is an array that consist an object with an id, result, and responseinput. addItem method pushes the object into arrayList and that is being pushed to list state. list state with a .map method is being rendered at the end in return inside a container after addItem() is called which is the submit button.

Only the first index of list state array that was fetched from the api doesn't get the data that is stored in result and all the index after does have data from the api. I tried setTimeout but it doesn't work. The .map method is at the return of a react component. I looked at the console and it seems that the first render, the api is being fetched and pending for a few second, but the container is already added to the webpage and thats why it doesnt have any data on. Been trying to figure out an entire day and cant find the solution.

export default function CreatePrompt() {
    const [responseInput, setResponseInput] = useState("");
    const [result, setResult] = useState("");
    const [list, setList] = useState([
        {
            id: 101,
            inputPrompt: "What is on your mind?",
            value: "You are on my mind.",
        }
    ]);

    const onSubmit = (event) => {
        event.preventDefault()

        const getData = async () => {
            const data = {
                prompt: responseInput,
                temperature: 0.5,
                max_tokens: 64,
                top_p: 1.0,
                frequency_penalty: 0.0,
                presence_penalty: 0,
            };
            try {
                const response = await fetch('*********', {
                    method: 'POST',
                    headers: {
                        "Content-Type": "application/json",
                        Authorization: `Bearer ${process.env.REACT_APP_USER_KEY}`
                    },
                    body: JSON.stringify(data),
                })
                if (response.ok) {
                    const jsonResponse = await response.json();
                    setResult(jsonResponse.choices[0].text)
                    setResponseInput(responseInput)
                }
            } catch (error) {
                console.log(error);
            }
        }
        getData()
    }

    const addItem = () => {
        if (responseInput !== '') {
            const promptObj = {
                id: Math.random(),
                inputPrompt: responseInput,
                value: result,
            }

            const arrayList = [...list];
            arrayList.push(promptObj);
            setList(arrayList);
            setResponseInput('');
        }
    }


    const deleteItem = (key) => {
        const arrayList = [...list]

        const updateList = arrayList.filter(item => item.id !== key);

        setList(updateList);
    }
    return (
        <div>
            <main className="ui container heading-container">
                <h1 id="heading">Fun With AI</h1>
                <p id="command-description">Hello, my name is GPT-3. Ask me to do something.</p>
                <div className="ui form">
                    <form className="field" onSubmit={onSubmit}>
                        <label id="prompt-subheading">Enter Prompt</label>
                        <div className='ui input focus'>
                            <textarea
                                type='text'
                                value={responseInput}
                                onChange={(e) => setResponseInput(e.target.value)}>
                            </textarea>
                        </div>
                        <button
                            className="ui button blue right floated"
                            id="submit-button"
                            onClick={addItem}
                        >Submit</button>
                    </form>
                </div>
            </main>
            <div className="ui container subheading-container">
                <h2 id="response-subheading">Responses</h2>
            </div>
            <div>
                {list.map(item => {
                    return (
                        <div
                            className="ui container new-response-container"
                            key={item.id}
                        >
                            <div>
                                <button
                                    className="ui right floated button grey mini delete-button"
                                    onClick={() => deleteItem(item.id)}
                                >X
                                </button>
                            </div>
                            <div className="info-container">
                                <div className="prompt-container">
                                    <h3 className="response-title">Prompt:</h3>
                                    <p id="prompt-input">
                                        {item.inputPrompt}
                                    </p>
                                </div>
                                <div className="response-container">
                                    <h3 className="response-title">Response:</h3>
                                    <p id="response-result">{item.value}</p>
                                </div>
                            </div>
                        </div>
                    )
                }).reverse()}
            </div>
        </div >
    )
}


Solution 1:[1]

Issue

From what I can tell, it seems you are wanting the user to enter some value into the textarea input that is used in the onSubmit handler, then use onSubmit makes a POST request with the entered data and addItem to update the list state with the entered responseInput and resolved response value.

The issue with the current code is that both onSubmit (via the form element's onSubmit handler) and addItem (via the submit button's onClick handler) are invoked simultaneously. In other words, addItem uses the unupdated result state value before onSubmit has made the POST request and received a response.

Solution

Call addItem or handle that logic synchronously in the onSubmit callback handler. I also suggest using a utility function to generate GUIDs, just about anything is better than Math.random().

Note that this suggested solution removes completely the intermediate result state and addItem function since the response value is handled entirely in the onSubmit handler.

Example:

import { v4 as uuidV4 } from 'uuid';

export default function CreatePrompt() {
  const [responseInput, setResponseInput] = useState("");
  const [list, setList] = useState([
    {
      id: uuidV4(),
      inputPrompt: "What is on your mind?",
      value: "You are on my mind.",
    }
  ]);

  const onSubmit = (event) => {
    event.preventDefault()

    const getData = async () => {
      const data = {
        prompt: responseInput,
        temperature: 0.5,
        max_tokens: 64,
        top_p: 1.0,
        frequency_penalty: 0.0,
        presence_penalty: 0,
      };

      try {
        const response = await fetch('*********', {
          method: 'POST',
          headers: {
            "Content-Type": "application/json",
            Authorization: `Bearer ${process.env.REACT_APP_USER_KEY}`
          },
          body: JSON.stringify(data),
        });

        if (response.ok) {
          const jsonResponse = await response.json();

          const promptObj = {
            id: uuidV4(),
            inputPrompt: responseInput,            // save current prompt input
            value: jsonResponse.choices[0].text,   // save resolved response value
          }

          setList(list => [...list, promptObj);    // enqueue list state update
          setResponseInput('');                    // clear out prompt value
        }
      } catch (error) {
        console.log(error);
      }
    };

    if (responseInput) {
      getData();
    }
  };

Remove the submit button's onClick handler. Note that even though type="submit" is the default type attribute value, it is best to be explicit anyway by specifying type="submit" so it is overtly clear to any future readers of your code that the button submits a form.

<main className="ui container heading-container">
  <h1 id="heading">Fun With AI</h1>
  <p id="command-description">Hello, my name is GPT-3. Ask me to do something.</p>
  <div className="ui form">
    <form className="field" onSubmit={onSubmit}>
      <label id="prompt-subheading">Enter Prompt</label>
      <div className='ui input focus'>
        <textarea
          type='text'
          value={responseInput}
          onChange={(e) => setResponseInput(e.target.value)}
        />
      </div>
      <button
        className="ui button blue right floated"
        id="submit-button"
        type="submit"
      >
        Submit
      </button>
    </form>
  </div>
</main>

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1