Cursor pagination con Redux Toolkit

La idea es manejar:

  • items → los resultados de la página actual.
  • nextCursor → el cursor para pedir la siguiente página.
  • previousCursor → el cursor para volver atrás.
  • sort → el orden aplicado a la búsqueda.
  • loading / error → estados de red.

1. Slice de Redux

// store/paginationSlice.js
import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";

// thunk para traer resultados de la API usando cursor
export const fetchPage = createAsyncThunk(
  "pagination/fetchPage",
  async ({ cursor, sort }, thunkAPI) => {
    // Ejemplo de API con cursor y sort
    const response = await fetch(
      `/api/items?cursor=${cursor || ""}&sort=${sort}`
    );
    const data = await response.json();
    return data; 
    // Supongamos que data = { items: [...], nextCursor: "abc", previousCursor: "xyz" }
  }
);

const paginationSlice = createSlice({
  name: "pagination",
  initialState: {
    items: [],
    nextCursor: null,
    previousCursor: null,
    sort: "docdate:asc",
    loading: false,
    error: null,
  },
  reducers: {
    changeSorting: (state, action) => {
      state.sort = action.payload;
    },
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchPage.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchPage.fulfilled, (state, action) => {
        state.loading = false;
        state.items = action.payload.items;
        state.nextCursor = action.payload.nextCursor;
        state.previousCursor = action.payload.previousCursor;
      })
      .addCase(fetchPage.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export const { changeSorting } = paginationSlice.actions;
export default paginationSlice.reducer;

2. Configurar el Store

// store/index.js
import { configureStore } from "@reduxjs/toolkit";
import paginationReducer from "./paginationSlice";

export const store = configureStore({
  reducer: {
    pagination: paginationReducer,
  },
});

3. Proveedor en la App

// main.jsx
import React from "react";
import ReactDOM from "react-dom/client";
import { Provider } from "react-redux";
import { store } from "./store";
import App from "./App";

ReactDOM.createRoot(document.getElementById("root")).render(
  <Provider store={store}>
    <App />
  </Provider>
);

4. Componente con Paginación y Ordenamiento

// App.jsx
import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import { fetchPage, changeSorting } from "./store/paginationSlice";

export default function App() {
  const { items, nextCursor, previousCursor, sort, loading, error } = useSelector(
    (state) => state.pagination
  );
  const dispatch = useDispatch();

  // Primera carga
  useEffect(() => {
    dispatch(fetchPage({ cursor: null, sort }));
  }, [dispatch, sort]);

  const handleNext = () => {
    if (nextCursor) dispatch(fetchPage({ cursor: nextCursor, sort }));
  };

  const handlePrevious = () => {
    if (previousCursor) dispatch(fetchPage({ cursor: previousCursor, sort }));
  };

  const handleSortChange = (e) => {
    dispatch(changeSorting(e.target.value));
    dispatch(fetchPage({ cursor: null, sort: e.target.value }));
  };

  return (
    <div>
      <h1>Resultados</h1>

      {loading && <p>Cargando...</p>}
      {error && <p>Error: {error}</p>}

      <select value={sort} onChange={handleSortChange}>
        <option value="docdate:asc">Fecha ↑</option>
        <option value="docdate:desc">Fecha ↓</option>
        <option value="slug:asc">Slug ↑</option>
        <option value="slug:desc">Slug ↓</option>
        <option value="score:asc">Score ↑</option>
        <option value="score:desc">Score ↓</option>
      </select>

      <ul>
        {items.map((item) => (
          <li key={item.id}>{item.title}{item.date}</li>
        ))}
      </ul>

      <div>
        <button disabled={!previousCursor} onClick={handlePrevious}>
          ← Anterior
        </button>
        <button disabled={!nextCursor} onClick={handleNext}>
          Siguiente →
        </button>
      </div>
    </div>
  );
}

5. Flujo completo (en pseudocódigo humano 🧠)

  1. Usuario abre la página → useEffect dispara fetchPage({cursor: null, sort}).
  2. Redux hace pending → fulfilled → llena items, nextCursor, previousCursor.
  3. Usuario da click en Siguiente → manda fetchPage({cursor: nextCursor, sort}).
  4. API responde con la siguiente página → Redux actualiza items y cursores.
  5. Usuario cambia orden → changeSorting guarda nuevo sort y vuelve a pedir página inicial.

Similar Posts