How to fetch data partially in react, redux?

Actions

import { FETCH_BLOG, FETCH_BLOG_ERROR, FETCH_BLOG_LOADING } from "../constants/blogActionTypes"
    
    const initialState = {
        blogs: [],
    error: '',
    loading: false,
    allBlogs: []
}
// eslint-disable-next-line import/no-anonymous-default-export
export default (blogs = initialState, action) => {
    switch (action.type) {
        case FETCH_BLOG_LOADING:
            return {
                blogs: [...blogs.blogs],
                loading: true,
                error: ''
            };
        case FETCH_BLOG_ERROR:
            return {
                blogs: [...blogs.blogs],
                loading: false,
                error: action.payload
            };
        case FETCH_BLOG:
            return {
                blogs: [...action.payload, ...blogs.blogs],
                loading: false,
                error: ''
            };
        default: return blogs;
    }
}

Reducers

export const fetchBlogs = (data) => async (dispatch) =>{

    dispatch({ type: FETCH_BLOG_LOADING, payload: true })
    fetch('http://localhost:5000/blog?show=' + data, {
        method: 'GET',
        headers: {
            authorization: userData.token
        }
    })
        .then(res => res.json())
        .then(data => {
            if (data.message) {
                dispatch(fetchBlogsError(data.message))
            } else {
                dispatch({ type: FETCH_BLOG, payload: data })
            }
        })
}

React

const [fetchData, setFetchData] = useState(0);
    const showData = () => {
        setFetchData(fetchData + 10)
    }

    const dispatch = useDispatch();

    const { loading, error, blogs, } = useSelector(state => state.blogs)
  
    const getData = useCallback(  () => {
        dispatch(fetchBlogs(fetchData))
    }, [fetchData])

    useEffect(() => {
        getData()
    }, [getData])

On the first render, I fetch 10 items.after clicking on load more I fetch another 10 data from database. On the blog component it’s fine but after go back to the home page and get back to the blog page; the blog items duplicates. How to fix this duplicate issue>

Answer

There are two issues here which are inter-related, you possibly don’t need to address #2 depending on how you address #1.

  1. You should add a condition to your thunk action so that you don’t fetch a page that you have previously fetched.
  2. You should separate your blog items by page so that you aren’t always appending the newest items at the end of the array if you fetch page 1 twice.

Sidenote: [...blogs.blogs] is unnecessary because there is reason to clone properties which you aren’t changing.

I’m confused by your API calls. It looks like /blog?show=20 is getting posts 21-30 but I would think based on the name show that it would be posts 1-20.

Using position indexes:

import { createAsyncThunk, createReducer } from "@reduxjs/toolkit";

export const fetchBlogs = createAsyncThunk(
  "blogs/fetchBlog",
  async (startIndex, { getState, rejectWithValue }) => {
    const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
      method: "GET",
      headers: {
        // where does userData come from ??
        authorization: userData.token
      }
    });
    const data = await res.json();
    if (data.message) {
      rejectWithValue(data.message);
    } else {
      return data;
    }
  },
  {
    condition: (startIndex, { getState }) => {
      const { blogs } = getState();
      // cancel if loading of if first post on paage is loaded
      if (blogs.loading || blogs.blogs[startIndex]) {
        return false;
      }
    }
  }
);

const initialState = {
  blogs: [],
  error: "",
  loading: false
};

export default createReducer(initialState, (builder) =>
  builder
    .addCase(fetchBlogs.pending, (state) => {
      state.loading = true;
      state.error = "";
    })
    .addCase(fetchBlogs.rejected, (state, action) => {
      state.loading = false;
      state.error = action.payload ?? action.error;
    })
    .addCase(fetchBlogs.fulfilled, (state, action) => {
      const startIndex = action.meta.arg;
      const newBlogs = action.payload;
      // insert in the array at the correct position
      state.blogs.splice(startIndex, newBlogs.length, newBlogs);
    })
);

Using separated pages:

import { createAsyncThunk, createReducer, createSelector } from "@reduxjs/toolkit";

export const fetchBlogs = createAsyncThunk(
  "blogs/fetchBlog",
  async (pageNumber, { getState, rejectWithValue }) => {
      const startIndex = 10 * (pageNumber - 1);
    const res = await fetch("http://localhost:5000/blog?show=" + startIndex, {
      method: "GET",
      headers: {
        // where does userData come from ??
        authorization: userData.token
      }
    });
    const data = await res.json();
    if (data.message) {
      rejectWithValue(data.message);
    } else {
      return data;
    }
  },
  {
    condition: (pageNumber, { getState }) => {
      const { blogs } = getState();
      // cancel if loading of if there is a property for this page
      if (blogs.loading || blogs.blogs[pageNumber]) {
        return false;
      }
    }
  }
);

const initialState = {
  //arrays keyed by page number
  blogs: {},
  error: "",
  loading: false
};

export default createReducer(initialState, (builder) =>
  builder
    .addCase(fetchBlogs.pending, (state) => {
      state.loading = true;
      state.error = "";
    })
    .addCase(fetchBlogs.rejected, (state, action) => {
      state.loading = false;
      state.error = action.payload ?? action.error;
    })
    .addCase(fetchBlogs.fulfilled, (state, action) => {
      const pageNumber = action.meta.arg;
      state.blogs[pageNumber] = action.payload;
    })
);

// want to flatten the blogs array when selecting
// create a memoized selector
export const selectBlogs = createSelector(
    state => state.blogs,
    (blogsState) => ({
        ...blogsState,
        blogs: Object.values(blogsState.blogs).flat(1)
    })
)

With component:

export default () => {
  const [pageNumber, setPageNumber] = useState(1);

  const showNext = () => {
    setPageNumber((page) => page + 1);
  };

  const dispatch = useDispatch();

  const { loading, error, blogs } = useSelector(selectBlogs);

  useEffect(() => {
    dispatch(fetchBlogs(pageNumber));
  }, [dispatch, pageNumber]);