0

enter image description here

Hello, I have made Firebase function which is watching if users matched. All parts work, but i added one more method getUserDataById where i want to get extra data from users, it returns undefined. So this is what i tried:

exports.UserPressesLike = functions.database
  .ref('/users/{userId}/matches/{otherUserId}')
  .onCreate(async (snapshot, context) => {
    // Grab the current value of what was written to the Realtime Database.
    const original = snapshot.val();
    const userId = context.params.userId;
    const matchedUserId = context.params.otherUserId;
    const a = await checkUserMatch(userId, matchedUserId);
    if (a === true) {
      console.log('Its a match');
      addNewChat(userId, matchedUserId);
      //create chat for both users
    } else {
      console.log('There is no match');
      //do nothing
      console.log(a);
    }

    return null;
  });

checkUserMatch = async (userId, matchedUserId) => {
  const isLiked = await admin
    .database()
    .ref('/users/' + matchedUserId + '/matches/' + userId)
    .once('value')
    .then(snapshot => {
      // let tempuserId = snapshot.val();
      // if()
      let isLiked = snapshot.exists();
      console.log(isLiked);
      return isLiked;
    })
    .catch(error => {
      console.log(error);
      return snapshot;
    });
  return isLiked;
};

addNewChat = async (userId, matchedUserId) => {
  const user1 = await getUserDataById(userId);
  const user2 = await getUserDataById(matchedUserId);
  console.log(JSON.stringify('User data: ' + user1));
  const snapshot = await admin
    .database()
    .ref('/chats')
    .push({
      members: { [userId]: true, [matchedUserId]: true },
      [userId]: { username: [user1.username] },
      [matchedUserId]: { username: [user2.username] },
    });
};

getUserDataById = async userId => {
  const snapshot = await admin
    .database()
    .ref('/users/' + userId)
    .once('value')
    .then(childsnapshot => {
      let data = childsnapshot.val();
      return data;
    });
};

But I get error:

TypeError: Cannot read property 'username' of undefined
    at addNewChat (/srv/index.js:93:36)
    at <anonymous>
    at process._tickDomainCallback (internal/process/next_tick.js:229:7)

The problem is in getUserDataById method. Because it returns undefined. Where I made mistake?

Why I get username: { 0 : emilis} it should be username: emilis?? enter image description here

1 Answer 1

1

Part 1: getUserDataById returns undefined

You forgot return snapshot in your async function. (However, as it as a plain object, not a snapshot, I would rename the variable)

getUserDataById = async userId => {
  const userData = await admin
    .database()
    .ref('/users/' + userId)
    .once('value')
    .then(childsnapshot => {
      let data = childsnapshot.val();
      return data;
    });
  return userData;
};

However, I would flatten it to the following (which is identical to the above, yet concise):

getUserDataById = userId => {
  return admin
    .database()
    .ref('/users/' + userId)
    .once('value')
    .then(childsnapshot => childsnapshot.val());
};

Part 2: Why is my data returned as {username: {0: "Emilis"}}?

{0: "Emilis"} being returned as an object, not an array, is caused by how Firebase stores arrays in the Realtime Database. There is quite a comprehensive article on arrays on the Firebase Blog covering these quirks which I recommend reading. I'll summarise the key ones here.

When any array is stored in the Realtime Database it is stored in it's object form where {username: [user1.username] } will be stored as {username: {0: "someusername"} }. Because JSON is typeless, the Realtime Database no longer understands this entry to be an array. An array with multiple entries will also be stored stored as a plain object ([value1, value2] will become {0: value1, 1: value2}).

When the Firebase JavaScript SDK downloads data from the Realtime Database, it checks the keys of any objects for a mostly sequential numeric sequence (0,1,2,3,... or 0,1,3,4,...) and if detected, converts it to an array using null for any missing entries.

As {0: value1, 1: value2} contains the sequential keys 0 and 1, it will be parsed as [value1, value2].

As {0: "someusername"} does not contain a sequence of keys, it is returned as-is.

To bypass this, remove the single entry array and use it's value directly (as below) or explicitly convert it to an array in your client code.

addNewChat = async (userId, matchedUserId) => {
  const user1 = await getUserDataById(userId);
  const user2 = await getUserDataById(matchedUserId);
  console.log(JSON.stringify('User data: ' + user1));
  return admin // don't forget to return the Promise!
    .database()
    .ref('/chats')
    .push({
      members: { [userId]: true, [matchedUserId]: true }, // FYI: these are "use value as the key" instructions not arrays.
      [userId]: { username: user1.username },
      [matchedUserId]: { username: user2.username },
    });
};
Sign up to request clarification or add additional context in comments.

4 Comments

Furthermore, because you are using the Realtime Database it is more efficient and cost effective to use a getUsernameById function that pulls /users/${userId}/username directly.
Thanks it works, i will fetch user picture to, so it was just an example.
Can you check my updated question, I added picture there.
I highly recommend removing the catch handler in the checkUserMatch function. snapshot will always be undefined in that handler and it will lead to side effects when errors should have been properly handled. If you wish it it to be an optional check that assumes that the user is not liked, use return false instead of return snapshot.

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.