I want to make a custom pagination using react-table. It needs to look quite a specific way:
Currently it looks like this, I guess this is the default for the current pagination I am using:
I need it to look like this:
My code is the following:
import React, { useEffect, useState } from 'react';
import { useTable, usePagination } from 'react-table';
import { TableProps } from '../../../types';
export const Table: React.FC<TableProps> = ({
columns,
data,
isLoading,
hasPagination = true,
pageNumber,
onPageChange,
maxPage,
onRowClick = () => undefined,
maxCount,
}) => {
const [canPreviousPage, setCanPreviousPage] = useState(false);
const [canNextPage, setCanNextPage] = useState(false);
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
pageCount,
nextPage,
previousPage,
} = useTable(
{
columns,
data,
manualPagination: true,
pageCount: maxPage,
},
usePagination,
);
useEffect(() => {
if (pageNumber <= 1) {
setCanPreviousPage(false);
} else {
setCanPreviousPage(true);
}
if (maxPage > pageNumber) {
setCanNextPage(true);
} else {
setCanNextPage(false);
}
}, [pageNumber, maxPage]);
const onNextPage = () => {
nextPage();
onPageChange(pageNumber + 1);
};
const onPreviousPage = () => {
previousPage();
onPageChange(pageNumber - 1);
};
const Pagination = () => {
if (!hasPagination) {
return null;
}
return (
<div className="pagination flex items-center text-black mt-3">
<Button onClick={onPreviousPage} disabled={!canPreviousPage}>
<Icon className={canPreviousPage ? 'color-black' : 'color-grey-200'}>chevron_left</Icon>
</Button>
<span>
Page{' '}
<strong>
{pageNumber} of {pageCount}
</strong>
</span>
<Button onClick={onNextPage} disabled={!canNextPage}>
<Icon className={canNextPage ? 'color-black' : 'color-grey-200'}>chevron_right</Icon>
</Button>
<div>{maxCount} records found</div>
<div className="mx-1">
<ActivityLoader isLoading={isLoading} />
</div>
</div>
);
};
return (
<div className="pt-10 pl-20 pr-16 font-nunito font-medium text-base">
<div className="align-middle inline-block w-full w-40 text-left">
<div className="shadow border-b border-gray-200">
<table {...getTableProps} className="w-full divide-y divide-gray-200">
<thead className="bg-mainBlue p-16">
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
<th
scope="col"
className="px-6 py-4 text-left font-medium text-gray-500 p-16 bg-mainBlue text-base"
{...column.getHeaderProps()}
>
{column.render('Header')}
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()} className="bg-white divide-y divide-gray-200">
{page.map((row) => {
prepareRow(row);
return (
<tr {...row.getRowProps()} onClick={() => onRowClick(_.get(row, 'original.id'))}>
{row.cells.map((cell) => {
return (
<td
className="px-6 py-4 whitespace-nowrap text-black"
{...cell.getCellProps()}
>
{cell.render('Cell')}
</td>
);
})}
</tr>
);
})}
</tbody>
</table>
</div>
</div>
<Pagination />
</div>
);
};
Can anyone help me to customize my pagination?? I have been reading some docs but I am struggling to implement a solution
First, create a new file called usePaginationPages.js
import { useCallback, useEffect, useMemo, useState } from "react";
export const usePaginationPages = ({ gotoPage, length, pageSize }) => {
const [currentPage, setCurrentPage] = useState(1);
const totalPages = useMemo(() => {
return Math.ceil(length / pageSize);
}, [length, pageSize]);
const canGo = useMemo(() => {
return {
next: currentPage < totalPages,
previous: currentPage - 1 > 0
};
}, [currentPage, totalPages]);
// currentPage siblings
const pages = useMemo(() => {
const start = Math.floor((currentPage - 1) / 5) * 5;
const end = start + 5 > totalPages ? totalPages : start + 5;
return Array.from({ length: end - start }, (_, i) => start + i + 1);
}, [currentPage, totalPages]);
// programatically call gotoPage when currentPage changes
useEffect(() => {
gotoPage(currentPage - 1);
}, [currentPage, gotoPage]);
// show first page when per page select options changes
useEffect(() => {
if (pageSize) {
goTo(1);
}
}, [pageSize]);
const goTo = (pg) => {
setCurrentPage(pg);
};
const goNext = useCallback(() => {
if (canGo.next) {
setCurrentPage((prev) => prev + 1);
}
}, [canGo]);
const goPrev = useCallback(() => {
if (canGo.previous) {
setCurrentPage((prev) => prev - 1);
}
}, [canGo]);
return {
canGo,
currentPage,
pages,
goTo,
goNext,
goPrev
};
};
Next, create Pagination.js by implementing our usePaginationPages hook
import { useState, useEffect, memo } from "react";
import { ChevronLeftIcon, ChevronRightIcon } from "#heroicons/react/24/solid";
import { usePaginationPages } from "./usePaginationPages";
function Pagination({ gotoPage, length, pageSize, setPageSize }) {
const [perPage, setPerPage] = useState(pageSize);
const {
canGo,
currentPage,
pages,
goTo,
goNext,
goPrev
} = usePaginationPages({
gotoPage,
length,
pageSize
});
// update pageSize when perPage changes
useEffect(() => {
// don't forget set to Number
setPageSize(Number(perPage));
}, [perPage, setPageSize]);
return (
<div className="m-4 flex items-center justify-center">
<button
onClick={goPrev}
disabled={!canGo.previous}
className="m-1 px-2 py-1 border rounded-md"
>
<ChevronLeftIcon className="h-6 w-4 text-blue-500" />
</button>
{pages.map((page, i) => (
<button
onClick={() => goTo(page)}
key={i}
style={{
background: currentPage === page ? "blue" : "none",
color: currentPage === page ? "white" : "black"
}}
className="m-1 px-3 py-1 border rounded-md"
>
{page}
</button>
))}
<button
onClick={goNext}
disabled={!canGo.next}
className="m-1 px-2 py-1 border rounded-md"
>
<ChevronRightIcon className="h-6 w-4 text-blue-500" />
</button>
<select
className="px-2 py-[6px] border rounded-md w-30 bg-white"
value={pageSize}
onChange={(e) => setPerPage(e.target.value)}
>
{[10, 50, 100].map((pageSize) => (
<option className="py-2" value={pageSize} key={pageSize}>
{pageSize} / page
</option>
))}
</select>
</div>
);
}
export default memo(Pagination);
You can see the full code here:
Javascript version:
Typescript version:
Related
I am getting a list of Products from an API call and I am rendering it through react-table.
const getItemsList=()=>{
getAuthorization()
.get("product/all_products/")
.then((res) => {
setProducts(res.data);
dispatch(setAPIDetailsItemsTable(res.data));
})
.catch((err) => {
console.log("Error in getting products", err);
});
}
useEffect(() => {
// Get all products
getItemsList();
}, []);
I am using redux for state Management
const data = APIDetailsItemsTable
const columns = React.useMemo(
() => [
{
Header: "Number",
accessor: "product_id",
},
{
Header: "Item Name",
accessor: "name",
},
{
Header: "Item Type",
accessor: "product_type",
},
{
Header: "Status",
accessor: "status",
Cell: StatusPill
}
],
[]
);
return (
< AddGuideItemstable
columns={columns}
data={data == undefined ? [] : data}
text="Undefined" />
)
/>
Here is the table component. I am working on Editing part. The checkbox can select only one item from the list.
function AddGuideItemstable({ columns, data, text }) {
const {
getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page,
visibleColumns,
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
state,
preGlobalFilteredRows,
setGlobalFilter,
selectedFlatRows,
state: { selectedRowIds }
} = useTable(
{
columns,
data,
initialState: {selectedRowIds},
stateReducer: (newState, action) => {
if (action.type === "toggleRowSelected") {
newState.selectedRowIds = {
[action.id]: true,
}
}
return newState;
},
},
useFilters, // useFilters!
useGlobalFilter,
useSortBy,
usePagination, // new
useRowSelect,
(hooks) => {
hooks.visibleColumns.push((columns) => {
return [
...columns,
{
Header: "Choose Items",
id: "selection",
Cell: ({ row }) => (
<div className="flex flex-col ml-6">
<CheckBox {...row.getToggleRowSelectedProps()}/>
</div>
),
//// dispatching selected item ID if changed
useEffect(() => {
let Id = selectedFlatRows.map(
d => d.original.id)
dispatch(setAddGuideItemID(Id))
console.log("selected row id", Id)
}, [selectedRowIds])
// Render the UI for your table
return (
<>
<table
{...getTableProps()}
className="min-w-full bg-transparent divide-y divide-gray-200"
>
<thead className=" border-b-8 border-white">
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{headerGroup.headers.map((column) => (
// Add the sorting props to control sorting. For this example
// we can add them into the header props
<th
scope="col"
className="group px-2 py-3 text-center text-sm font-medium text-gray-400 font-Roboto tracking-wider"
{...column.getHeaderProps(
column.getSortByToggleProps()
)}
>
<div className="flex items-center justify-between">
{column.render("Header")}
{/* Add a sort direction indicator */}
<span>
{column.isSorted ? (
column.isSortedDesc ? (
<SortDownIcon className="w-4 h-4 text-gray-100" />
) : (
<SortUpIcon className="w-4 h-4 text-gray-100" />
)
) : (
<SortIcon className="w-4 h-4 text-gray-100 opacity-0 group-hover:opacity-100" />
)}
</span>
</div>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()} className="bg-white">
{page.map((row, index) => {
// new
prepareRow(row);
return (
<tr
{...row.getRowProps()}
className={
index % 2 === 0
? "bg-cyan-100 border-b-8 border-white"
: "bg-white border-b-8 border-white"
}
>
{row.cells.map((cell) => {
return (
<td
{...cell.getCellProps()}
className="pr-1 pl-2 whitespace-nowrap"
role="cell"
>
{cell.column.Cell.name === "defaultRenderer" ? (
<div className="text-sm font-semibold text-black">
{cell.render("Cell")}
</div>
) : (
cell.render("Cell")
)}
</td>
);
})}
</tr>
);
})}
</tbody>
The checkbox component is as below
import react, { forwardRef, useEffect, useRef, useState } from "react";
export const CheckBox = forwardRef(({ indeterminate, ...rest }, ref) => {
const defaultRef = useRef();
const resolvedRef = ref || defaultRef;
useEffect(() => {
resolvedRef.current.indeterminate = indeterminate;
}, [resolvedRef, indeterminate]);
return (
<>
<div class="flex items-center">
<input
type="checkbox"
ref={resolvedRef}
{...rest}
id="A3-yes"
name="A3-confirmation"
class="opacity-0 absolute h-8 w-8"
/>
</>
);
});
I have an ID stored in a state
let ID = useSelector((state)=> state.guide.AddGuideResID)
What I want to do is if " ID " in the state is equal to the "ID in the products list" then mark the checkbox checked for that row.
I want to achieve like this. I want to mark only single item thats original ID matches the ID in my redux state. Checkbox checked manually
I am arslan Chaudhry. currently, I am working on an eCommerce site where I will store data in local storage because I don't have too much backend knowledge. how I can add functionality like delete and update on another folder.
my code is given below.
Books.js
import React from "react";
import "./components/book.css";
import Carousel from "react-multi-carousel";
import "react-multi-carousel/lib/styles.css";
import { FaShoppingCart } from "react-icons/fa";
import { useEffect } from "react";
import { useState, useRef } from "react";
import { useNavigate } from "react-router-dom";
import { createContext } from "react";
const Books = () => {
let arr=()=>{
let dat=localStorage.getItem("products")
if (dat) {
return JSON.parse(dat)
}
else{
return []
}
}
const [booksData, setbooksData] = useState([]);
const [productData, setproductData] = useState(arr());
let nav = useNavigate();
// slider
const responsive = {
superLargeDesktop: {
breakpoint: { max: 4000, min: 3000 },
items: 5,
},
desktop: {
breakpoint: { max: 3000, min: 1024 },
items: 3,
},
tablet: {
breakpoint: { max: 1024, min: 464 },
items: 2,
},
mobile: {
breakpoint: { max: 464, min: 0 },
items: 1,
},
};
let croser = useRef("");
let loding = useRef("");
const getJason = async () => {
try {
let fea = await fetch(
"https://script.google.com/macros/s/AKfycbxFCG7S-kjncQZwvcMnqq4wXoBAX8ecH1zkY2bLP7EE-YHlnKbiJ3RUuHtWLe6sIK30Kw/exec"
);
let acData = await fea.json();
let itemsData = acData.shop.filter((element) => {
if (element.name) {
return element;
}
});
setbooksData(itemsData);
if (itemsData) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
} catch (error) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
};
// get product data from api
useEffect(() => {
getJason();
}, []);
// go to cart button
const goto = () => {
nav("/Cart");
};
// local data
let addNow=(e)=>{
let data=productData.find((element)=>{return element.id === e.id });
let cart;
if (data) {
cart=productData.map((element)=>{
return element.id === e.id ? {...element, quantity:element.quantity+1}:element
})
}
else{
cart=[...productData,{...e, quantity:1}]
}
setproductData(cart);
};
useEffect(() => {
localStorage.setItem("products",JSON.stringify(productData))
}, [productData])
console.log(productData);
return (
<>
<div className="row " style={{ marginTop: "10px" }}>
<div className="col-lg-12 col-md-12 col-sm-12 col-xs-12">
<div className="section-headline text-center">
<h2>Books Shop</h2>
</div>
</div>
</div>
<div className="lodingBooks" ref={loding}>
<div class="spinner-border" role="status"></div>
<h4>Please wait....</h4>
</div>
<div ref={croser}>
<div className=" shadow go_to_cart" onClick={goto}>
<i class="bi bi-cart-check text-white"></i>
</div>
<Carousel responsive={responsive} className="container">
{booksData.map((element) => {
return (
<>
<div class="container page-wrapper">
<div class="page-inner">
<div class="row">
<div class="el-wrapper">
<div class="box-up">
<img class="img" src={element.images} alt="" />
<div class="img-info">
<div class="info-inner">
<span class="p-name text-info">
{element.name}
</span>
<span class="p-company ">Author:CHAUDHRY</span>
</div>
<div class="a-size ">
About:This is a complete book on javascript
<span class="size"></span>
</div>
</div>
</div>
<input
type="text"
value={1}
style={{ display: "none" }}
/>
<div class="box-down">
<div class="h-bg">
<div class="h-bg-inner"></div>
</div>
<a class="cart">
<span class="price">{element.price + "$"}</span>
<span
class="add-to-cart btn btn-sm"
style={{ backgroundColor: "#3EC1D5" }}
onClick={()=>{addNow(element)}}
>
<span class="txt">
ADD TO CART <FaShoppingCart />
</span>
</span>
</a>
</div>
</div>
</div>
</div>
</div>
</>
);
})}
</Carousel>
</div>
</>
);
};
export default Books;
and here is my cart file. where i want to perform the action like update and delete.
Cart.js
import React from "react";
import "./components/cart.css";
import { useEffect } from "react";
const Cart = () => {
let data = localStorage.getItem("products");
let javaData = JSON.parse(data);
let removeData = (e) => {
};
useEffect(() => {
localStorage.clear()
}, [])
return (
<>
<div class="container mt-5 mb-5">
<div class="d-flex justify-content-center row">
<div class="col-md-8">
<div class="p-2 shoingTitle">
<h4>Shop Now</h4>
<span class="text-danger">Remove all</span>
</div>
{javaData ? (
javaData.map((item) => {
return (
<>
<div class="d-flex flex-row justify-content-between align-items-center p-2 bg-white mt-4 px-3 rounded">
<div class="mr-1 imageandpara">
<img class="rounded" src={item.images} width="70" />
<span class="font-weight-bold">{item.name}</span>
</div>
<div class="d-flex flex-column align-items-center product-details">
<div class="d-flex flex-row product-desc"></div>
</div>
<div class="d-flex flex-row align-items-center qty">
<i class="minusSign shadow">
<i class="bi bi-dash"></i>
</i>
<span class="text-grey quantityNumber">
{item.quantity}
</span>
<i class="minusSign shadow">
<i class="bi bi-plus"></i>
</i>
</div>
<div>
<span class="text-grey productAmount">{`${
item.quantity * item.price
}$`}</span>
</div>
<div
class="d-flex align-items-center text-dark "
style={{
cursor: "pointer",
fontWeight: "900",
fontSize: "15px",
}}
onClick={() => {
removeData(item);
}}
>
<i class="bi bi-x text-danger"></i>
</div>
</div>
</>
);
})
) : (
<h3 style={{ textAlign: "center" }}>Cart is empety</h3>
)}
<div class="d-flex flex-row align-items-center mt-3 p-2 bg-white rounded">
<input
type="text"
class="form-control gift-card "
placeholder="discount code/gift card"
/>
<button
class="btn btn-sm ml-3 shadow"
type="button"
style={{
outline: "#3EC1D5",
backgroundColor: "#3EC1D5",
color: "white",
}}
>
Apply
</button>
</div>
<div class="totalItems">
Total Items: <strong>12</strong>
</div>
<span class="TotalPrice">
Total price: <strong>12$</strong>
</span>
<div class="d-flex flex-row align-items-center mt-3 p-2 bg-white rounded">
<button
class="btn btn-block btn-sm ml-2 pay-button shadow"
type="button"
style={{ backgroundColor: "#3EC1D5" }}
>
Proceed to Pay
</button>
</div>
</div>
</div>
</div>
</>
);
};
export default Cart;
Try this for add:
let removeData = (e) => {
localStorage.setItem("name of the item") // e.target.name
};
There's alot going on in your site :)
I think it will be responsible to create a context, that will serve the cart to all other components.
Things to note here, (Found some little improvements)
Run the function in the useState hook, don't just leave it there like you did
When using Array.filter you need to return a boolean iorder for it too filter your array properly.
This is the code I brought up hope it helps you out.
CartContext.js file.
import React, { createContext, useContext, useEffect, useState } from "react";
export const CartContext = createContext();
function Cart({ children }) {
const arr = useCallback(() => {
let dat = localStorage.getItem("products");
if (dat) {
return JSON.parse(dat);
} else {
return [];
}
}, []);
const [productData, setproductData] = useState(() => arr());
const getJason = async () => {
try {
let fea = await fetch(
"https://script.google.com/macros/s/AKfycbxFCG7S-kjncQZwvcMnqq4wXoBAX8ecH1zkY2bLP7EE-YHlnKbiJ3RUuHtWLe6sIK30Kw/exec"
);
let acData = await fea.json();
// filter callback function should return a boolean. That is either true or false in order to make it work.
// SO i think this function isn't going to function properly
let itemsData = acData.shop.filter((element) => {
if (element.name) {
return element;
}
});
setbooksData(itemsData);
if (itemsData) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
} catch (error) {
croser.current.style.filter = "blur(0px)";
loding.current.style.display = "none";
}
};
// get product data from api
useEffect(() => {
getJason();
}, []);
const addProduct = (e) => {
// check if product id available on cart
const findProduct = productData.find((element) => {
return element.id === e.id;
});
// add first quantity if not available
if (!findProduct)
return setproductData([...productData, { ...e, quantity: 1 }]);
// increase quantity by 1
const newCart = productData.map((element) => {
return {
...element,
quantity: element.id === e.id ? element.quantity + 1 : element.quantity,
};
});
setproductData(newCart);
};
const removeProduct = (e) => {
// check if product id available on cart
const findProductQuantity = productData.find((element) => {
return element.id === e.id && element.quantity >= 1;
});
// add first quantity if not available
if (!findProduct)
// Your ui should prevent this
return;
// decrease quantity by 1
const reducedQuantityCart = productData.map((element) => {
return {
...element,
quantity: element.id === e.id ? element.quantity - 1 : element.quantity,
};
});
// filter out all products with quantity less than 1 (quantity : 0)
const newCart = productData.filter((element) => {
return element.quantity >= 1;
});
setproductData(newCart);
};
const deleteProduct = (e) => {
// check if product id available on cart
const findProduct = productData.find((element) => {
return element.id === e.id;
});
// add first quantity if not available
if (!findProduct)
// Your ui should prevent this
return;
const productIndex = productData.findIndex((element) => {
return element.id === e.id;
});
// splice (delete) product from the productData array
const newCart = [productData].splice(productIndex, 1);
setproductData(newCart);
};
const value = {
productData,
addProduct,
removeProduct,
deleteProduct,
};
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
}
In here you create a context, create all your function you will use to update your cart, and pass them to your Context provider
// create a hook can you use anywhere in the app
export const useCart = () => {
const context = useContext(CartContext);
if (!context) {
throw new Error("Must use useCart in CartContext Child");
}
const { productData, addProduct, removeProduct, deleteProduct } = context;
return { productData, addProduct, removeProduct, deleteProduct };
};
For this you create a custom hook that you can use inside any component provided it is a child of the Cart context. So make sure you wrap it all around your app
// Use Case
const SomeComponent = () => {
const { productData, addProduct, removeProduct } = useCart();
return (
<div>
<p> Number of Products: {productData.length}</p>
<div>
{productData?.map((product) => (
<div>
<p>{product?.name}</p>
<button onClick={() => addProduct(product)}>add</button>
<button onClick={() => removeProduct(product)}>
subtract/reduce
</button>
<button onClick={() => deleteProduct(product)}>delete</button>
</div>
))}
</div>
</div>
);
};
Use case Scenario od how this code will work. Hope you find this helful
I try to load user history and show it inside a table.
Inside this table should be a button with "show more"
This button is a dropdown toggle button.
Everything working so far but when i click the button that is inside a map function all dropdowns open at the same time.
I know i've to use an index in the map function but I've no clue how i can use this index for my dropdown state.
import React, { useEffect, useState } from 'react';
import axios from 'axios';
import { useSelector } from 'react-redux';
function OrderHistory() {
const [history, setHistory] = useState([]);
const [dropdown, setDropdown] = useState(false);
const auth = useSelector((state) => state.auth);
const token = useSelector((state) => state.token);
const { user } = auth;
useEffect(() => {
const getHistory = async () => {
const res = await axios.get('/user/history', {
headers: { Authorization: token },
userid: user._id,
});
setHistory(res.data);
};
getHistory();
}, [token, setHistory]);
console.log(history);
const clickHandler = (index) => (event) => {
//event.preventDefault();
setDropdown(!dropdown);
console.log('clicked');
};
return (
<div className='history-page h-screen'>
<h2>History</h2>
<h4>You have {history.length} ordered</h4>
<table>
<thead>
<tr>
<th>Bestellnummer</th>
<th>Datum</th>
<th>Summe</th>
<th>Details</th>
</tr>
</thead>
<tbody>
{history.map((items, index) => (
<tr key={items._id}>
<td>
{items._id}
</td>
<td>{new Date(items.createdAt).toLocaleDateString()}</td>
<td>
{(
Math.round(
items.cartItems.reduce((x, item) => x + item.price, 0) * 100
) / 100
).toFixed(2)}
€
</td>
<td>
<button
class='bg-yellow-500 hover:bg-yellow-400 text-white font-bold py-2 px-4 rounded'
key={index}
onClick={clickHandler(index)}
>
Show more
</button>
<div className={dropdown ? '' : 'hidden'}>
<div>{items.firstName}</div>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
);
}
export default OrderHistory;
Initialize the dropdown state with null.
const [dropdown, setDropdown] = useState(null);
In clickHandler store, i just store the index.
const clickHandler = (index) => {
setDropdown((prev) => {
return prev === index ? null : index;
});
console.log('clicked', index);
};
Now in map function, check where you apply the condition to show the dropdown. I check if the dropdown is equal to that index, then that dropdown will visible otherwise add hidden class.
You can also use _id to show/hide dropdown instead of index
<td>
<button
class='bg-yellow-500 hover:bg-yellow-400 text-white font-bold py-2 px-4 rounded'
key={index}
onClick={() => clickHandler(index)}
>
Show more
</button>
<div
className={dropdown === index ? '' : 'hidden'}
// style={{ display: dropdown === index ? 'block' : 'none' }}
>
<div>{items.firstName}</div>
</div>
</td>
I am working on an ecommerce, where I am using material UI pagination component for implementing pagination. Here is new requirement arises. I need to add functionality in pagination: if user click on let's say respectively 3,7,11,13 if they click on browser back button they will go back to 11 then 7 then 3 and lastly 1. How do I do that?
I am using react, react router dom.
Here is pagination structure:
FYI, this is url and API structure:
URL and API structure
import React, { useEffect, useState } from "react";
import { useHistory, useParams } from "react-router-dom";
import ProductList from "../../components/common/ProductList/ProductList";
import {
GET_PRODUCTS_BY_BRAND,
GET_PRODUCTS_BY_CATEGORY,
GET_PRODUCTS_BY_SUBCATEGORY,
GET_PRODUCTS_BY_VENDOR,
} from "../../requests/HomePageApi";
import { Pagination } from "#material-ui/lab";
import "./ShopPage.scss";
const ShopPage = () => {
const { type, slug, subcategory } = useParams();
const [loading, setLoading] = useState(true);
// const [error, setError] = useState(false);
const [brands, setBrands] = useState([]);
const [colors, setColors] = useState([]);
const [sizes, setSizes] = useState([]);
const [products, setProducts] = useState(null);
const [filteredProducts, setFilteredProducts] = useState(null);
const [page, setPage] = React.useState(0);
const [count, setCount] = React.useState(1);
const [limit, setLimit] = React.useState(60);
const [total, setTotal] = React.useState(60);
const [sideFilter, setSideFilter] = useState(false);
const [vandor, setvandor] = useState({
vendorImg: "",
vendorName: "",
vendorSlug: "",
});
const [filter, setFilter] = useState({
// brands: "",
color: "",
size: "",
price: "",
});
const closeSideFilter = () => {
setSideFilter(false);
};
const getProducts = async (slug, qParams) => {
try {
let res;
if (type === "category") {
subcategory
? (res = await GET_PRODUCTS_BY_SUBCATEGORY(
slug,
subcategory,
qParams
))
: (res = await GET_PRODUCTS_BY_CATEGORY(slug, qParams));
}
if (type === "brand") res = await GET_PRODUCTS_BY_BRAND(slug, qParams);
if (type === "store") res = await GET_PRODUCTS_BY_VENDOR(slug, qParams);
if (res) setLoading(false);
if (res && res.products && res.products.length > 0) {
setProducts(res.products);
setFilteredProducts(res.products);
setTotal(res.total);
setCount(Math.ceil(res.total / limit));
if (type === "brand") {
setvandor({
vendorImg: `/assets/images/brand/${res.products[0].brand_logo}`,
vendorName: res.products[0].brand_name,
vendorSlug: res.products[0].brand_slug,
});
} else if (type === "store") {
setvandor({
vendorImg: `/assets/images/brand/${res.products[0].brand_logo}`,
vendorName: res.products[0].shop_name,
vendorSlug: res.products[0].vendorSlug,
});
}
if (res.colors) {
const uniqueColors = [...new Set(res.colors)];
setColors(uniqueColors);
}
if (res.sizes) {
const uniqueSizes = [...new Set(res.sizes)];
setSizes(uniqueSizes);
}
// if (res.brands) setBrands(res.brands);
}
} catch (error) {
console.log(error);
}
};
// console.log({ filteredProducts, filter, page, count, limit, total });
React.useMemo(() => {
let qParams = {
page: page,
limit: limit,
size: filter.size,
color: filter.color,
// brands: filter.brands,
price: filter.price.length ? `${filter.price[0]},${filter.price[1]}` : "",
};
if (slug) {
getProducts(slug, qParams);
}
}, [slug, page, limit, filter, count]);
React.useEffect(() => {
setPage(0);
}, [filter]);
const changeLimit = (limit) => {
setPage(0);
setLimit(limit);
};
const handleChange = (event, value) => {
// window.scrollTo({ top: 0, left: 0, behavior: "smooth" });
setPage(value - 1);
};
const slugTitle = (slug) => slug.split("-").join(" ");
return (
<FadeTransition>
{/* {loading && (
<div className="section-big-py-space ratio_asos py-5">
<div className="custom-container">
<Skeleton type="ShopPage" />
</div>
</div>
)} */}
{!loading && products === null && (
<div className="section-big-py-space ratio_asos py-5">
<div className="custom-container">
<h3 style={{ color: "#32375A", textAlign: "center" }}>
Sorry, No Product Found!
</h3>
</div>
</div>
)}
{products && (
<div className="title-slug-section">
<h2 class="title-slug">{slug && slugTitle(slug)}</h2>
</div>
)}
{products && (
<section className="section-big-py-space ratio_asos">
{/* {type !== "category" && (
<div className="merchant-page-header">
<div className="custom-container">
<div
className="shadow-sm bg-white rounded p-3 mb-5 d-flex align-items-center w-100"
style={{ minHeight: "132px" }}
>
<div className="row align-items-center w-100">
<div className="col-lg-6">
<div className="row align-items-center">
{vandor && vandor.vendorImg && (
<div className="col-auto">
<Image
src={vandor.vendorImg}
alt={vandor.vendorName}
className="img-fluid merchant-img"
/>
</div>
)}
<div className="col-auto mt-lg-0 mt-2">
<h3 className="mb-0"> {vandor.vendorName} </h3>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
)} */}
<div className="collection-wrapper">
<div className="custom-container">
<div className="row">
<div className="col-sm-3 collection-filter category-page-side">
{/* <SidebarFilter
type={type}
brands={brands}
colors={colors}
sizes={sizes}
onChange={(data) => setFilter(data)}
/> */}
<InnerCategory />
{products && (
<RowSlider title="New Products" products={products} />
)}
</div>
<div className="collection-content col-lg-9">
<div className="page-main-content">
<div className="collection-product-wrapper">
<div className="row">
<div className="col-xl-12">
{/* <Button
variant='contained'
className='bg-dark text-light d-lg-none mb-3 mt-2 w-100'
onClick={() => setSideFilter(true)}
>
<span className='filter-btn '>
<i
className='fa fa-filter'
aria-hidden='true'
></i>
Filter
</span>
</Button> */}
</div>
</div>
<MainFilter
type={type}
// brands={brands}
colors={colors}
sizes={sizes}
page={page}
limit={limit}
onCountChange={(c) => changeLimit(c)}
onChange={(data) => setFilter(data)}
/>
{/* <TopFilter
onCountChange={(x) => changeLimit(x)}
total={total}
page={page}
limit={limit}
setSideFilter={setSideFilter}
/> */}
{filteredProducts && (
<ProductList products={filteredProducts} />
)}
{count > 1 && (
<div className="d-flex justify-content-center mt-4">
<Pagination
count={count}
page={page + 1}
onChange={handleChange}
shape="rounded"
/>
</div>
)}
</div>
</div>
</div>
</div>
</div>
</div>
</section>
)}
{!loading && products?.length === 0 && (
<div className="merchant-page-header">
<div className="custom-container pt-5">
<div
className="shadow-sm bg-white rounded p-3 mb-5 d-flex align-items-center justify-content-center w-100"
style={{ minHeight: "132px" }}
>
<h3 className="mb-0">No Products found!</h3>
</div>
</div>
</div>
)}
<Drawer
open={sideFilter}
className="add-to-cart"
onClose={() => setSideFilter(false)}
transitionDuration={400}
style={{ paddingLeft: "15px" }}
>
<SidebarFilter
onClose={closeSideFilter}
type={type}
// brands={brands}
colors={colors}
sizes={sizes}
onChange={(data) => setFilter(data)}
/>
</Drawer>
</FadeTransition>
);
};
export default ShopPage;
Usually you would like to have URL to represent selected page, so you could refresh the page and still be on the same page. Or share the exact page via copy-paste of URL. Especially on e-commerce sites. So I would recommend to sync selected page with URL.
While it's so common scenario, i have couple hooks for that. First of all - URL hook, which should work with your reactjs app setup.
https://www.npmjs.com/package/hook-use-url
and then couple more hooks to not worry about pagination details inside component:
usePage.js:
import useUrl from "hook-use-url";
export default function usePage() {
const url = useUrl();
const page = url.get({ variable: "page" })
? parseInt(url.get({ variable: "page" }), 10)
: 1;
const setPage = (value) => {
url.multipleActions({
setPairs: [{ variable: "page", value: value }],
});
};
return [page, setPage];
}
and usePerPage.js:
import useUrl from "hook-use-url";
export default function usePerPage() {
const url = useUrl();
const perPage = url.get({ variable: "per-page" })
? parseInt(url.get({ variable: "per-page" }), 10)
: 25;
const setPerPage = (value) => {
url.multipleActions({
setPairs: [
{ variable: "page", value: 1 },
{ variable: "per-page", value },
],
});
};
return [perPage, setPerPage];
}
Inside components you can use these like so:
(Take a note that which page is 1st depends on your backend API, in my case 1st page is always 1 and not 0, but mui.com component starts from 0 that's why there is -1 and +1).
function MyComp(){
const [page, setPage] = usePage();
const [perPage, setPerPage] = usePerPage();
// ....
return (
<TablePagination
count={totalRows}
page={page - 1}
onPageChange={(e, newPage) => {
setPage(newPage + 1);
}}
rowsPerPage={perPage}
onRowsPerPageChange={(e) => {
setPerPage(e.target.value);
}}
/>
)
}
I am new to REACT and having trouble getting a table component to work correctly.
I am using the react-tables package and the tutorial example here but I am doing everything in a table.js component file and adding it to my App.js. Basically, I am trying to create a table with pagination, sorting, and selectable rows with checkboxes. My issue is that I am only getting the checkboxes to populate in the header (see below).
Here is my code; what am I doing incorrectly here? Note - my rows are being created using page, not row and are coming from my API. Any suggestions on how to fix this?
import React, { useMemo, useEffect, useState } from 'react'
import reactTable, {useTable, useRowSelect, usePagination, useSortBy} from 'react-table'
import axios from "axios";
import { COLUMNS } from './columns'
import './Table.css'
export const Table = () => {
const [loadingData, setLoadingData] = useState(true);
const columns = useMemo(() => COLUMNS, []);
const [data, setData] = useState([]);
const IndeterminateCheckbox = React.forwardRef(
//This is the function for the checkboxes in page select
({ indeterminate, ...rest }, ref) => {
const defaultRef = React.useRef()
const resolvedRef = ref || defaultRef
React.useEffect(() => {
resolvedRef.current.indeterminate = indeterminate
}, [resolvedRef, indeterminate])
return (
<>
<input type="checkbox" ref={resolvedRef} {...rest} />
</>
)
}
)
useEffect(() => {
async function getData() {
await axios
.get("http://localhost:5000/my/api")
.then((response) => {
// check if the data is populated
console.log(response.data);
setData(response.data);
// you tell it that you had the result
setLoadingData(false);
});
}
if (loadingData) {
// if the result is not ready so you make the axios call
getData();
}
}, []);
const tableInstance = useTable({
columns,
data,
initialState: { pageIndex: 2 }
}, useSortBy,
usePagination,
useRowSelect,
hooks => {
hooks.visibleColumns.push(columns => [
// Let's make a column for selection
{
id: 'selection',
// The header can use the table's getToggleAllRowsSelectedProps method
// to render a checkbox
Header: ({ getToggleAllPageRowsSelectedProps }) => (
<div>
<IndeterminateCheckbox {...getToggleAllPageRowsSelectedProps()} />
</div>
),
// The cell can use the individual row's getToggleRowSelectedProps method
// to the render a checkbox
Cell: ({ row }) => (
<div>
<IndeterminateCheckbox {...row.getToggleRowSelectedProps()} />
</div>
),
},
...columns,
])
}
);
const { getTableProps,
getTableBodyProps,
headerGroups,
prepareRow,
page, //Begins the pagination and select stuff
canPreviousPage,
canNextPage,
pageOptions,
pageCount,
gotoPage,
nextPage,
previousPage,
setPageSize,
selectedFlatRows,
state: { pageIndex, pageSize, selectedRowIds }} = tableInstance
return (
<div className="container">
{loadingData ? (
<p>Loading Please wait...</p>
) : (
<table {...getTableProps()}>
<thead>
{headerGroups.map((headerGroup) => (
<tr {...headerGroup.getHeaderGroupProps()}>
{
headerGroup.headers.map((column) =>(
<th {...column.getHeaderProps(column.getSortByToggleProps())}>
{column.render('Header')}
<span>
{column.isSorted
? column.isSortedDesc
? ' 🔽'
: ' 🔼'
: ''}
</span>
</th>
))}
</tr>
))}
</thead>
<tbody {...getTableBodyProps()}>
{
page.map((row, i) => {
prepareRow(row);
return(
<tr {...row.getRowProps()}>
{
row.cells.map(cell => {
console.log(cell.render('Cell').props.value);
//<td {...cell.getCellProps()}>{cell.render('Cell').props.value}</td>
return <td>{cell.render('Cell').props.value}</td>
})
}
</tr>
)
})
}
</tbody>
</table>
)}
<div className="pagination">
<button onClick={() => gotoPage(0)} disabled={!canPreviousPage}>
{'<<'}
</button>{' '}
<button onClick={() => previousPage()} disabled={!canPreviousPage}>
{'<'}
</button>{' '}
<button onClick={() => nextPage()} disabled={!canNextPage}>
{'>'}
</button>{' '}
<button onClick={() => gotoPage(pageCount - 1)} disabled={!canNextPage}>
{'>>'}
</button>{' '}
<span>
Page{' '}
<strong>
{pageIndex + 1} of {pageOptions.length}
</strong>{' '}
</span>
<span>
| Go to page:{' '}
<input
type="number"
defaultValue={pageIndex + 1}
onChange={e => {
const page = e.target.value ? Number(e.target.value) - 1 : 0
gotoPage(page)
}}
style={{ width: '100px' }}
/>
</span>{' '}
</div>
</div>
)
}
Return <td {...cell.getCellProps()}>{cell.render('Cell')}</td> from tr.
This should work:
<tbody {...getTableBodyProps()}>
{page.map((row, i) => {
prepareRow(row);
return (
<tr {...row.getRowProps()}>
{row.cells.map((cell) => {
return (
<td {...cell.getCellProps()}>{cell.render("Cell")}</td> // fix return statement
);
})}
</tr>
);
})}
</tbody>