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 🧠)
- Usuario abre la página →
useEffect
disparafetchPage({cursor: null, sort})
. - Redux hace
pending → fulfilled
→ llenaitems
,nextCursor
,previousCursor
. - Usuario da click en Siguiente → manda
fetchPage({cursor: nextCursor, sort})
. - API responde con la siguiente página → Redux actualiza
items
y cursores. - Usuario cambia orden →
changeSorting
guarda nuevo sort y vuelve a pedir página inicial.