Since emojiCounter is an array of Objects, you need to first read the Array, update it in the frontend then save it back to Firestore. Using the array-union() method will not work, because it is an array of Objects.
You can verify that with the following code. The objects are added, not updated.
  const db = firebase.firestore();
  const docRef = db.collection('testSO').doc('testSO');
  const emojiCounter = [
    { emoji: 'haha', by: 'user1' },
    { emoji: 'haha', by: 'user2' },
  ];
  docRef
    .set({ emojiCounter })
    .then(() => {
      return docRef.get();
    })
    .then((doc) => {
      console.log('#1', doc.data().emojiCounter);
      return docRef.update({
        emojiCounter: firebase.firestore.FieldValue.arrayUnion({
          emoji: 'hoho',
          by: 'user1',
        }),
      });
    })
    .then(() => {
      return docRef.get();
    })
    .then((doc) => {
      console.log('#2', doc.data().emojiCounter);
      return docRef.update({
        emojiCounter: firebase.firestore.FieldValue.arrayUnion({
          emoji: 'hihi',
          by: 'user2',
        }),
      });
    })
    .then(() => {
      return docRef.get();
    })
    .then((doc) => {
      console.log('#3', doc.data().emojiCounter);
    });
Note that you may encounter a problem with the solution described above, because, depending on your app functions, it may imply that user1 could change the emoji value of user2. If you want to avoid that, you should adopt another approach. For example allow the user to only update his emoji (e.g. with one emoji doc per user) and have a Cloud Function, triggered when one of those docs is updated, and which updates a central document which contains the array. You should use a Transaction to perform the update.
Also, since you are using one array that contains the emojis of all users, note the maximum size of a field value: 1,048,487 bytes.