Europa
Europa

Reputation: 1292

ReactJS fetch data from backend, show in a form, and send data back

In my ReactJS frontend I am building a form where the user can view and edit settings. The form is part of a function that does the following:

This is a picture of the flow:

enter image description here

This is a picture of the ReactApp:

enter image description here

This is my code so far:

import { useContext, useEffect, useState } from "react";

export function Settings() {
  
    const [data, setData] = useState([]);


    // Send general settings
    const handleSubmit = async (e) => {
      e.preventDefault();
      let result = await fetch("https://localhost:5002/api/update_settings", {
        method: "POST",
        headers: {
          'Content-Type': 'application/json'
        },
        body: data,
      });
      let resultJson = await result.json();
      let resultMessage = resultJson['message']
      let resulData = resultJson['data']
      let resultError = resultJson['error']
      if (result.status === 200 || result.status === 201) {
        document.getElementById("feedback_div").style.display = "block";
        document.getElementById("feedback_p").innerHTML = resultMessage;
      }
      else{
        document.getElementById("feedback_div").style.display = "block";
        document.getElementById("feedback_p").innerHTML = resultError + " " + resultMessage;
      }
    };


    useEffect(() => {
      fetch('https://localhost:5002/api/get_settings')
      .then(response => response.json())
      .then(json => setData(json))

    }, []);
    

  return (
    <div>
      <h1>Settings</h1>
      
        
        {/* Feedback */}
        <div id="feedback_div" style={{display: "none"}}><p id='feedback_p'>Feedback box is here</p></div>

        {/* Form */}
        <form onSubmit={handleSubmit}>
            <label>
            <p>Title</p>
            <input type="text" name="inp_title" value={data?.settings_website_title} onChange={(e) => setData(e.target.value)} />
            </label>

            <label>
            <p>Title short</p>
            <input type="text" name="inp_title_short" value={data?.settings_website_title_short} onChange={(e) => setData(e.target.value)}  />
            </label>

            <p><button>Submit</button></p>
        </form>
      
    </div>
    );
}
  
export default Settings;

Backend get_settings return value:

 {
   "settings_website_title", "My Website",
   "settings_website_title_short", "MyWeb"
 }

My problems:

How can I send the data back to the backend? I belive that when the user makes changes into the text box I call onChange={(e) => setData(e.target.value)} but I do not think this is correct? Because data should be the JSON, and not a single value.

Also I get this error on this code:

Warning: A component is changing an uncontrolled input to be controlled. This is likely caused by the value changing from undefined to a defined value, which should not happen. Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/link/controlled-components

Upvotes: 0

Views: 1694

Answers (2)

Satwinder Singh
Satwinder Singh

Reputation: 229

You are in right direction but doing small mistakes. Please follow below instructions to save the data successfully.

  // 1. initialize your form inputs here,, it will be used to send data in api request body
const [data, setData] = useState({
    settings_website_title: "",
   settings_website_title_short: ""
 });

 // 2.  You can't override the data in @data varaible. So your input will look something like this.
 <input type="text" name="inp_title" value={data?.settings_website_title} onChange={(e) => setData({...data,  settings_website_title: e.target.value})} />


 // 3. on click of submit button call the method where you make api call
 <button onClick={(e) => {handleSubmit()}}>Submit</button>

Upvotes: 0

David
David

Reputation: 219047

but I do not think this is correct? Because data should be the JSON, and not a single value.

Indeed. Instead of setting the entirety of state to one value, set it to an updated version of itself in which that one value is changed. For example:

<input
  type="text"
  name="inp_title"
  value={data?.settings_website_title}
  onChange={(e) => setData({ ...data, settings_website_title: e.target.value })}
/>

You can extract multiple settings into a single update handler if you align the name of the element with the property being updated. For example:

<input
  type="text"
  name="settings_website_title"
  value={data?.settings_website_title}
  onChange={handleChange}
/>
and...
<input
  type="text"
  name="settings_website_title_short"
  value={data?.settings_website_title_short}
  onChange={handleChange}
 />

Then your handleChange function can be something like:

const handleChange = e => {
  setData({
    ...data,
    [e.target.name]: e.target.value
  });
};

In either case, you're replacing the state object with a copy of itself, changing only the one property updated by that <input> value.


Warning: A component is changing an uncontrolled input to be controlled...

This is because the initial values for your inputs are undefined (or possibly null). An empty string would be preferable in this case. Instead of using optional chaining for the values, initialize state to a default. For example:

const [data, setData] = useState({
  settings_website_title: '',
  settings_website_title_short: ''
});

Then you don't need the optional chaining in the value properties on your <input> elements:

value={data.settings_website_title}

Since data will always be a valid object with the properties you're looking for.

Upvotes: 1

Related Questions