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?

TheWebs
  • 12,470
  • 30
  • 107
  • 211

5 Answers5

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));
Nikhil
  • 6,493
  • 10
  • 31
  • 68
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);
slider
  • 12,810
  • 1
  • 26
  • 42
  • 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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set), then better to change state validations to be a `Set` instead of an array. – Nikhil Sep 25 '19 at 17:10
  • @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`. – slider Sep 25 '19 at 17:22
  • This approach has `O(n + m)` time complexity when we consider `Set.has()` operation to be constant time`O(1)`. Though it is generally the case, ECMA spec mandates implementations to be [linear time `O(n)`](https://stackoverflow.com/a/31092145/2924577), which makes your time complexity `O(mn)`. You can see the comparision in [jsperf](https://jsperf.com/check-if-array-of-objects-exists-in-array-of-objects) where `find()` outperforms this implementation. To add to that, this solution needs extra space for `Set`. This is the reason for using those terms. – Nikhil Sep 25 '19 at 18:16
  • 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 Sep 25 '19 at 18:23
  • @Nikhil You are misquoting / have misunderstood the answer. According to the spec, they must be implemented in *sublinear time*: http://www.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. – slider Sep 25 '19 at 18:39
  • _sublinear_ means almost linear, or less than linear. It also doesn't imply constant time. The spec and my [linked answer](https://stackoverflow.com/a/31092145/2924577) also tell the same. You have brought in time complexity first, and I know it wouldn't be very significant for smaller datasets. I understand what you mean, I overlooked this point about using objects in `Set`. In such case, [Map()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) or even plain JS object with `{message}|{fieldId}` would be ideal. I wouldn't split hairs anymore on this. – Nikhil Sep 25 '19 at 19:17
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];
    });
Benjamin
  • 3,428
  • 1
  • 15
  • 25
-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.

Vishal Sharma
  • 316
  • 1
  • 8
-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 

abhirathore2006
  • 3,317
  • 1
  • 25
  • 29