0

Relatively new to react and working on a project with a backend. Basically all functionality works accordingly, however, I am a little unsure on how to implement a put request that allows me to send only specific values to backend.

WHAT HAPPENS: when I send a request to update a company it requires me fill out all fields. If I leave fields empty, it will send them as null.

E.G:

company has fields of name, email,password. I would like to update ONLY email field without changing the others.

Component:

function UpdateCompany(props): JSX.Element {

  const history = useHistory();
  const [name , setName] = useState('');
  const [email , setEmail] = useState('');
  const [password , setPassword] = useState('');
  const [skipCount, setSkipCount] = useState(true); 

  async function formData() {
    try {

      const response = await axios.put<CompanyModel>(globals.adminUrls.updateCompany + props.location.state.id, {
        name: name,
        email: email,
        password: password
      });

      const updated = response.data;
      store.dispatch(companyUpdatedAction(updated));
      notify.success(SccMsg.COMPANY_UPDATED)

    }
    catch (err) {
      notify.error(err);
    }
  }

  const handleSubmit = (e) => {
      e.preventDefault();
      formData();
  }

  useEffect(() => {
    if (skipCount) setSkipCount(false);
    if (!skipCount)  formData();

}, []);

  return (
    <div className="custom-field">
      <h2>Update Company</h2>
      <div>
        <form onSubmit={handleSubmit} >
          <label>Name</label>
        <input type="text" name="name" onChange={(e)=>{setName(e.target.value)}}/>
        <label>Email</label>
        <input type="text" name="email" onChange={(e)=>{setEmail(e.target.value)}}/>
        <label>Password</label>
        <input type="text" name="password" onChange={(e)=>{setPassword(e.target.value)}}/>

        <input  type="submit" name="submit"/>
        </form>
      </div>

    </div>
  );
}

export default UpdateCompany;

Hope I am clear on my questions.

Thanks.

Backend Controller:

@PutMapping(value = "update/company/{id}")
    @ResponseStatus(HttpStatus.NO_CONTENT)
    @Override
    public void updateCompany(@PathVariable int id, @RequestBody CompanyDto companyDto) throws DoesNotExistException, AlreadyExistsException {
        adminService.updateCompany(id, companyDto);

Backend Service:

 public void updateCompany(@Valid int id, CompanyDto companyDto) throws DoesNotExistException, AlreadyExistsException {

Company companyDao = companyMapper.toDao(companyDto);

if (!compRepo.existsById(id))
    throw new DoesNotExistException("Company does not exist");

companyDao.setId(id);

compRepo.saveAndFlush(companyDao);
Shai Ace
  • 120
  • 2
  • 9
  • not very clear what the problem is – Giorgi Moniava Sep 29 '21 at 16:54
  • @GiorgiMoniava sorry if I was unclear, at the moment if I fill up form fields and try and update only `1` parameter, all others would be `null` and updated accordingly, I would like to make an option to be able to update only `specific` fields without the rest of them turning `null`. Hope I am clear. – Shai Ace Sep 29 '21 at 16:54
  • Well, if you would like to only update the email, then you can send only the email as parameter in the `PUT` request body. I'm not really sure what's blocking you currently. – Salvino D'sa Sep 29 '21 at 16:54
  • @Salvino I would like the user to have all `3` options available to update. but also have a choice to update only selected fields without changing the rest. as of now if I won't update a specific field. it will send it as `null` and I am trying to avoid that. – Shai Ace Sep 29 '21 at 16:56
  • Yep, I got that. So it can be solved by a simple `if` condition. If any property value is equal to null, don't put it in the request body. So if email is populated email will be sent in the request body. If email and name is populated, email and name will be sent in the request body. – Salvino D'sa Sep 29 '21 at 16:57

2 Answers2

0

Just use a simple if condition to perform a null check while populating the PUT request body. You can do it in the following way:

async function formData() {
    try {
     const data = {};
     if(name) data.name = name; 
     if(email) data.email = email;
     if(password) data.password = password
     
     const response = await axios.put<CompanyModel>(globals.adminUrls.updateCompany + props.location.state.id, data);

      const updated = response.data;
      store.dispatch(companyUpdatedAction(updated));
      notify.success(SccMsg.COMPANY_UPDATED)

    }
    catch (err) {
      notify.error(err);
    }
  }
Salvino D'sa
  • 4,018
  • 1
  • 7
  • 19
  • I think this would depend on the end point api. What the OP seems to be saying is if he sends a null for one of those three properties, it won't accept the put request. I could be wrong... I'm kinda making an inference. – silencedogood Sep 29 '21 at 17:27
  • This gives me the same result. if I don't add fields it return's them as `null` – Shai Ace Sep 29 '21 at 17:52
  • ah, that means you're updating all the values even if they don't exist in the back end. Well, this is a logical error in the backend. Have you hardcoded the column keys in the update condition? You should only update the key values passed to you from the end. Not the entire row. – Salvino D'sa Sep 29 '21 at 17:54
  • @Salvino I will try and fix it. I also added some information. Thank you – Shai Ace Sep 29 '21 at 18:06
  • yeah I can see that you're using spring. is it native spring DAO? or you're using some other 3rd party DAO? – Salvino D'sa Sep 29 '21 at 18:07
  • @Salvino Using Spring boot with `JpaRepository` – Shai Ace Sep 29 '21 at 18:08
  • combine my answer with [this](https://stackoverflow.com/questions/65564567/springboot-can-dto-be-changed-at-runtime-with-null-values-not-being-present-in) in backend. That's should do it for you. – Salvino D'sa Sep 29 '21 at 18:14
  • @Salvino I have used to `@JsonInclude(JsonInclude.Include.NON_NULL)` on my `customerDto` bean and used your method, however, spring still check's for nulls on my `Custtomer ` bean and hands me an exception. I would like to have `@Notnull` on my main Customer bean for creation but ignore nulls on update. it doesnt seem to work on my end. – Shai Ace Sep 30 '21 at 11:21
  • Could you please add the `customerDto` bean in the question? Let us have a look at it fully. – Salvino D'sa Sep 30 '21 at 15:07
0

You’ll need to get the current values, then set initial state with them.

This will have the added benefit of showing the current values within the input fields on the form. For this reason, the added call is justified, and very miniscule anyways (how often will company info really be updated??).

Something like this could work:

const [name , setName] = useState('');
const [email , setEmail] = useState('');
const [password , setPassword] = useState('');

// Get the current data, so you can set initial state with it.
useEffect(() => {
    const currentData = await axios.get<CompanyModel>();

    setName(currentData.name);
    setEmail(currentData.email);
    setPassword(currendData.password);

    if (skipCount) setSkipCount(false);
    if (!skipCount)  formData();
}, []);


async function formData() {

  .....

   const response = await axios.put<CompanyModel>(globals.adminUrls.updateCompany + 
      props.location.state.id, {
       name: name,
       email: email,
       password: password
     });

   const updated = response.data;

  ......

}

With this example, you're values will simply remain the same unless the user types something else in the form.

The only thing you may need to change is the get request, as I'm not familiar with your end point.

silencedogood
  • 3,209
  • 1
  • 11
  • 36
  • Wouldn't this be sending the same old values to the `PUT` request for no reason? I mean why send the values to back end if you're not going to update them at all or update them to the same old value? – Salvino D'sa Sep 29 '21 at 17:51
  • @Salvino Nope. It would only send the same thing if the user doesn't change the value within the form. Once the user changes the value in the form, it will update the state, and send the update value upon engaging the put request. – silencedogood Sep 29 '21 at 17:55
  • Yep, ideally, you should put a null check in the front end and only patch the properties that have been passed from the front end in the back-end query. – Salvino D'sa Sep 29 '21 at 17:57
  • @silencedogood I agree with that apart for the extra `async` call, however, I could give you my backend api of the controller and the service. would that help? – Shai Ace Sep 29 '21 at 17:57
  • @salvino I agree on that too. I am trying to figure that out in someway. – Shai Ace Sep 29 '21 at 17:58
  • @ShaiAce If you're able to modify the backend api, then yes. That should have been included in the OP. The only reason I offered this as a solution is because without seeing and being able to modify the backend api, it's the only logical approach. And for the record, the extra call is very common (and justified) in this scenario, as it gives the user the opportunity to see current values before making changes. – silencedogood Sep 29 '21 at 17:58
  • @silencedogood I have added my backend controller and service. in addition I can added any more needed information that could help. Thanks – Shai Ace Sep 29 '21 at 18:02