2

I am fetching a pets list from the swagger pets API. I want to remove pets with duplicate ids and then send it to React to avoid the duplicate key problem. I tried the code below but does not work for me. Can anyone help me out with this?

I am new to RxJS so want to remove the commented code which filters distinct objects based on pet id.

export function fetchPetEpic(action$) {
    return action$.pipe(
        ofType('GET_PETS'),
        switchMap(action => 
            ajax.getJSON(`https://petstore.swagger.io/v2/pet/findByStatus?status=${action.payload}`).pipe(
                map( response => response.map( (pet) => ({
                    id: pet.id,
                    pet: pet.pet
                }))),
                distinct( petList => petList.id),
                // map( petList => petList.filter((pet, index, list) => {
                //                     return list.map(petObj =>
                //                         petObj.id).indexOf(pet.id) === index;
                //                     })),
                map(response => petActions.setPets(response))
        )),
        catchError( error => ({ type: 'ERROR_PETS_FETCH', payload: error})),
    )
}

Suppose the pets array is something like [ { id: 1, pet: dog}, { id: 2, pet: cat }, { id: 3, pet: fish}, { id: 2, pet: rat }, { id: 4, pet: parrot}, { id: 1, pet: owl } ]

Expected output:[{ id: 1, pet: dog}, { id: 2, pet: cat }, { id: 3, pet: fish}, { id: 4, pet: parrot}]

The output I am getting is the same array as input.

3
  • The docs for distidnct say, "Returns an Observable that emits all items emitted by the source Observable that are distinct by comparison from previous items." Unless you get a duplicate one right after the other, distinct won't cut it. In fact-- I think you're better off without. You want to operate on the set as an event, not individual items within the set as events. Commented Jun 7, 2019 at 6:56
  • But if you check the link in official docs it has the same problem solved - rxjs-dev.firebaseapp.com/api/operators/distinct. Cannot figure out why it's not working for me ! Commented Jun 7, 2019 at 7:09
  • @seniorquico could you please look at the above link which I mentioned. Commented Jun 7, 2019 at 10:58

1 Answer 1

7

The getJSON function will return an observable that emits one item (the response) and completes. (Or, it can emit one error.) Given your happy path example of returning an array...

The following map operator will receive the deserialized JSON array and map it to a new array. That is, the map operator will have exactly one input event and result in exactly one output event.

map(response => response.map(pet => ({
  id: pet.id,
  name: pet.name,
  status: pet.status,
}))),

The distinct operator works across multiple events. For each event, it filters out any duplicates (with the definition of duplicate customizable as you've noted). The following distinct operator will always pass through your single event. Also, take note that petList is an array. Arrays don't have an id property, so the lambda will return undefined. It doesn't really matter what the lambda returns, though. Because there's only a single event, it's not using the return value in any comparisons.

distinct(petList => petList.id),

It appears you're expecting distinct to process each element of the array. To accomplish this, you can use from to convert the array to an observable and concatMap to emit each element in order. With multiple events flowing through the pipe, distinct can do its magic:

...
concatMap(from), // shorthand for: `concatMap(response => from(response)),`
map(pet => ({
  id: pet.id,
  name: pet.name,
  status: pet.status,
})),
distinct(pet => pet.id),
...

However, this change would result in emitting multiple Redux actions (one for each distinct pet). We can use the reduce operator to accumulate each of the distinct pets back into a single array before emitting the Redux action.

...
reduce((pets, pet) => [...pets, pet], []),
map(pets => petActions.setPets(pets)),
...

Let's put it all together:

export function fetchPetEpic(action$) {
  return action$.pipe(
    ofType('GET_PETS'),
    switchMap(action =>
      ajax.getJSON(`https://petstore.swagger.io/v2/pet/findByStatus?status=${action.payload}`).pipe(
        concatMap(from), // shorthand for: `concatMap(response => from(response)),`
        map(pet => ({
          id: pet.id,
          name: pet.name,
          status: pet.status,
        })),
        distinct(pet => pet.id),
        reduce((pets, pet) => [...pets, pet], []),
        map(pets => petActions.setPets(pets)),
        catchError(error => ({ type: 'ERROR_PETS_FETCH', payload: error })),
      )
    ),
  )
}

One final note: Something looks off between the example data you provided and the map you're applying. If the array looks like the following:

[ { id: 1, pet: dog }, ...

Then each element doesn't have name or status properties, just id and pet properties. Maybe the following is intended?

map(item => ({
  id: item.id,
  name: item.pet.name,
  status: item.pet.status,
})),

Or maybe it's just a problem with the example data you provided. Either way, I'd recommend double checking the map to ensure it projects your values as expected.

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

2 Comments

Thanks buddy. Worked for me and now I know where it went wrong !
This is misusing the distinct and reduceoperators in my opinion. they are built for diferent purposes rather than manipulating arrays.

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.