I created a pagination component inside my app:
const Paginate = ({ pages, page, keywordInput = '' }) => {
return pages > 1 && (
<>
<nav aria-label="Page navigation example">
<ul className="pagination">
{[...Array(pages).keys()].map(x => (
<li className={`page-item ${x+1 === page ? 'active' : ''} `} key={x + 1}>
<Link to={keywordInput ? `/search/${keywordInput}/page/${x+1}` : `/page/${x+1}`} className="page-link">{x+1}</Link>
</li>
)) }
</ul>
</nav>
</>
)
}
On my homepage that has the search bar this will work as it will go to the search result page and will display 6 items per page. So I tried to customize this on my controller so I can use this on my admin backend and can add the pagination on each listing backend pages.
// @description Fetch all resorts
// @route GET /api/resorts
// @access Public
const getResorts = expressAsyncHandler(async (req, res) => {
const pageSize = 6
const page = Number(req.query.pageNumber) || 1
const keyword = req.query.keyword ? {
name: {
$regex: req.query.keyword,
$options: 'i'
}
} : {}
const count = await Resort.countDocuments({ ...keyword } )
const resorts = await Resort.find({ ...keyword }).limit(pageSize).skip(pageSize * (page - 1))
res.json({ resorts, page, pages: Math.ceil(count / pageSize) })
})
// @description Fetch all resorts created by the resort owner
// @route GET /api/resorts
// @access Public
const getOwnerResorts = expressAsyncHandler(async (req, res) => {
const userId = req.params.userid
const pageSize = 6
const page = Number(req.query.pageNumber) || 1
const count = await Resort.countDocuments({ user: userId } )
const resorts = await Resort.find({ user: userId }).limit(pageSize).skip(pageSize * (page - 1))
res.json({ resorts, page, pages: Math.ceil(count / pageSize) })
})
Now on my react frontend I created a route on my App.js that will point page to page without the keyword on my backend:
<Route path='/admin/resortslist' component={ResortListAdminScreen} exact />
<Route path='/admin/resortslist/page/:pageNumber' component={ResortListAdminScreen} exact />
And then on my admin access - ResortListAdminScreen.js
, I tried to call the paginate component above:
const ResortListAdminScreen = ({ history }) => {
const dispatch = useDispatch()
const resortList = useSelector(state => state.resortList)
const { loading, error, resorts, page, pages } = resortList
const resortDelete = useSelector(state => state.resortDelete)
const { loading:loadingDelete, error:errorDelete, success:successDelete } = resortDelete
const userLogin = useSelector(state => state.userLogin)
const { userInfo } = userLogin
const resortCreate = useSelector(state => state.resortCreate)
const {
loading:loadingCreate,
error:errorCreate,
success:successCreate,
resort:createdResort
} = resortCreate
useEffect(() => {
if(userInfo.role !== 'administrator'){
history.push('/')
}
dispatch(listResorts())
}, [dispatch, history, userInfo, successDelete, successCreate, createdResort])
const deleteHandler = (id) => {
if(window.confirm('Are you sure')){
dispatch(deleteResort(id))
}
}
return (
<>
<h1>Resorts</h1>
{ loadingDelete && <Loader />}
{ errorDelete && <Message variant='danger'>{errorDelete}</Message>}
{ loadingCreate && <Loader />}
{ errorCreate && <Message variant='danger'>{errorCreate}</Message>}
{ loading ? <Loader /> : error ? <Message variant='danger'>{error}</Message> : (
<>
<Link to='/admin/resorts/create'>Create Resort</Link>
<table className="table">
<thead className="thead-dark">
<tr>
<th scope="col">Id</th>
<th scope="col">Name</th>
<th scope="col">Price Per Night</th>
<th scope="col">Description</th>
<th scope="col">Address</th>
<th scope="col">Phone</th>
<th scope="col">Website</th>
<th scope="col">Amenities</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{resorts.map(resort => (
<tr key={resort._id}>
<td>{resort._id}</td>
<td>{resort.name}</td>
<td>{resort.price_per_night}</td>
<td>{resort.description}</td>
<td>{`${resort.address}, ${resort.city}, ${resort.province}, Philippines ${resort.zip_code}`}</td>
<td>{resort.phone}</td>
<td>{resort.website}</td>
<td>{
Object.entries(resort.amenities).map(
([key, value], index, arr) => {
return (value && index === arr.length-1) ? <span>{key}</span> : value ? <span>{`${key}, `}</span> : null;
})
}</td>
<td>
<Link to={`/admin/resort/${resort._id}/edit`}>
<button classNameName="btn btn-sm">
EDIT
</button>
</Link>
<button classNameName="btn btn-sm" onClick={() => deleteHandler(resort._id)}>
DELETE
</button>
</td>
</tr>
))}
</tbody>
</table>
</>
)}
<Paginate pages={pages} page={page} />
</>
)
}
export default ResortListAdminScreen
Notice that I added the <Paginate pages={pages} page={page} />
at the bottom. When I click on each page number it returns http://localhost:3000/page/1
and the screen is blank. I also tried visiting http://localhost:3000/admin/resortsList/page/1
but it doesn't show the next page list instead it is still stuck on the first page (which is the first 6 items).
Not sure how to fix this, but I would want to customize the paginate component and be able to reuse it on my component. Any idea how to attain this so I can add this on my backend listing? Should I duplicate the Paginate component or it can be customize?
Note: On the admin access backend we don't need a search but only a normal pagination
question from:
https://stackoverflow.com/questions/65516648/creating-a-reusable-pagination-in-react-express-mongodb