2

Consider the following:

  this.setState({
    validations: [...this.state.validations, ...validations]
  }, () => {
    // ... Do something
  });

Where I am doing: [...this.state.validations, ...validations] validations is an an array of objects.

So for example:

// validation
[{message: 'hello', isError: true, fieldId: 87}, ....] 

Essentially every time we have a validation error we set an array of objects to an array as you see above.

The issue is that sometimes duplicate objects get added. By duplicate I mean on the message section, so this.state.validations could look like:

[
  {message: 'hello', isError: true, fieldId: 87}, 
  {message: 'hello', isError: true, fieldId: 87}, 
  {message: 'hello', isError: true, fieldId: 87}, 
  ...
]

What I would like to do is filter this.state.validations based on the message and fieldId in validations and say:

 // If this message doesn't exist for this field id, set it.

Is there a simple non messy way of doing this where I could use filter or something, to loop over the state validations and the validations comparing both fieldId and message and if it doesn't exist: add it?

0

5 Answers 5

1

You can create a Set of "keys" from your existing state validations array where each key is a string of the form message|fieldId. Then you can create your new array by filtering out elements from validations based on whether on not keys of the new validation messages exist in the set created earlier:

const stateValidations = [
  {message: 'hello', isError: true, fieldId: 87},
  {message: 'world', isError: true, fieldId: 87},
  {message: 'hi', isError: true, fieldId: 88}
];
const validations = [
  {message: 'hello', isError: true, fieldId: 87},
  {message: 'there', isError: true, fieldId: 88}
];

const messageKey = ({message, fieldId}) => `${message}|${fieldId}`;
const existing = new Set(stateValidations.map(messageKey));

const res = [...stateValidations, ...validations.filter(v => !existing.has(messageKey(v)))];
console.log(res);

Sign up to request clarification or add additional context in comments.

6 Comments

To future readers: This solution shows creating a Set and using it to filter validations. This is a different but roundabout way of doing a simple task and involves unnecessary memory allocation for creating a Set. If going with Set, then better to change state validations to be a Set instead of an array.
@Nikhil I'm not sure why you use the terms "roundabout" and "unnecessary". This approach has O(n + m) running time where the tradeoff is extra space for the set, whereas using find results in an O(nm) runtime (although it's certainly possible that the number of objects in the actual situation won't be large enough to make a difference). I'm also not sure what you will accomplish by changing state validations to be a Set.
This approach has O(n + m) time complexity when we consider Set.has() operation to be constant timeO(1). Though it is generally the case, ECMA spec mandates implementations to be linear time O(n), which makes your time complexity O(mn). You can see the comparision in jsperf where find() outperforms this implementation. To add to that, this solution needs extra space for Set. This is the reason for using those terms.
My suggestion for changing state validations to Set reduces lines of code for checking for duplicates and has efficient O(n) time complexity, also highlights the semantical notion of Set being inherently unique, and is an appropriate data structure in this case.
@Nikhil You are misquoting / have misunderstood the answer. According to the spec, they must be implemented in sublinear time: ecma-international.org/ecma-262/6.0/index.html#sec-set-objects. Also note that better running time means it will be faster "eventually" so your input size has to be large enough to see the difference. Finally, the has check will fail for objects if they "look equal" but are actually different objects in memory. That's why you can't create a Set directly from state validations as the has check will fail.
|
1

You can verify if each validation already exists using Array.find() and only add to the state validations if it doesn't exist.

var stateValidations = [
  {message: 'hello', isError: true, fieldId: 87}, 
  {message: 'hello', isError: true, fieldId: 42}, 
  {message: 'error', isError: true, fieldId: 95}, 
];

var validations = [
  {message: 'hello', isError: true, fieldId: 42}, // Already exists.
  {message: 'hello', isError: true, fieldId: 101} // New item.
];

// Check if each validation already exists, and only add if doesn't exist.
validations.forEach(validation => {
  const found = stateValidations.find(item => item.message === validation.message 
    && item.fieldId === validation.fieldId);

  if (!found) {
    // If not found, add validation.
    stateValidations.push(validation);
  }
});

console.log(JSON.stringify(stateValidations));

Comments

1

Well, first of all we need to fix something. setState is asynchronous, and so it is a potential pitfall to call setState and pass in a value that depends upon the previous state.

So instead of doing this: this.setState({ value: ...this.state.value });

We need to use setState with an updater function, like so: this.setState(prevState => ({ value: ...prevState.value }));

Next, lets solve your problem by filtering your new validations, by finding a match in the prevState.validations.

    this.setState(prevState => {
      let toAdd = validations.filter(
        item =>
          undefined ===
          prevState.validations.find(
            prevItem => item.message === prevItem.message && item.fieldId === prevItem.fieldId
          )
      );
      return [...prevState.validations, ...toAdd];
    });

2 Comments

You have to change the filter condition to NOT include an item if already exists. Right now it does the opposite.
@Nikhil ah yes! Thanks for catching that! I've updated the code.
-1

One of the better way to structure the validations as an object instead of an array

e.g.

validations: { [fieldId1]: [{ message: 'hello', isError: true }, { message: 'bye', isError: true}], [fieldId2]: [{ message: 'hello', isError: true }], }

With this structure, you can clearly see with one field Id, what all validations are present.

Now to find out if a validation already occurs on a fieldId, U can do the following:

if (validations[fieldId].filter(v => v.message === 'your message').length === 0) { this.setState({ validations: { ...validations, [fieldId]: [...validations[fieldId], { message: 'your message', isError: true }] } }) }

with this way, when u need to read all the validations on a particular fieldId, you can directly say:

this.state.validations[fieldId]

instead of again filtering the array on fieldId.

Comments

-1

Solution is pretty straightforward ,

  1. find what makes an item unique
  2. loop over existing items and add if they are not repeated. use new item if it is repeated and shorten the new array to improve performance
  3. add all remaining items
const stateValidations = [
  {message: 'hello', isError: true, fieldId: 85},
  {message: 'world', isError: true, fieldId: 86},
  {message: 'hi', isError: true, fieldId: 88}
];
const validations = [
  {message: 'hello', isError: true, fieldId: 87},
  {message: 'there', isError: true, fieldId: 88}
];

let newValidations = stateValidations.reduce((acc, curr)=>{
  let haveNewvalidation = validations.findIndex((v)=>v.fieldId===curr.fieldId);
  if(haveNewvalidation !=-1){
    acc.push(validations[haveNewvalidation]);
    // this will improve findIndex performance
    validations.splice(haveNewvalidation,1);

  } else  {
    acc.push(curr);
  }
  return acc;
}, [])

// push remaining validations
newValidations.push(...validations)

console.log(newValidations)```

Demo: https://repl.it/@abhirathore2006/mergeTwoArrays 

Comments

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.