Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Infinite Scroll doesn't keep previous items in React/Redux

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?

like image 424
Nima Zarei Avatar asked Aug 14 '26 13:08

Nima Zarei


1 Answers

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)

like image 113
Anarno Avatar answered Aug 17 '26 01:08

Anarno



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!