0

I am having a onChange function i was trying to update the array optionUpdates which is inside of sentdata by index wise as i had passed the index to the onChange function.

Suppose i update any two values of the input field from option which is inside of postdata therefore the input name i.e. orderStatus with changed value and with order should be saved inside of optionUpdates

For example: Suppose i update the option 1 and option 3 of my postdata further inside of options of orderStatus values so my optionUpdates which is inside of sentdata should look like this

optionUpdates: [
{
  Order: 1,
  orderStatus: "NEW1"
},
{
  Order: 3,
  orderStatus: "New2"
}
]

here is what i tried

setSentData(oldValue => {
      const curoptions = oldValue.sentdata.optionUpdates[idx];
      console.log(curoptions);
      curoptions.event.target.name = event.target.value;
      return {
        ...oldValue,
        sentdata: {
          ...oldValue.sentdata.optionUpdates,
          curoptions
        }
      };
    });
  };

Demo

complete code:

import React from "react";
import "./styles.css";

export default function App() {
  const x = {
    LEVEL: {
      Type: "LINEN",
      options: [
        {
          Order: 1,
          orderStatus: "INFO",
          orderValue: "5"
        },
        {
          Order: 2,
          orderStatus: "INPROGRESS",
          orderValue: "5"
        },
        {
          Order: 3,
          orderStatus: "ACTIVE",
          orderValue: "9"
        }
      ],
      details: "2020  N/w UA",
      OrderType: "Axes"
    },
    State: "Inprogress"
  };

  const [postdata, setPostData] = React.useState(x);

  const posting = {
    optionUpdates: []
  };

  const [sentdata, setSentData] = React.useState(posting);

  const handleOptionInputChange = (event, idx) => {
    const target = event.target;
    setPostData(prev => ({
      ...prev,
      LEVEL: {
        ...prev.LEVEL,
        options: prev.LEVEL.options.map((item, id) => {
          if (id === idx) {
            return { ...item, [target.name]: target.value };
          }
          return item;
        })
      }
    }));
    setSentData(oldValue => {
      const curoptions = oldValue.sentdata.optionUpdates[idx];
      console.log(curoptions);
      curoptions.event.target.name = event.target.value;
      return {
        ...oldValue,
        sentdata: {
          ...oldValue.sentdata.optionUpdates,
          curoptions
        }
      };
    });
  };

  return (
    <div className="App">
      {postdata.LEVEL.options.map((item, idx) => {
        return (
          <input
            key={idx}
            type="text"
            name="orderStatus"
            value={postdata.LEVEL.options[idx].orderStatus}
            onChange={e => handleOptionInputChange(e, idx)}
          />
        );
      })}
    </div>
  );
}

1 Answer 1

1

If I've understood correctly then what you're looking to do is save a copy of the relevant options object in sentdata every time one changes. I think the best way to approach this is by doing all your state modification outside of setPostData, which then makes the results immediately available to both setPostData and setSentData. It will also make the setters easier to read, which is good because you have some quite deeply nested and complicated state here.

A few other things worth noting first:

  1. Trying to use synchronous event results directly inside the asynchronous setter functions will throw warnings. If you do need to use them inside setters, then it is best to destructure them from the event object first. This implementation uses destructuring although it didn't end up being necessary in the end.

  2. You seem to have got a bit muddled up with setSentData. The oldValue parameter returns the whole state, as prev in setPostData does. For oldValue.sentdata you just wanted oldValue. You also wanted curoptions[event.target.name], not curoptions.event.target.name.

So, on to your code. I would suggest that you change the way that your input is rendered so that you are using a stable value rather than just the index. This makes it possible to reference the object no matter which array it is in. I have rewritten it using the Order property - if this value is not stable then you should assign it one. Ideally you would use a long uuid.

    {postdata.LEVEL.options.map(item => {
        return (
          <input
            key={item.Order}
            type="text"
            name="orderStatus"
            value={item.orderStatus}
            onChange={e => handleOptionInputChange(e, item.Order)}
          />
        );
    })}

The handleOptionInputChange function will now use this Order property to find the correct objects in both postdata and sentdata and update them, or if it does not exist in sentdata then push it there. You would do this by cloning, modifying, and returning the relevant array each time, as I explained before. Here is the function again with comments:

    const handleOptionInputChange = (event, orderNum) => {
        const { name, value } = event.target;

        /* Clone the options array and all objects 
        inside so we can mutate them without 
        modifying the state object */
        const optionsClone = postdata.LEVEL.options
          .slice()
          .map(obj => Object.assign({}, obj));

        /* Find index of the changed object */
        const optionIdx = optionsClone.findIndex(obj => obj.Order === orderNum);

        /* If the orderNum points to an existing object...*/
        if (optionIdx >= 0) {

          /* Change the value of object in clone */
          optionsClone[optionIdx][name] = value;

          /* Set postdata with the modified optionsClone */
          setPostData(prev => ({
            ...prev,
            LEVEL: {
              ...prev.LEVEL,
              options: optionsClone
            }
          }));

          /* Clone the optionUpates array and all 
          contained objects from sentdata */
          const updatesClone = sentdata.optionUpdates
            .slice()
            .map(obj => Object.assign({}, obj));

          /* Find the index of the changed object */
          const updateIdx = updatesClone.findIndex(obj => obj.Order === orderNum);

          /* If the changed object has already been 
          changed before, alter it again, otherwise push 
          a new object onto the stack*/
          if (updateIdx >= 0) {
            updatesClone[updateIdx][name] = value;
          } else {
            updatesClone.push({ Order: orderNum, [name]: value });
          }

          /* Set sentdata with modified updatesClone */
          setSentData(prev => ({
            ...prev,
            optionUpdates: updatesClone
          }));
        }
    };
Sign up to request clarification or add additional context in comments.

6 Comments

Hi @lawrence-witt. There seems a small problem that is whenever i update the value for the input field it does create the new object each time , where as it should update in same object in sentdata . even on single update setSentData it create new object
Ah, ok, I thought that's what you wanted. I do wonder why sentdata is even necessary now though, since its just a copy of x.LEVEL.options? The solution is basically to do the same thing again; clone the array of objects you want to modify, mutate it, then put it back into state. I've updated my answer using the Order property to do this, although I don't know if it's a stable value. If it's not then you should assign each option a unique identifier to use in place of idx.
Hi @lawrence-witt Can you please bring some comment in the code as i am getting confused in some points, if possible.
Can you tell how does my optionsClone get update with name and value as you have never updated that one
Ok, I think I understand what you're after now and have done a rewrite of my answer. We update optionsClone when we update alteredObject, as it retains its reference to optionsClone. This is why we had to clone everything in the first place, to stop the original state being mutated by reference. EDIT: I realised we don't need alteredObject at all anymore, it's gone now and we target optionsClone directly.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.