Home > Enterprise >  redux thunk doesnt delete items from the API
redux thunk doesnt delete items from the API

Time:12-17

My app is a simple bookstore, I am using this API enter image description here

CodePudding user response:

Issue

From what I can read of the code it appears at some point when initiating the process to remove a book you drop the correct id somewhere along the way.

  1. User clicks "Remove" button and deleteHandler is called and { item_id } value is passed.
  2. deleteHander names argument id
  3. deleteHandler dispatches the deleteBooks action with { item_id } as argument, now named bookId
function Book({ title, author, item_id, category }) {
  const dispatch = useDispatch();

  const deleteHandler = (id) => { // (2) id == { item_id }
    console.log(id);
    dispatch(deleteBooks(id)); // (3)
  }

  return (
    <>
      <span>
        {title}
        </span>
        <br/>
        <span>
        {author}
      </span>
      <br/>
      <span>
        {category}
      </span>
      <br/>
      <button
        type="button"
        onClick={() => deleteHandler({ item_id })} // (1)
      >
        Remove
      </button>
    </>
  );
}
  1. deleteBooks consumes bookId and passes it directly in the DELETE requests URL path, and in the request body

It is at this point I'd expect the DELETE request to fail in the backend since the URL is likely incorrect and the body is incorrect. fetch only throws an error on network errors or cancelled requests though, so I am sure the code just continues along error-free. I'm fairly certain response.data is simply undefined.

  1. deleteBooks dispatches removeBook and passes bookId, still { item_id }, as a payload
export const deleteBooks = createAsyncThunk(
  REMOVE,
  async (bookId, thunkAPI) => {
    const response = await fetch(`${BOOK_URL}/${bookId}`,  { // (4)
      method: 'DELETE',
      body: JSON.stringify({
        item_id: bookId // (4) { item_id: { item_id } }
      }),
      headers: {
        'Content-Type': 'application/json',
      },
    });
    await thunkAPI.dispatch(removeBook(bookId)); // (5)
    return response.data;
  }
);
  1. removeBook takes { item_id } as a payload value
export const removeBook = (book) => ({ // (6) { item_id }
  type: REMOVE,
  book, // (6) { item_id } -> action.book.item_id
});
  1. bookReducer reducer function handles REMOVE action and correctly accesses into the action payload to get the nested item_id property and filters the book state array.
const bookReducer = (state = initialState, action) => {
  switch (action.type) {
    ...

    case REMOVE: 
      return state.filter((book) => book.item_id !== action.book.item_id); // (7)

    ...
  }
};

Solution

Payload/argument naming issues aside, you can likely fix the issue in the deleteBooks action by correctly accessing into the passed "book" object to get the nested item_id property.

Example:

export const deleteBooks = createAsyncThunk(
  REMOVE,
  async (book, thunkAPI) => {
    const { item_id } = book // <-- destructure item_id from book object
    const response = await fetch(`${BOOK_URL}/${item_id}`,  { // <-- pass in URL path
     method: 'DELETE',
      body: JSON.stringify({ item_id }), // <-- pass in body
      headers: {
        'Content-Type': 'application/json',
      },
    });
    await thunkAPI.dispatch(removeBook(book)); // <-- pass book object
    return response.data;
  }
);

Suggestions

Use consistent naming throughout code

I suggest editing all these functions/actions/reducers/etc to consistently name the referenced objects throughout the code. It's much easier to keep and maintain the mental mapping of the data flowing through/across the app.

Since the Book component starts with just a book id, i.e. the item_id prop, then passing a "book id" value around makes a bit more sense.

function Book({ title, author, item_id, category }) {
  const dispatch = useDispatch();

  const deleteHandler = (id) => {
    console.log(id);
    dispatch(deleteBooks(id));
  }

  return (
    <>
      <span>
        {title}
        </span>
        <br/>
        <span>
        {author}
      </span>
      <br/>
      <span>
        {category}
      </span>
      <br/>
      <button
        type="button"
        onClick={() => deleteHandler(item_id)} // <-- pass book id
      >
        Remove
      </button>
    </>
  );
}
export const deleteBooks = createAsyncThunk(
  REMOVE,
  async (bookId, thunkAPI) => {
    const response = await fetch(`${BOOK_URL}/${bookId}`,  {
      method: 'DELETE',
        body: JSON.stringify({ item_id: bookId }),
      headers: {
        'Content-Type': 'application/json',
      },
    });
    await thunkAPI.dispatch(removeBook(bookId));
    return response.data;
  }
);
export const removeBook = (bookId) => ({
  type: REMOVE,
  bookId,
});
const bookReducer = (state = initialState, action) => {
  switch (action.type) {
    ...

    case REMOVE: 
      return state.filter(
        (book) => book.item_id !== action.bookId // <-- access book id
      );

    ...
  }
};

Check for successful response when removing book

The the fetch response ok* property to ensure the DELETE request was actually successful.

Example:

export const deleteBooks = createAsyncThunk(
  REMOVE,
  async (bookId, thunkAPI) => {
    const response = await fetch(`${BOOK_URL}/${bookId}`,  {
      method: 'DELETE',
        body: JSON.stringify({ item_id: bookId }),
      headers: {
        'Content-Type': 'application/json',
      },
    });
    
    if (response.ok) {
      thunkAPI.dispatch(removeBook(bookId));
    }
    return response.data;
  }
);

*Note: response.ok is pretty standard but consult the API documentation if success/failure is expressed differently.

  • Related