Skip to content

refactoring assistance with reducing some array data objects in JavaScript

I need to reduce data in profiles array in a way such that the final object groups the data in profile obj based on the favorite movie and the users that liked/favorited the movie. I want something like:

{
 'Forrest Gump': ["Nicholas Lain"],
 'Planet Earth 1': ["Jane Jones", "Matthew Johnson"]
}

from the following data objects:

const profiles = [
      {
        id: 1,
        userID: '1',
        favoriteMovieID: '1',
      },
      {
        id: 2,
        userID: '2',
        favoriteMovieID: '1',
      },
      {
        id: 3,
        userID: '4',
        favoriteMovieID: '5',
      }
    ];

    const users = {
      1: {
        id: 1,
        name: 'Jane Cruz',
        userName: 'coder',
      },
      2: {
        id: 2,
        name: 'Matthew Johnson',
        userName: 'mpage',
      }
    };

    const movies = {
      1: {
        id: 1,
        name: 'Planet Earth 1',
      },
      2: {
        id: 2,
        name: 'Selma',
      }
    };

I need some ideas in refactoring the following code for it to go back to the users and movies object to grab their names from the IDs I have captured below. Instead of IDs, I need to capture the names.

profiles.reduce(function (acc, obj) {
    let key = obj['favoriteMovieID']
    if (!acc[key]) {
      acc[key] = []
    }
    acc[key].push(obj.userID)
    return acc
  }, {})

Answer

Here is one technique, doing a fold on the profiles, grabbing the movie and person names inside the parameters, and then simply writing a new accumulator with that data. Note that there is a potential performance problem with this, as described in Rich Snapp’s excellent article. If that causes you an actual issue, it’s easy enough to change this to mutate the accumulator.

I added some additional data to show what happens when the user or the movie isn’t in the appropriate lists. If that cannot ever happen, you can simplify the name and person declarations a bit. But I wouldn’t recommend it, as things that “can never happen” in fact regularly do happen.

const groupNamesByMovie = (profiles, users, movies) => 
  profiles .reduce ((
    a, {userID, favoriteMovieID}, _, __, 
    {name} = movies [favoriteMovieID] || {name: 'Unknown Movie'},
    {name: person} = users [userID] || {name: 'Unknown Person'}
  ) => ({
    ...a,
    [name]: [... (a [name] || []), person]
  }), {})

const profiles = [{id: 1, userID: "1", favoriteMovieID: "1"}, {id: 2, userID: "2", favoriteMovieID: "1"}, {id: 3, userID: "4", favoriteMovieID: "5"}, {id: 4, userID: "6", favoriteMovieID: "5"}, {id: 5, userID: "5", favoriteMovieID: "7"}]
const users = {1: {id: 1, name: "Jane Cruz", userName: "coder"}, 2: {id: 2, name: "Matthew Johnson", userName: "mpage"}, 4: {id: 4, name: "Nicholas Lain", userName: "nlain"}, 5: {id: 5, name: "Fred Flintstone", userName: "bedrock1"}}
const movies = {1: {id: 1, name: 'Planet Earth 1'}, 2: {id: 2, name: 'Selma'}, 5: {id: 5, name: 'Forrest Gump'}}

console .log (
  groupNamesByMovie (profiles, users, movies)
)

Note that the arguments _ and __ are just meant to be placeholders, since we don’t care about reduce‘s index and array parameters.

Update

There was a request for clarification. For comparison, here’s a more imperative version of this same idea:

const getNamesByMovie = (profiles, users, movies) =>
  profiles .reduce ((acc, {userID, favoriteMovieID}) => {
    const movie = movies [favoriteMovieID]
    const name = movie ? movie.name : 'Unknown Movie'
    const user = users [userID]
    const person = user ? user.name : 'Unknown Person'
    const fans = acc [name] || []
    return {
      ... acc,
      [name]: [... fans, person]
    }

  }, {})

And if you wanted to avoid that potential performance problem, you could replace the return statement with something like this:

    acc [name] = fans
    fans .push (person)
    return acc

Either of these does the same sort of thing as the original above. I choose that initial style because I don’t like mutating the accumulator object, preferring to always create a new version… and because I prefer working with expressions over statements. But this style does take some getting used to.

You also asked how we passed additional parameters to the reduce callback. We don’t. Instead we define some additional parameters and initialize them based on the earlier parameters.