3

I'm trying to load the second set of items from the reddit API with Infinite scrolling when the user scrolls to the bottom of the page and although they do load successfully, the previous items are overridden by the new ones.

You can see this happening here: https://reddix.netlify.app/

This is the Redux Slice with the Thunks:

// Gets the first 10 Posts from the API
export const getPosts = createAsyncThunk(
  "post/getPosts",
  async (apiAddress) => {
    const response = await fetch(apiAddress);
    if (!response.ok) throw new Error("Request Failed!");
    const data = await response.json();
    return data;
  }
);

// Loads the Next 10 Posts
export const getMorePosts = createAsyncThunk(
  "post/getMorePosts",
  async (apiAddress) => {
    const response = await fetch(apiAddress);
    if (!response.ok) throw new Error("Request Failed!");
    const data = await response.json();
    return data;
  }
);

const redditPostSlice = createSlice({
  name: "post",
  initialState: {
    redditPost: {},
    isLoading: false,
    hasError: false,
    moreIsLoading: false,
    moreHasError: false,
  },
  extraReducers: (builder) => {
    builder
      .addCase(getPosts.pending, (state) => {
        state.isLoading = true;
        state.hasError = false;
      })
      .addCase(getPosts.fulfilled, (state, action) => {
        state.redditPost = action.payload.data;
        state.isLoading = false;
        state.hasError = false;
      })
      .addCase(getPosts.rejected, (state) => {
        state.isLoading = false;
        state.hasError = true;
      })
      .addCase(getMorePosts.pending, (state) => {
        state.moreIsLoading = true;
        state.moreHasError = false;
      })
      .addCase(getMorePosts.fulfilled, (state, action) => {
        state.redditPost = action.payload.data;
        state.moreIsLoading = false;
        state.moreHasError = false;
      })
      .addCase(getMorePosts.rejected, (state) => {
        state.moreIsLoading = false;
        state.moreHasError = true;
      });
  },
});

And in this Search components I have the functionality for loading the pages:

const Search = () => {
  const [input, setInput] = useState("");
  const [isFetching, setIsFetching] = useState(false);
  const redditPost = useSelector(selectRedditPost);
  const dispatch = useDispatch();

  // Get the Last Post
  const lastPost = () => {
    if (redditPost.children) {
      const [lastItem] = redditPost.children.slice(-1);

      const lastKind = lastItem.kind;
      const lastId = lastItem.data.id;

      return `${lastKind}_${lastId}`;
    } else {
      return;
    }
  };

  // API Endpoints
  const hotApiAddress = `https://www.reddit.com/r/${input}/hot.json?limit=10`;
  const newApiAddress = `https://www.reddit.com/r/${input}/new.json?limit=10`;
  const moreApiAddress = `https://www.reddit.com/r/${input}/new.json?limit=10&after=${lastPost()}`;

  // Get Hot Posts
  const handleHot = (e) => {
    e.preventDefault();
    if (!input) return;

    dispatch(getPosts(hotApiAddress));
  };

  // Get New Posts
  const handleNew = (e) => {
    e.preventDefault();
    if (!input) return;

    dispatch(getPosts(newApiAddress));
  };

  // Fire Upon Reaching the Bottom of the Page
  const handleScroll = () => {
    if (
      window.innerHeight + document.documentElement.scrollTop !==
      document.documentElement.offsetHeight
    )
      return;

    setIsFetching(true);
  };

  // Debounce the Scroll Event Function and Cancel it When Called
  const debounceHandleScroll = debounce(handleScroll, 100);

  useEffect(() => {
    window.addEventListener("scroll", debounceHandleScroll);
    return () => window.removeEventListener("scroll", debounceHandleScroll);
  }, [debounceHandleScroll]);

  debounceHandleScroll.cancel();

  // Get More Posts
  const loadMoreItems = useCallback(() => {
    dispatch(getMorePosts(moreApiAddress));
    setIsFetching(false);
  }, [dispatch, moreApiAddress]);

  useEffect(() => {
    if (!isFetching) return;
    loadMoreItems();
  }, [isFetching, loadMoreItems]);

Is there any way to keep the previous items when the next set loads?

Nima
  • 996
  • 2
  • 9
  • 37
  • The answer to this question can be found here: https://stackoverflow.com/questions/70367945/append-previous-state-to-new-state-in-redux – Nima Dec 16 '21 at 08:14

1 Answers1

1

Because you set on every dispatch a different payload value, your previous array disappears. Take a look at the entityAdapter. Whit this adapter you can easily manage arrays, you can add, modify, update or remove items from the array. This is can be a solution for you. Keep in a list the previous value, and when a next action is dispatched, append the existing list.

Note: you need the upsertMany method on the entityAdapter to keep the previous values.

Other solutions without entityAdapter: You have to store the array in the state somehow because when another payload appears you have to access this array for example state.redditPosts = [...state.redditPosts, ...payload.array]. Or because you use redux js toolkit, you can mutate the state, state.redditPosts.push(...payload.array)

Anarno
  • 1,470
  • 9
  • 18
  • I'll check it. Thanks! The problem is that I'm not getting an array in my payload. It's actually an object with only one value being the array of children which I'm accessing elsewhere. – Nima Dec 15 '21 at 13:46
  • I tried to understand the entityAdapter but couldn't get it to work at all. Is there any other way to do this? – Nima Dec 15 '21 at 17:19
  • 1
    I made an update on the answer. – Anarno Dec 16 '21 at 07:54
  • Thanks for the update. Unfortunately, it didn't help me. I have already found an answer from another question I made and will include the link in the comment section of the question. – Nima Dec 16 '21 at 08:13
  • 1
    Yes, this is what I'm trying to say :) – Anarno Dec 16 '21 at 08:20