[React] Item correctly deleted, but visually shows last item in array deleted

Working on a full-stack Node/React app. Lots of new stuff for me. I must be missing something, though. I was working on styling the inputs for a table that is generated from my MongoDB data, when I realized that when the user deletes an item, it shows the last item in the array (so, the bottom of the table) being removed. However, upon refresh of the page (and verifying by looking at the db), the correct item is deleted.

What am I missing?

Here is the repo.
We’re working on:
src/actions/moments,
src/components/moment,
src/components/updateMoment.

Sorry, I don’t currently have a deployed version. That’s also on the to-do, as my server-side isn’t deploying right now.

I must be lost but I don’t see any action id in the action creator:

// ACTION
export const deleteMomentSuccess = data => ({
  type: DELETE_MOMENT_SUCCESS,
  values: data
});


// REDUCER
  if (action.type === DELETE_MOMENT_SUCCESS) {
    let momentsArr = [...state.moments];
    let deletedItem = momentsArr.findIndex(item => item.id === action.id);

    momentsArr.splice(deletedItem, 1);
    return Object.assign({}, state, {
      moments: momentsArr
    });
  }

So I fear that deletedItem is undefined so effectively not splicing anything.
This may be the issue.

Side note:
I personally don’t like the direction you are taking on this. On one hand you have the client with it’s own “copy” of the server data, and the user sees this.
On the other end you perform some action on the BE but never use that data, so your hope is that the client and the BE stays “in sync”.

I’ve found this be a huge inconsistency that came and bit me hard.

Major problem was wen two clients were seeing two different output due to some concurrency.
We removed the client side entirely and relied only on BE.
Which is also more straightforward

load data -> user interact -> request update -> wait response -> use new data

We used client side data only for portion that were waiting to be sent to the server, like drafts :slight_smile:

hope it helps.

Also final note. The whole removing items from the list, assuming you know the index of the element to be removed could be simplified with

return [...array.slice(0, index), ...array.slice(index + 1)]

:+1:

2 best practices:

  • don’t use an array index to select/remove data, use an unique id, especially if you work with a database (async problems!)
  • use filter() to remove data, in combination with the unique id from above
  • and like Marmiz said: keep your data in sync!
1 Like

Interesting. I added a console.log(momentsArr.splice(deletedItem, 1)); and it does indeed return the incorrect item, as it’s just grabbing the last item in the array. I’ll see if I can work out how to do it using your example.

Returning to the action… This has been pretty difficult for me to understand. So, I need to pass the id thru the Reducer as well? Does this work? I’m not sure how to check. :confused:

export const DELETE_MOMENT_SUCCESS = 'DELETE_MOMENT_SUCCESS';
export const deleteMomentSuccess = (data, itemIndex) => ({
  type: DELETE_MOMENT_SUCCESS,
  values: data,
  itemIndex
});

As for how I’m structuring this – I’m enrolled with Thinkful, and we are told to write a front-end and back-end. Is that what you’re referring to? If you don’t have a “copy” on the front-end, how else would you create the front-end? What back-end data is never used? Unless I’m misunderstanding, which is definitely the case, because I’m surprised I’ve made it this far.

@miku86:
In our Thinkful program, we were told to use the index value. I know this is a huge no-no, because I’ve read numerous other places not to use the index value. However, I’ve also never seen an example of what to use instead.

edit: The component is getting the object id passed to it, so I could use that. Now, to figure out how to pass that on to be used.

Could you give an example of using filter() with a unique id?

Thank you both. Apologies for my being aloof.

Edit: I’ve got it working. Sorry, it had been a while since I built those actions and reducers, and since this is all new, I was a little rusty on how I’d written them. Here’s what they look like now, and they’re working:

// REDUCER
  if (action.type === DELETE_MOMENT_SUCCESS) {
    let momentsArr = [...state.moments];
    let deletedItem = momentsArr.findIndex(item => item.id === action.id);

    momentsArr.splice(deletedItem, 1);
    return Object.assign({}, state, {
      moments: momentsArr
    });

// ACTION
export const DELETE_MOMENT_SUCCESS = 'DELETE_MOMENT_SUCCESS';
export const deleteMomentSuccess = id => ({
  type: DELETE_MOMENT_SUCCESS,
  id

I do want to do this more correctly though, so I’m looking at filter().

Redux has great docs: https://redux.js.org/basics/reducers#handling-actions

(previousState, action) => newState

The reducer is a pure function that takes the previous state and an action, and returns the next state.

So if you think about it, your previousState is your array of moments and the newState (= next state) should be your array of moments minus the moment you want to delete/deleted. So the reducer has to know, which moment you want to delete to go from previousState to newState. So action is the only thing left, where you could send this information.

Everytime I write a reducer case, I put two console.log()s after the case/if, so that I know at the beginning, what my current state and action are.

with case:

case MY_ACTION:
  console.log(state);
  console.log(action);  

with if:

if (action.type === MY_ACTION){
  console.log(state);
  console.log(action);  
}

You have a backend, e.g. with a database.
When a user goes to the frontend, his data gets fetched from the backend.
Now the frontend and the backend have the same data, they are in sync.
Now, if the user changes some data in the frontend and you don’t send your changes to the backend,
the data is out of sync. The same problem applies the other way round as well.
So try to have synced data. How you do this depends on your application architecture.

Think about this: If you use an array, you have to have a close look at the order of the actions:

const coolGuys = [
  { "id": 77, "name": "miku86" },
  { "id": 5, "name": "SlouchingToast" },
  { "id": 101, "name": "Marmiz" },
];

If you want to delete SlouchingToast and miku86, you first have to delete index 1 and then index 0. => remove index 1 (SlouchingToast), remove (updated) index 0 (miku86)

But what will happen, if the user clicks in the correct order, but due the async nature, the execution turns around? => remove index 0 (miku86), remove (updated) index 1 (Marmiz)

const coolGuys = [
  { "id": 77, "name": "miku86" },
  { "id": 5, "name": "SlouchingToast" },
  { "id": 101, "name": "Marmiz" },
];

const removeGuy = idOfGuyIWantToDelete => {
  return coolGuys.filter(guy => guy.id !== idOfGuyIWantToDelete);
}

console.log(removeGuy(77));

If you build this in React, most of the time you have an array of guys and you map over them to build a HTML List or Table. You can assign the List Item or Table Row to the guy.id, then you only have to read the (grand)parent of event.currentTarget of the button you clicked, if you have one delete button for every Table Row.

@miku86 did an awesome job explaining why IDs are preferred, and I endorse that.

Actions need to return Objects. This objects are picked up in the reducer function.
So If I were to write an action like this

const fakeAction = () => ({
 type: 'fake',
 a: 1,
 b: 2,
 z: 'boring value' 
})

Means I can reference this object in the reducer and access its value in this case is:

action.z // 'boring value'
action.type // 'fake'
action.a // 1

In your previous attempt you tried to use an action.id but there were no id prop in the action object :slight_smile:


The problem here is that you are fetching some data, and then store it locally in Redux as state, and rely on that from that moment on for the client.

This means that every change the client does you have to update both the client and the original data (let’s assume is a DB).

eg:

user click delete
 - delete from redux so the client update
 - send request to server so the element is removed from DB

The issue here is that if one of the two fails, you can occur in data that is not in “sync”, like you had at the start.

Your BE correctly deleted the data, but your Redux not. So the client still see the element on the page, but as a matter of fact is “gone”.
That’s why upon reload the data was missing, the two instances (Redux / DB) were not in sync anymore.

You should have only “one source of truth”, in my opinion in this case should be the DB.