prevent duplicate objects being added to state react redux

Viewed 10638

I have a question regarding preventing duplicates from being added to my redux store.

It should be straight forward but for some reason nothing I try is working.

export const eventReducer = (state = [], action) => {

    switch(action.type) {

        case "ADD_EVENT":

            return [...state, action.event].filter(ev => {
                if(ev.event_id !== action.event.event_id){
                   return ev;
                }
            });


        default:
            return state;

    }

};

The action looks something like the below:

{
   type: "ADD_EVENT",
   event: { event_id: 1, name: "Chelsea v Arsenal" }
}

The issue is that on occasions the API I am working with is sending over identical messages through a websocket, which means that two identical events are getting added to my store.

I have taken many approaches but cannot figure out how to get this to work. I have tried many SO answers,

6 Answers

You can use Array.prototype.find().

Example (Not tested)

const eventExists = (events, event) => {
  return evets.find((e) => e.event_id === event.event_id);
}
export const eventReducer = (state = [], action) = > {
    switch (action.type) {
    case "ADD_EVENT":
      if (eventExists(state, action.event)) {
        return state;
      } else {
        return [...state, action.event];
      }
    default:
        return state;
    }
};

Update (@CodingIntrigue's comment)

You can also use Array.prototype.some() for a better approach

const eventExists = (events, event) => {
  return evets.some((e) => e.event_id === event.event_id);
}
export const eventReducer = (state = [], action) = > {
    switch (action.type) {
    case "ADD_EVENT":
      if (eventExists(state, action.event)) {
        return state;
      } else {
        return [...state, action.event];
      }
    default:
        return state;
    }
};

Why your code is failing?

Code:

return [...state, action.event].filter(ev => {
    if(ev.event_id !== action.event.event_id){
       return ev;
    }
});

Because first you are adding the new element then filtering the same element, by this way it will never add the new value in the reducer state.


Solution:

Use #array.findIndex to check whether item already exist in array or not if not then only add the element otherwise return the same state.

Write it like this:

case "ADD_EVENT":

    let index = state.findIndex(el => el.event_id == action.event.event_id);

    if(index == -1)
        return [...state, action.event];
    return state;

Solution:

const eventReducer = ( state = [], action ) => {
    switch (action.type) {
        case 'ADD_EVENT':
            return state.some(( { event_id } ) => event_id === action.event.event_id)
                ? state
                : [...state, action.event];
        default:
            return state;
    }
};

Test:

const state1 = eventReducer([], {
    type: 'ADD_EVENT',
    event: { event_id: 1, name: 'Chelsea v Arsenal' }
});

const state2 = eventReducer(state1, {
    type: 'ADD_EVENT',
    event: { event_id: 2, name: 'Chelsea v Manchester' }
});

const state3 = eventReducer(state2, {
    type: 'ADD_EVENT',
    event: { event_id: 1, name: 'Chelsea v Arsenal' }
});

console.log(state1, state2, state3);

You can something like this, for the logic part to ensure you don't get the same entry twice.

const x = filter.arrayOfData(item => item.event_id !== EVENT_FROM_SOCKET);
if (x.length === 0) {
  // dispatch action here
} else {
  // ignore and do nothing
}

You need to be careful when using Arrays in reducers. You are essentially adding more items to the list when you call:

[...state, action.event]

If you instead use a map then you can prevent duplicates

const events = { ...state.events }
events[action.event.event_id] = action.event.name]
{...state, events }

If duplicate exist in previous state then we should return same state else update the state

case "ADDPREVIEW":
     let index = state.preview.findIndex(dup => dup.id == action.payload.id);
     return {
             ...state,
             preview: index == -1 ? [...state.preview,action.payload]:[...state.preview]
     };
Related