Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
209 views
in Technique[技术] by (71.8m points)

javascript - React hooks, component is reading old data from Redux

My component is set to display a list of books, as card thumbnails. Each item from the list of books is generated by this component. Each Card has a favorites icon, when clicking it adds the book to favoriteTitles array. By pressing again on the favorites icon it removes it from the list.

const Card = ({ title, history }) => {
  const dispatch = useDispatch();
  const { favoriteTitles } = useSelector(({ titles }) => titles);
  const { id, name, thumbnail } = title;
  const [favorite, setFavorite] = useState(favoriteTitles?.some(item => item.titleId === title.id));

  const handleFavoriteClick = () => {
      const isFavorite = favoriteTitles?.some(item => item.titleId === title.id);
      if (isFavorite) {
      dispatch(removeFavoriteTitle(title));
      setFavorite(false);
    } else {
      dispatch(addFavoriteTitle(title));
      setFavorite(true);
    }
  };

  return (
  <CardContainer>
    <Thumbnail thumbnail={thumbnail} />
    {name}
    <FavesIcon isActive={favorite} onClick={handleFavoriteClick} />
  </CardContainer>
  );
};

The issue with this component is when you press once on FavesIcon to add, and if you changed your mind and want to remove it and press right away again, the favoritesTitles array still has the old value.

Let's suppose our current favorites list looks like this:

const favoritesTitles = [{titleId: 'book-1'}];

After pressing on favorites icon, the list in Redux gets updated:

const favoritesTitles = [{titleId: 'book-1'}, {titleId: 'book-2'}];

And if I press again to remove it, the favoritesTitles array inside the component is still the old array with 1 item in it. But if I look in Redux the list updated and correct.

How component should get the updated Redux value?

Update

I have specific endpoints for each action, where I add or remove from favorites:

GET: /users/{userId}/favorites - response list eg [{titleId: 'book-1'}, {titleId: 'book-2'}]

POST: /users/me/favorites/{titleId} - empty response

DELETE: /users/me/favorites/{titleId} - empty response

For each action when I add or remove items, on success request I dispatch the GET action. Bellow are my actions:

export const getFavoriteTitles = userId =>
  apiDefaultAction({
    url: GET_FAVORITE_TITLES_URL(userId),
    onSuccess: data => {
      return {
        type: 'GET_FAVORITE_TITLES_SUCCESS',
        payload: data,
      };
    },
  });

export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
  return dispatch(
    apiDefaultAction({
      method: 'POST',
      url: SET_FAVORITE_TITLES_URL,
      data: {
        titleId: id,
      },
      onSuccess: () => {
        dispatch(getFavoriteTitles(userId));
        return { type: 'SET_FAVORITE_TITLE_SUCCESS' };
      },
    })
  );
}; 

My reducers are pretty straight forward, I'm not mutating any arrays. Since only GET request is returning the list of array, I don't do any mutating in my reducers:

case 'GET_FAVORITE_TITLES_SUCCESS':
  return {
    ...state,
    favoriteTitles: action.payload,
  };
case 'SET_FAVORITE_TITLE_SUCCESS':
  return state;
case 'DELETE_FAVORITE_TITLE_SUCCESS':
  return state;

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

It seems that by the time you click FavesIcon second time after adding to favourites, GET: /users/{userId}/favorites request is still pending and favoriteTitles list is not updated yet. That's why the component still contains an old value.

You need to update favoriteTitles list right away after triggering addFavoriteTitle or removeFavoriteTitle actions, without waiting GET_FAVORITE_TITLES_SUCCESS action to be dispatched. This pattern is called 'Optimistic UI':

export const toggleFavorite = itemId => {
  return {
    type: 'TOGGLE_FAVORITE',
    payload: { itemId },
  };
}

export const addFavoriteTitle = (userId, id) => (dispatch, getState) => {
  dispatch(toggleFavorite(id));
  return dispatch(
    ...
  );
};

export const removeFavoriteTitle = (userId, id) => (dispatch, getState) => {
  dispatch(toggleFavorite(id));
  return dispatch(
    ...
  );
}; 

And your reducer can look something like this:

case 'TOGGLE_FAVORITE':
  return {
    ...state,
    favoriteTitles: state.favoriteTitles.map(item => item.titleId).includes(action.payload.itemId)
      ? state.favoriteTitles.filter(item => item.titleId !== action.payload.itemId)
      : [...state.favoriteTitles, { titleId: action.payload.itemId }],

  };

UPD. Please, check out a minimal working sandbox example


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...