How to implement pagination in React - reactjs

I am new to ReactJS and am creating a simple TODO application in it. Actually, it is a very basic app with no DB connection, tasks are stored in an array. I added Edit and Delete functionality to it now I want to add pagination.
How do I implement it? Any help will be appreciated. Thank you...!!

I've implemented pagination in pure React JS recently. Here is a working demo: http://codepen.io/PiotrBerebecki/pen/pEYPbY
You would of course have to adjust the logic and the way page numbers are displayed so that it meets your requirements.
Full code:
class TodoApp extends React.Component {
constructor() {
super();
this.state = {
todos: ['a','b','c','d','e','f','g','h','i','j','k'],
currentPage: 1,
todosPerPage: 3
};
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({
currentPage: Number(event.target.id)
});
}
render() {
const { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.slice(indexOfFirstTodo, indexOfLastTodo);
const renderTodos = currentTodos.map((todo, index) => {
return <li key={index}>{todo}</li>;
});
// Logic for displaying page numbers
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(todos.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
return (
<li
key={number}
id={number}
onClick={this.handleClick}
>
{number}
</li>
);
});
return (
<div>
<ul>
{renderTodos}
</ul>
<ul id="page-numbers">
{renderPageNumbers}
</ul>
</div>
);
}
}
ReactDOM.render(
<TodoApp />,
document.getElementById('app')
);

I have tried to recreate the simple pagination example given by piotr-berebecki which was great. But when there will be a lot of pages then the pagination will overflow in the screen. So, I used previous and back button along with forward and backward button to stream back and forth between the pages. And for design part I have used bootstrap 3.
You can customize no of pages to display in pagination using the pagebound values. Make sure to use same value for upperPageBound and pageBound.
class TodoApp extends React.Component {
constructor() {
super();
this.state = {
todos: ['a','b','c','d','e','f','g','h','i','j','k','l','m',
'n','o','p','q','r','s','t','u','v','w','x','y','z'],
currentPage: 1,
todosPerPage: 3,
upperPageBound: 3,
lowerPageBound: 0,
isPrevBtnActive: 'disabled',
isNextBtnActive: '',
pageBound: 3
};
this.handleClick = this.handleClick.bind(this);
this.btnDecrementClick = this.btnDecrementClick.bind(this);
this.btnIncrementClick = this.btnIncrementClick.bind(this);
this.btnNextClick = this.btnNextClick.bind(this);
this.btnPrevClick = this.btnPrevClick.bind(this);
// this.componentDidMount = this.componentDidMount.bind(this);
this.setPrevAndNextBtnClass = this.setPrevAndNextBtnClass.bind(this);
}
componentDidUpdate() {
$("ul li.active").removeClass('active');
$('ul li#'+this.state.currentPage).addClass('active');
}
handleClick(event) {
let listid = Number(event.target.id);
this.setState({
currentPage: listid
});
$("ul li.active").removeClass('active');
$('ul li#'+listid).addClass('active');
this.setPrevAndNextBtnClass(listid);
}
setPrevAndNextBtnClass(listid) {
let totalPage = Math.ceil(this.state.todos.length / this.state.todosPerPage);
this.setState({isNextBtnActive: 'disabled'});
this.setState({isPrevBtnActive: 'disabled'});
if(totalPage === listid && totalPage > 1){
this.setState({isPrevBtnActive: ''});
}
else if(listid === 1 && totalPage > 1){
this.setState({isNextBtnActive: ''});
}
else if(totalPage > 1){
this.setState({isNextBtnActive: ''});
this.setState({isPrevBtnActive: ''});
}
}
btnIncrementClick() {
this.setState({upperPageBound: this.state.upperPageBound + this.state.pageBound});
this.setState({lowerPageBound: this.state.lowerPageBound + this.state.pageBound});
let listid = this.state.upperPageBound + 1;
this.setState({ currentPage: listid});
this.setPrevAndNextBtnClass(listid);
}
btnDecrementClick() {
this.setState({upperPageBound: this.state.upperPageBound - this.state.pageBound});
this.setState({lowerPageBound: this.state.lowerPageBound - this.state.pageBound});
let listid = this.state.upperPageBound - this.state.pageBound;
this.setState({ currentPage: listid});
this.setPrevAndNextBtnClass(listid);
}
btnPrevClick() {
if((this.state.currentPage -1)%this.state.pageBound === 0 ){
this.setState({upperPageBound: this.state.upperPageBound - this.state.pageBound});
this.setState({lowerPageBound: this.state.lowerPageBound - this.state.pageBound});
}
let listid = this.state.currentPage - 1;
this.setState({ currentPage : listid});
this.setPrevAndNextBtnClass(listid);
}
btnNextClick() {
if((this.state.currentPage +1) > this.state.upperPageBound ){
this.setState({upperPageBound: this.state.upperPageBound + this.state.pageBound});
this.setState({lowerPageBound: this.state.lowerPageBound + this.state.pageBound});
}
let listid = this.state.currentPage + 1;
this.setState({ currentPage : listid});
this.setPrevAndNextBtnClass(listid);
}
render() {
const { todos, currentPage, todosPerPage,upperPageBound,lowerPageBound,isPrevBtnActive,isNextBtnActive } = this.state;
// Logic for displaying current todos
const indexOfLastTodo = currentPage * todosPerPage;
const indexOfFirstTodo = indexOfLastTodo - todosPerPage;
const currentTodos = todos.slice(indexOfFirstTodo, indexOfLastTodo);
const renderTodos = currentTodos.map((todo, index) => {
return <li key={index}>{todo}</li>;
});
// Logic for displaying page numbers
const pageNumbers = [];
for (let i = 1; i <= Math.ceil(todos.length / todosPerPage); i++) {
pageNumbers.push(i);
}
const renderPageNumbers = pageNumbers.map(number => {
if(number === 1 && currentPage === 1){
return(
<li key={number} className='active' id={number}><a href='#' id={number} onClick={this.handleClick}>{number}</a></li>
)
}
else if((number < upperPageBound + 1) && number > lowerPageBound){
return(
<li key={number} id={number}><a href='#' id={number} onClick={this.handleClick}>{number}</a></li>
)
}
});
let pageIncrementBtn = null;
if(pageNumbers.length > upperPageBound){
pageIncrementBtn = <li className=''><a href='#' onClick={this.btnIncrementClick}> … </a></li>
}
let pageDecrementBtn = null;
if(lowerPageBound >= 1){
pageDecrementBtn = <li className=''><a href='#' onClick={this.btnDecrementClick}> … </a></li>
}
let renderPrevBtn = null;
if(isPrevBtnActive === 'disabled') {
renderPrevBtn = <li className={isPrevBtnActive}><span id="btnPrev"> Prev </span></li>
}
else{
renderPrevBtn = <li className={isPrevBtnActive}><a href='#' id="btnPrev" onClick={this.btnPrevClick}> Prev </a></li>
}
let renderNextBtn = null;
if(isNextBtnActive === 'disabled') {
renderNextBtn = <li className={isNextBtnActive}><span id="btnNext"> Next </span></li>
}
else{
renderNextBtn = <li className={isNextBtnActive}><a href='#' id="btnNext" onClick={this.btnNextClick}> Next </a></li>
}
return (
<div>
<ul>
{renderTodos}
</ul>
<ul id="page-numbers" className="pagination">
{renderPrevBtn}
{pageDecrementBtn}
{renderPageNumbers}
{pageIncrementBtn}
{renderNextBtn}
</ul>
</div>
);
}
}
ReactDOM.render(
<TodoApp />,
document.getElementById('app')
);
Working demo link : https://codepen.io/mhmanandhar/pen/oEWBqx
Image : simple react pagination

Here is a way to create your Custom Pagination Component from react-bootstrap lib and this component you can use Throughout your project
Your Pagination Component (pagination.jsx or js)
import React, { Component } from "react";
import { Pagination } from "react-bootstrap";
import PropTypes from "prop-types";
export default class PaginationHandler extends Component {
constructor(props) {
super(props);
this.state = {
paging: {
offset: 0,
limit: 10
},
active: 0
};
}
pagingHandler = () => {
let offset = parseInt(event.target.id);
this.setState({
active: offset
});
this.props.pageHandler(event.target.id - 1); };
nextHandler = () => {
let active = this.state.active;
this.setState({
active: active + 1
});
this.props.pageHandler(active + 1); };
backHandler = () => {
let active = this.state.active;
this.setState({
active: active - 1
});
this.props.pageHandler(active - 1); };
renderPageNumbers = (pageNumbers, totalPages) => {
let { active } = this.state;
return (
<Pagination>
<Pagination.Prev disabled={active < 5} onClick={ active >5 && this.backHandler} />
{
pageNumbers.map(number => {
if (
number >= parseInt(active) - 3 &&
number <= parseInt(active) + 3
) {
return (
<Pagination.Item
id={number}
active={number == active}
onClick={this.pagingHandler}
>
{number}
</Pagination.Item>
);
} else {
return null;
}
})}
<Pagination.Next onClick={ active <= totalPages -4 && this.nextHandler} />
</Pagination>
); };
buildComponent = (props, state) => {
const { totalPages } = props;
const pageNumbers = [];
for (let i = 1; i <= totalPages; i++) {
pageNumbers.push(i);
}
return (
<div className="pull-right">
{this.renderPageNumbers(pageNumbers ,totalPages)}
</div>
);
};
render() {
return this.buildComponent(this.props, this.state);
}
}
PaginationHandler.propTypes =
{
paging: PropTypes.object,
pageHandler: PropTypes.func,
totalPages: PropTypes.object
};
Use of Above Child Component in your Parent Component as shown below
import Pagination from "../pagination";
pageHandler = (offset) =>{
this.setState(({ paging }) => ({
paging: { ...paging, offset: offset }
}));
}
render() {
return (
<div>
<Pagination
paging = {paging}
pageHandler = {this.pageHandler}
totalPages = {totalPages}>
</Pagination>
</div>
);
}

Sample pagination react js working code
import React, { Component } from 'react';
import {
Pagination,
PaginationItem,
PaginationLink
} from "reactstrap";
let prev = 0;
let next = 0;
let last = 0;
let first = 0;
export default class SamplePagination extends Component {
constructor() {
super();
this.state = {
todos: ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','T','v','u','w','x','y','z'],
currentPage: 1,
todosPerPage: 3,
};
this.handleClick = this.handleClick.bind(this);
this.handleLastClick = this.handleLastClick.bind(this);
this.handleFirstClick = this.handleFirstClick.bind(this);
}
handleClick(event) {
event.preventDefault();
this.setState({
currentPage: Number(event.target.id)
});
}
handleLastClick(event) {
event.preventDefault();
this.setState({
currentPage:last
});
}
handleFirstClick(event) {
event.preventDefault();
this.setState({
currentPage:1
});
}
render() {
let { todos, currentPage, todosPerPage } = this.state;
// Logic for displaying current todos
let indexOfLastTodo = currentPage * todosPerPage;
let indexOfFirstTodo = indexOfLastTodo - todosPerPage;
let currentTodos = todos.slice(indexOfFirstTodo, indexOfLastTodo);
prev = currentPage > 0 ? (currentPage -1) :0;
last = Math.ceil(todos.length/todosPerPage);
next = (last === currentPage) ?currentPage: currentPage +1;
// Logic for displaying page numbers
let pageNumbers = [];
for (let i = 1; i <=last; i++) {
pageNumbers.push(i);
}
return (
<div>
<ul>
{
currentTodos.map((todo,index) =>{
return <li key={index}>{todo}</li>;
})
}
</ul><ul id="page-numbers">
<nav>
<Pagination>
<PaginationItem>
{ prev === 0 ? <PaginationLink disabled>First</PaginationLink> :
<PaginationLink onClick={this.handleFirstClick} id={prev} href={prev}>First</PaginationLink>
}
</PaginationItem>
<PaginationItem>
{ prev === 0 ? <PaginationLink disabled>Prev</PaginationLink> :
<PaginationLink onClick={this.handleClick} id={prev} href={prev}>Prev</PaginationLink>
}
</PaginationItem>
{
pageNumbers.map((number,i) =>
<Pagination key= {i}>
<PaginationItem active = {pageNumbers[currentPage-1] === (number) ? true : false} >
<PaginationLink onClick={this.handleClick} href={number} key={number} id={number}>
{number}
</PaginationLink>
</PaginationItem>
</Pagination>
)}
<PaginationItem>
{
currentPage === last ? <PaginationLink disabled>Next</PaginationLink> :
<PaginationLink onClick={this.handleClick} id={pageNumbers[currentPage]} href={pageNumbers[currentPage]}>Next</PaginationLink>
}
</PaginationItem>
<PaginationItem>
{
currentPage === last ? <PaginationLink disabled>Last</PaginationLink> :
<PaginationLink onClick={this.handleLastClick} id={pageNumbers[currentPage]} href={pageNumbers[currentPage]}>Last</PaginationLink>
}
</PaginationItem>
</Pagination>
</nav>
</ul>
</div>
);
}
}
ReactDOM.render(
<SamplePagination />,
document.getElementById('root')
);

I recently created this Pagination component that implements paging logic like Google's search results:
import React, { PropTypes } from 'react';
 
const propTypes = {
    items: PropTypes.array.isRequired,
    onChangePage: PropTypes.func.isRequired,
    initialPage: PropTypes.number   
}
 
const defaultProps = {
    initialPage: 1
}
 
class Pagination extends React.Component {
    constructor(props) {
        super(props);
        this.state = { pager: {} };
    }
 
    componentWillMount() {
        this.setPage(this.props.initialPage);
    }
 
    setPage(page) {
        var items = this.props.items;
        var pager = this.state.pager;
 
        if (page < 1 || page > pager.totalPages) {
            return;
        }
 
        // get new pager object for specified page
        pager = this.getPager(items.length, page);
 
        // get new page of items from items array
        var pageOfItems = items.slice(pager.startIndex, pager.endIndex + 1);
 
        // update state
        this.setState({ pager: pager });
 
        // call change page function in parent component
        this.props.onChangePage(pageOfItems);
    }
 
    getPager(totalItems, currentPage, pageSize) {
        // default to first page
        currentPage = currentPage || 1;
 
        // default page size is 10
        pageSize = pageSize || 10;
 
        // calculate total pages
        var totalPages = Math.ceil(totalItems / pageSize);
 
        var startPage, endPage;
        if (totalPages <= 10) {
            // less than 10 total pages so show all
            startPage = 1;
            endPage = totalPages;
        } else {
            // more than 10 total pages so calculate start and end pages
            if (currentPage <= 6) {
                startPage = 1;
                endPage = 10;
            } else if (currentPage + 4 >= totalPages) {
                startPage = totalPages - 9;
                endPage = totalPages;
            } else {
                startPage = currentPage - 5;
                endPage = currentPage + 4;
            }
        }
 
        // calculate start and end item indexes
        var startIndex = (currentPage - 1) * pageSize;
        var endIndex = Math.min(startIndex + pageSize - 1, totalItems - 1);
 
        // create an array of pages to ng-repeat in the pager control
        var pages = _.range(startPage, endPage + 1);
 
        // return object with all pager properties required by the view
        return {
            totalItems: totalItems,
            currentPage: currentPage,
            pageSize: pageSize,
            totalPages: totalPages,
            startPage: startPage,
            endPage: endPage,
            startIndex: startIndex,
            endIndex: endIndex,
            pages: pages
        };
    }
 
    render() {
        var pager = this.state.pager;
 
        return (
            <ul className="pagination">
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(1)}>First</a>
                </li>
                <li className={pager.currentPage === 1 ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage - 1)}>Previous</a>
                </li>
                {pager.pages.map((page, index) =>
                    <li key={index} className={pager.currentPage === page ? 'active' : ''}>
                        <a onClick={() => this.setPage(page)}>{page}</a>
                    </li>
                )}
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.currentPage + 1)}>Next</a>
                </li>
                <li className={pager.currentPage === pager.totalPages ? 'disabled' : ''}>
                    <a onClick={() => this.setPage(pager.totalPages)}>Last</a>
                </li>
            </ul>
        );
    }
}
 
Pagination.propTypes = propTypes;
Pagination.defaultProps
export default Pagination;
And here's an example App component that uses the Pagination component to paginate a list of 150 example items:
import React from 'react';
import Pagination from './Pagination';
 
class App extends React.Component {
    constructor() {
        super();
 
        // an example array of items to be paged
        var exampleItems = _.range(1, 151).map(i => { return { id: i, name: 'Item ' + i }; });
 
        this.state = {
            exampleItems: exampleItems,
            pageOfItems: []
        };
 
        // bind function in constructor instead of render (https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md)
        this.onChangePage = this.onChangePage.bind(this);
    }
 
    onChangePage(pageOfItems) {
        // update state with new page of items
        this.setState({ pageOfItems: pageOfItems });
    }
 
    render() {
        return (
            <div>
                <div className="container">
                    <div className="text-center">
                        <h1>React - Pagination Example with logic like Google</h1>
                        {this.state.pageOfItems.map(item =>
                            <div key={item.id}>{item.name}</div>
                        )}
                        <Pagination items={this.state.exampleItems} onChangePage={this.onChangePage} />
                    </div>
                </div>
                <hr />
                <div className="credits text-center">
                    <p>
                        JasonWatmore.com
                    </p>
                </div>
            </div>
        );
    }
}
 
export default App;
For more details and a live demo you can check out this post

Please see this code sample in codesandbox
https://codesandbox.io/s/pagino-13pit
import Pagino from "pagino";
import { useState, useMemo } from "react";
export default function App() {
const [pages, setPages] = useState([]);
const pagino = useMemo(() => {
const _ = new Pagino({
showFirst: false,
showLast: false,
onChange: (page, count) => setPages(_.getPages())
});
_.setCount(10);
return _;
}, []);
const hanglePaginoNavigation = (type) => {
if (typeof type === "string") {
pagino[type]?.();
return;
}
pagino.setPage(type);
};
return (
<div>
<h1>Page: {pagino.page}</h1>
<ul>
{pages.map((page) => (
<button key={page} onClick={() => hanglePaginoNavigation(page)}>
{page}
</button>
))}
</ul>
</div>
);
}

1- Use react-bootstrap
import React from "react";
import { Pagination } from "react-bootstrap";
import Link from "next/link";
interface PaginateProps {
pages: number;
page: number;
keyword?: string;
isAdmin?: boolean;
}
const Paginate = ({
pages,
page,
// keyword is for searching
keyword = "",
isAdmin = false,
}: PaginateProps) => {
return pages > 1 ? (
<Pagination>
{/* keys returns a new Array iterator contians the keys for each index */}
// pages is total number of pages
{[...Array(pages).keys()].map((x) => (
// this is next.js component. you could use react Link
<Link
passHref
key={x + 1}
href={
!isAdmin
// since array indexing starts at 0, use x+1
? `/?keyword=${keyword}&page=${x + 1}`
: `/admin/productlist/?keyword=${keyword}&page=${x + 1}`
}
>
<Pagination.Item active={x + 1 === page}>{x + 1}</Pagination.Item>
</Link>
))}
</Pagination>
) : null;
};
export default Paginate;
then use it in a page.
<Paginate page={page} pages={pages} keyword={keyword} />
2- use react-js pagination
import Pagination from "react-js-pagination";
// bootstrap class to center
<div className="d-flex justify-content-center mt-5">
<Pagination
activePage={page}
itemsCountPerPage={resPerPageValue}
totalItemsCount={Count}
onChange={handlePagination}
nextPageText={"Next"}
prevPageText={"Prev"}
firstPageText={"First"}
lastPageText={"Last"}
// overwriting the style
itemClass="page-item"
linkClass="page-link"
/>
</div>
onChange handler
const handlePagination = (pageNumber) => {
// window.location.href will reload entire page
router.push(`/?page=${pageNumber}`);
};

I've recently created library which helps to cope with pagination cases like:
storing normalized data in Redux
caching pages based on search filters
simplified react-virtualized list usage
refreshing results in background
storing last visited page and used filters
DEMO page implements all above features.
Source code you can find on Github

A ReactJS dumb component to render pagination. You can also use this Library:
https://www.npmjs.com/package/react-js-pagination
Some Examples are Here:
http://vayser.github.io/react-js-pagination/
OR
You can also Use this https://codepen.io/PiotrBerebecki/pen/pEYPbY

Make sure you make it as a separate component
I have used tabler-react
import * as React from "react";
import { Page, Button } from "tabler-react";
class PaginateComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
limit: 5, // optional
page: 1
};
}
paginateValue = (page) => {
this.setState({ page: page });
console.log(page) // access this value from parent component
}
paginatePrevValue = (page) => {
this.setState({ page: page });
console.log(page) // access this value from parent component
}
paginateNxtValue = (page) => {
this.setState({ page: page });
console.log(page) // access this value from parent component
}
render() {
return (
<div>
<div>
<Button.List>
<Button
disabled={this.state.page === 0}
onClick={() => this.paginatePrevValue(this.state.page - 1)}
outline
color="primary"
>
Previous
</Button>
{this.state.array.map((value, index) => {
return (
<Button
onClick={() => this.paginateValue(value)}
color={
this.state.page === value
? "primary"
: "secondary"
}
>
{value}
</Button>
);
})}
<Button
onClick={() => this.paginateNxtValue(this.state.page + 1)}
outline
color="secondary"
>
Next
</Button>
</Button.List>
</div>
</div>
)
}
}
export default PaginateComponent;

I have implemented pagination + search in ReactJs, see the output:
Pagination in React
View complete code on GitHub: https://github.com/navanathjadhav/generic-pagination
Also visit this article for step by step implementation of pagination: https://everblogs.com/react/3-simple-steps-to-add-pagination-in-react/

Give you a pagination component, which is maybe a little difficult to understand for newbie to react:
https://www.npmjs.com/package/pagination

Related

Unable to create class Component for material ui ColorlibStepIcon in typescript format

My objective is to create Stepper as class component, but i could not able to do the ColorlibStepIcon functional component into class component.
I've tried but it is showing couple of errors.
Here is the working sample
In this way i've tried (i've commented the code in the above link):
class ColorlibStepIcon extends React.Component(StepIconProps, PState) {
constructor(props: StepIconProps) {
super(props);
this.state = {
icons: { 1: <SettingsIcon />, 2: <GroupAddIcon />, 3: <VideoLabelIcon /> }
};
}
render() {
const { active, completed } = this.props;
const classes = this.useColorlibStepIconStyles();
return (
<div
className={clsx(classes.root, {
[classes.active]: active,
[classes.completed]: completed
})}
>
{icons[String(props.icon)]}
</div>
);
}
}
One more query, can anyone help in handleNext method also?
handleNext = () => {
setActiveStep((prevActiveStep) => prevActiveStep + 1);
};
Can anyone please help me in making class component. I was stuck from days?
Thanks in advance!
First of all you should't use JSX inside of state's prop. It's not a react way and to my mind not too good approach.
It's better to make it much easier.
For instance I am using functional components and hooks but you can take an idea at least.
just have a look.
components inside of render right away ( I prefer this)
interface State {
currentStep: number;
}
export class StepsSimpleClassComponent extends React.Component<StepIconProps, State> {
private stepsCount = 1 // from zero;
public state: State = {
currentStep: 0
}
private handlePrev = () => {
const result = this.state.currentStep - 1;
this.setState({
currentStep: result < 0 ? 0 : result
})
}
private handleNext = () => {
const result = this.state.currentStep + 1;
this.setState({
currentStep: result > this.stepsCount ? 1 : result
})
}
render() {
const { currentStep } = this.state;
return (
<>
<Step0 active={currentStep >= 0} />
<Step1 active={currentStep >= 1} />
<button onClick={this.handlePrev}>Prev</button>
<button onClick={this.handleNext}>Next</button>
</>
)
}
}
the same but with a functional component
export const StepsSimple: React.FC = () => {
const stepsCount = 1 // from zero;
const [currentStep, setCurrentStep] = React.useState(0);
function handlePrev() {
const result = currentStep - 1;
setCurrentStep(result < 0 ? 0 : result);
}
function handleNext() {
const result = currentStep + 1;
setCurrentStep(result > stepsCount ? 1 : result);
}
return (
<>
<Step0 active={currentStep >= 0} />
<Step1 active={currentStep >= 1} />
<button onClick={handlePrev}>Prev</button>
<button onClick={handleNext}>Next</button>
</>
)
}
using and array of steps
export const StepsUsingMap: React.FC = () => {
const [currentStep, setCurrentStep] = React.useState(0);
const steps = [
Step0,
Step1
]
const stepsCount = steps.length - 1 // from zero;
function handlePrev() {
const result = currentStep - 1;
setCurrentStep(result < 0 ? 0 : result);
}
function handleNext() {
const result = currentStep + 1;
setCurrentStep(result > stepsCount ? 1 : result);
}
return (
<>
{steps.map((Step, index) => (
<Step key={index} active={currentStep >= index} />
))}
<button onClick={handlePrev}>Prev</button>
<button onClick={handleNext}>Next</button>
</>
)
}
an example of steps components
interface Props {
active: boolean;
}
// Step example
const Step0: React.FC<Props> = ({ active = false }) => {
return (
<div className={clsx({ [classes.active]: active, })}>Step0</div>
)
}
const Step1: React.FC<Props> = ({ active = false }) => {
return (
<div className={clsx({ [classes.active]: active, })}>Step1</div>
)
}

React-table does not show valid page number when filter is applied again

In my web application there is the table which displays '50' records on each page depending on the number of pages are present in the applied filter. Problem is, ex-> for a 1 JAN 2021 to 2 FEB 2021 there are 2 pages each displaying 50 records if I switch to 2 page it shows 2 of 2 pages and then again I change the the filter but pagination of react table shows 2 of 2 pages instead of 1 of 2 pages
While the data show of 1 pages according to the filter applied.
The whole code of component
import _ from 'lodash';
import PropTypes from 'prop-types';
import React from 'react';
import ReactTable from 'react-table';
import { STORAGE_KEYS } from '../../constants/localStorageKeys';
import { PAGINATION_PROPS } from '../../constants/paginationProps';
import { APPOINTMENTS_MODAL_CONTENT } from '../../constants/status';
import {
areDemographicsEnabled,
areEmailOrTextRemindersEnabled,
areEmailRemindersEnabled,
areTextRemindersEnabled,
getAppointmentTableData,
hasPrecheckForms,
isBalanceEnabled,
isCopayEnabled,
isInsuranceEnabled,
isPrecheckEnabled,
areBroadcastEnabled
} from './AppointmentsUtils';
import BalanceExpandedCell from './tableCells/BalanceExpandedCell';
import CopayExpandedCell from './tableCells/CopayExpandedCell';
import DemographicsExpandedCell from './tableCells/DemographicsExpandedCell';
import FormsExpandedCell from './tableCells/FormsExpandedCell';
import InsuranceExpandedCell from './tableCells/InsuranceExpandedCell';
import RemindersExpandedCell from './tableCells/RemindersExpandedCell';
import selectTableHOC from 'react-table/lib/hoc/selectTable';
const SelectTable = selectTableHOC(ReactTable);
class AppointmentsTable extends React.PureComponent {
static propTypes = {
clickedOnReminderStatusesViewAll: PropTypes.func.isRequired,
error: PropTypes.bool,
fetchAppointments: PropTypes.func.isRequired,
filteredAppointments: PropTypes.array,
onRowClick: PropTypes.func.isRequired,
pageNumber: PropTypes.number,
practiceConfig: PropTypes.object,
selectAll: PropTypes.bool,
selectedAppointmentIds: PropTypes.array,
toggleAll: PropTypes.func.isRequired,
toggleSelection: PropTypes.func.isRequired,
totalPages: PropTypes.number
};
constructor(props) {
super(props);
const { practiceConfig } = props;
this.state = {
isExpanded: false,
expanded: _.fill(Array(PAGINATION_PROPS.PAGE_SIZE_VALUE), false),
displayAllColumns:
isPrecheckEnabled(practiceConfig) || areEmailOrTextRemindersEnabled(practiceConfig)
? this.getColumnsVisibilitySettings()
: true
};
this.isSelected = this.isSelected.bind(this);
this.toggleHeaderExpansion = this.toggleHeaderExpansion.bind(this);
this.toggleColumnsVisibility = this.toggleColumnsVisibility.bind(this);
this.getExpandedRowSubComponent = this.getExpandedRowSubComponent.bind(this);
this.fetchData = this.fetchData.bind(this);
this.setRef = this.setRef.bind(this);
this.getTdProps = this.getTdProps.bind(this);
this.onExpandedChange = this.onExpandedChange.bind(this);
this.onPatientNameClick = this.onPatientNameClick.bind(this);
}
componentDidUpdate(prevProps) {
const { filteredAppointments } = this.props;
if (filteredAppointments !== prevProps.filteredAppointments) {
this.setRowsExpansion(this.state.isExpanded);
}
}
getColumnsVisibilitySettings() {
let displayAllColumns = true;
const localStorageValue = localStorage.getItem(STORAGE_KEYS.DISPLAY_ALL);
if (localStorageValue != null) {
displayAllColumns = JSON.parse(localStorageValue);
}
return displayAllColumns;
}
isSelected(id) {
const { selectedAppointmentIds } = this.props;
return selectedAppointmentIds.includes(id);
}
toggleHeaderExpansion() {
const isExpanded = !this.state.isExpanded;
this.setRowsExpansion(isExpanded);
this.setState({ isExpanded });
}
setRowsExpansion(isExpanded) {
const expanded = _.fill(Array(PAGINATION_PROPS.PAGE_SIZE_VALUE), isExpanded);
if (!_.isEqual(expanded, this.state.expanded)) {
this.setState({ expanded });
}
}
toggleColumnsVisibility() {
const displayAllColumns = !this.state.displayAllColumns;
localStorage.setItem(STORAGE_KEYS.DISPLAY_ALL, displayAllColumns);
this.setState({ displayAllColumns });
}
getExpandedRowPrecheckDiv(row) {
const { practiceConfig } = this.props;
return (
<div className="expanded-row">
{areDemographicsEnabled(practiceConfig) && (
<DemographicsExpandedCell status={row.demographics} />
)}
{isInsuranceEnabled(practiceConfig) && (
<InsuranceExpandedCell
insuranceData={row.insurance}
clickedOnInsuranceDetails={() =>
this.props.onRowClick(
row._original,
APPOINTMENTS_MODAL_CONTENT.INSURANCE
)
}
/>
)}
{isCopayEnabled(practiceConfig) && <CopayExpandedCell paymentData={row.copay} />}
{isBalanceEnabled(practiceConfig) && (
<BalanceExpandedCell paymentData={row.balance} />
)}
{hasPrecheckForms(practiceConfig) && (
<FormsExpandedCell patientForms={_.get(row, 'forms', null)} />
)}
</div>
);
}
getExpandedRowSubComponent({ original: appointment, row }) {
const { clickedOnReminderStatusesViewAll, practiceConfig } = this.props;
return (
<div className="rt-tr expanded-row">
<div className="rt-td precheck-cell expander-cell" />
{isPrecheckEnabled(practiceConfig) && this.getExpandedRowPrecheckDiv(row)}
<div className="expanded-row">
{areEmailRemindersEnabled(practiceConfig) && (
<RemindersExpandedCell
appointment={appointment}
clickedOnViewAll={clickedOnReminderStatusesViewAll}
practiceConfig={practiceConfig}
type="email"
/>
)}
{areTextRemindersEnabled(practiceConfig) && (
<RemindersExpandedCell
appointment={appointment}
clickedOnViewAll={clickedOnReminderStatusesViewAll}
practiceConfig={practiceConfig}
type="text"
/>
)}
{areBroadcastEnabled(practiceConfig) && (
<div className="rt-td precheck-cell expanded-precheck-cell"></div>
)}
{areBroadcastEnabled(practiceConfig) && (
<div className="rt-td precheck-cell expanded-precheck-cell"></div>
)}
</div>
</div>
);
}
fetchData(reactTableState) {
const { pageNumber } = this.props;
// This function gets called twice on table page change. Once for the page change itself,
// and once for the data change. Don't re-fetch on data change. By the way, the table's
// page is 0-based, but the prop's is 1-based.
//
// This function also gets called on initial load. In that case, the table's page is 0,
// and the pageNumber prop is undefined.
if (pageNumber !== reactTableState.page + 1) {
this.props.fetchAppointments(reactTableState.page);
}
}
setRef(r) {
this.reactTable = r;
}
onPatientNameClick(rowInfo) {
this.props.onRowClick(rowInfo.original, APPOINTMENTS_MODAL_CONTENT.ALL_PATIENT_DATA);
}
onTextClicked = rowInfo => {
this.props.onRowClick(rowInfo.original, APPOINTMENTS_MODAL_CONTENT.BROADCASTTEXT);
};
onEmailClicked = rowInfo => {
this.props.onRowClick(rowInfo.original, APPOINTMENTS_MODAL_CONTENT.BROADCASTMAIL);
};
getTdProps(_state, rowInfo, column) {
const props = {};
if (column.id === 'patientName') {
props.onClick = () => this.onPatientNameClick(rowInfo);
}
if (column.id === 'textBroadcast') {
props.onClick = () => this.onTextClicked(rowInfo);
}
if (column.id === 'emailBroadcast') {
props.onClick = () => this.onEmailClicked(rowInfo);
}
return props;
}
onExpandedChange(_newExpanded, index) {
const expanded = this.state.expanded.slice();
expanded[index] = !expanded[index];
this.setState({ expanded });
}
render() {
const {
error,
filteredAppointments,
pageNumber = 1,
practiceConfig,
selectAll,
toggleAll,
toggleSelection,
totalPages = 1
} = this.props;
const data = error ? [] : filteredAppointments || [];
const pages = error ? 0 : totalPages;
const tableText = error
? 'Appointments are currently unavailable. Please try back later.'
: 'There are no appointments for selected filters';
const expandedRowSubComponent =
isPrecheckEnabled(practiceConfig) || areEmailOrTextRemindersEnabled(practiceConfig)
? { SubComponent: this.getExpandedRowSubComponent }
: {};
const reactTableProps = {
className: 'mf-appointments-table',
columns: getAppointmentTableData(
this.toggleHeaderExpansion,
this.toggleColumnsVisibility,
this.state.isExpanded,
this.state.displayAllColumns,
practiceConfig
),
data,
defaultPageSize: PAGINATION_PROPS.PAGE_SIZE_VALUE,
expanded: this.state.expanded,
getTdProps: this.getTdProps,
id: 'appointmentsTable',
loadingText: 'Loading...',
manual: true, // informs React Table that you'll be handling sorting and pagination server-side
minRows: 0,
nextText: '',
noDataText: tableText,
ofText: 'of',
onExpandedChange: this.onExpandedChange,
onFetchData: this.fetchData, // Request new data when things change
page: pageNumber - 1,
pages,
pageText: '',
previousText: '',
ref: this.setRef,
rowsText: '',
showPageSizeOptions: false,
showPagination: pages > 1,
showPaginationBottom: true,
sortable: false,
...expandedRowSubComponent
};
const selectTableProps = {
isSelected: this.isSelected,
selectAll,
selectType: 'checkbox',
toggleAll,
toggleSelection
};
return (
<div>
<SelectTable {...reactTableProps} {...selectTableProps} />
</div>
);
}
}
export default AppointmentsTable;
The Pagination has an input in which you can edit the page

How to update the state in react?

I'd like to update the state and display the current state in a React Fragment inside a method. I'm updating the state in reserve method and I'd like to display the new value on a button click. I was trying to do that in the span in fetchData() method but when I click the button, I don't see the updated value. Below is the screen shot of the application and code.
class BooksComponent extends Component{
constructor(props){
super(props)
this.state ={
booksData: [],
offset: 0,
perPage: 3,
currentPage: 0,
}
this.reserve = this.reserve.bind(this)
this.fetchData = this.fetchData.bind(this)
}
fetchData(){
axios.get('/library')
.then(res => {
const booksData = res.data
const slice = booksData.slice(this.state.offset, this.state.offset + this.state.perPage)
const books = slice.map(book =>
<React.Fragment key={book.id}>
<p>{book.id} - {book.title} - {book.author}</p>
<button onClick={() => this.reserve(book.id)}>Reserve {book.quantity}</button>
<span>{this.state.booksData.quantity}</span>
</React.Fragment>)
this.setState({
pageCount: Math.ceil(booksData.length / this.state.perPage),
books })
})
}
handlePageClick = (e) => {
const selectedPage = e.selected;
const offset = selectedPage * this.state.perPage;
this.setState({
currentPage: selectedPage,
offset: offset
}, () => {
this.fetchData()
});
};
componentDidMount() {
this.fetchData()
}
render() {
return (
<div className="App">
{this.state.books}
<ReactPaginate
previousLabel={"prev"}
nextLabel={"next"}
breakLabel={"..."}
breakClassName={"break-me"}
pageCount={this.state.pageCount}
marginPagesDisplayed={2}
pageRangeDisplayed={5}
onPageChange={this.handlePageClick}
containerClassName={"pagination"}
subContainerClassName={"pages pagination"}
activeClassName={"active"}/>
</div>
)
}
reserve(id) {
console.log("clicked")
this.setState({
booksData: this.state.booksData.map(item => {
if (item.id === id) {
return { ...item, quantity: (item.quantity - 1) >= 0 ? (item.quantity - 1) : 0};
} else {
return item;
}
})
})
}
}
export default BooksComponent
Don't store jsx in your state, store the data and let React handle the render, so, just do this in your fetchData function
this.setState({
pageCount: Math.ceil(booksData.length / this.state.perPage),
books
})
and in your render
<div className="App">
{this.state.books.map(book =>
<React.Fragment key={book.id}>
<p>{book.id} - {book.title} - {book.author}</p>
<button onClick={() => this.reserve(book.id)}>Reserve {book.quantity}</button>
<span>{this.state.booksData.quantity}</span>
</React.Fragment>
)}
<ReactPaginate...
</div>
This will re-render when the state changes and will display the correct value

Conection between components React - shopping-cart

I tried to bulid shopping cart in React I don't use redux so I think it can be a problem too. I have now alomost done application so I want to finish it in this way without using redux. Ok whats the problem. I made function to add items into the shopping cart in component counter but I don't want to display this products in this component but in main component App in header. In component counter I creat component ShoppingCart to display the products - but I want to only push products into the ShoppingCart but display them in component App.
I tried a lot of diffrent methods but it's not working. I can display products but really not in the place I want. I think the problem is how I can comunicate with my items between components.
This is my Counter
import React, { Component } from "react";
import "./Counter";
import "./Counter.css";
import ShoppingCart from "./ShoppingCart";
class Counter extends Component {
state = {
availableProducts: 20,
shoppingCart: 0,
cart: []
};
handleRemoveFromCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart - 1
});
};
handleAddToCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart + 1
});
};
handleAddProductsToCart = props => {
// console.log("clicked", this.props.name, this.state.shoppingCart)
let found = false;
const updateCart = this.state.cart.map(cartItem => {
if (cartItem.name === this.props.name) {
found = true;
cartItem.productsNumber = this.state.shoppingCart;
return cartItem;
} else {
return cartItem;
}
});
if (!found) {
updateCart.push({
name: this.props.name,
productsNumber: this.state.shoppingCart,
key: this.props.name
});
}
this.setState({
cart: updateCart
});
// return <ShoppingCart cart={updateCart} />;
// console.log(updateCart);
};
render() {
const cart = this.state.cart.map(cartItem => (
<ShoppingCart
name={cartItem.name}
productsNumber={cartItem.productsNumber}
key={cartItem.key}
/>
));
return (
<>
<div className="counter">
<button
className="buttonCount"
disabled={this.state.shoppingCart === 0 ? true : false}
onClick={this.handleRemoveFromCart}
>-</button>
<span> {this.state.shoppingCart} </span>
<button
className="buttonCount"
disabled={
this.state.shoppingCart === this.state.availableProducts
? true
: false
}
onClick={this.handleAddToCart}
>
+
</button>
<button
className="buy"
disabled={this.state.shoppingCart <= 0 ? true : false}
onClick={this.handleAddProductsToCart}
>Add to cart</button>
</div>
<div>{cart}</div>
</>
);
}
}
export default Counter;
and this is Shopping
import React, {Component} from "react"
import "./ShoppingCart";
import "./ShoppingCart.css";
class ShoppingCart extends Component {
render() {
return (
<>
<div>{this.props.name}</div>
<div>{this.props.productsNumber}</div>
</>
);
}
}
export default ShoppingCart;
If you have any suggestions it will be helpful. Thank you.
I think the following code can help you
import React, { Component } from "react";
import "./Counter";
import "./Counter.css";
import ShoppingCart from "./ShoppingCart";
class Counter extends Component {
state = {
availableProducts: 20,
shoppingCart: 0,
cart: []
};
handleRemoveFromCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart - 1
});
};
handleAddToCart = () => {
this.setState({
shoppingCart: this.state.shoppingCart + 1
});
};
handleAddProductsToCart = props => {
// console.log("clicked", this.props.name, this.state.shoppingCart)
let found = false;
const updateCart = this.state.cart.map(cartItem => {
if (cartItem.name === this.props.name) {
found = true;
cartItem.productsNumber = this.state.shoppingCart;
return cartItem;
} else {
return cartItem;
}
});
if (!found) {
updateCart.push({
name: this.props.name,
productsNumber: this.state.shoppingCart,
key: this.props.name
});
}
this.setState({
cart: updateCart
});
// return <ShoppingCart cart={updateCart} />;
// console.log(updateCart);
};
CreateCard=(cartItem)=>{
console.log(cartItem)
return(
<ShoppingCart
name={cartItem.name}
productsNumber={cartItem.productsNumber}
key={cartItem.key}
/>
);
}
render() {
return (
<>
<div className="counter">
<button
className="buttonCount"
disabled={this.state.shoppingCart === 0 ? true : false}
onClick={this.handleRemoveFromCart}
>-</button>
<span> {this.state.shoppingCart} </span>
<button
className="buttonCount"
disabled={
this.state.shoppingCart === this.state.availableProducts
? true
: false
}
onClick={this.handleAddToCart}
>
+
</button>
<button
className="buy"
disabled={this.state.shoppingCart <= 0 ? true : false}
onClick={this.handleAddProductsToCart}
>Add to cart</button>
</div>
<div>{this.state.cart!=null?this.state.cart.map(cartItem => (this.CreateCard(cartItem))):""}</div>
</>
);
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.3.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.3.0/umd/react-dom.production.min.js"></script>
I hope help you

Cannot setState() on dynamically generated components

Let keyComponent filters a string for keywords and return them with an event handler(to toggle them) and generates a state (targetState) within this.state. The problem is that if I click on any of the keywords the state isn't updated/changed. I can see all the states being generated in this.state through console.log. they are simply not updating when clicked, no errors either.
I would appreciate some help ;-)
import React from 'react';
import { render } from 'react-dom';
import { sectionUpsert } from '/imports/api/userProgress/upsertMethods.jsx';
export default class LetsCheck extends React.Component {
constructor(props) {
super(props);
this.state = {
reference: props.reference,
text: props.text,
keys: props.keys,
CorrectArray: [],
};
}
handleClick(e, TxtBit, targetState) {
console.log(targetState);
console.log(this.state.targetState);
let tempA = this.state.CorrectArray;
let target = targetState;
tempA.push(TxtBit);
let obj = { [target]: true, }
console.log(obj);
this.setState(obj);
// this.setState({
// CorrectArray: tempA,
// [target]: true,
// });
console.log(this.state);
}
handleUnclick(e, TxtBit, targetState) {
console.log('unclicked' + TxtBit + index);
}
componentWillUnmount() {
let keys = this.state.keys;
let correct = this.state.CorrectArray;
let keyWW = keys.filter(function(key){
return !correct.includes(key) && keys.indexOf(key) % 2 === 0
});
const secData = {
userId: Meteor.userId(),
ref: this.state.reference,
keyWR: this.state.CorrectArray,
keyWW: keyWW,
porSect: Math.round((this.state.CorrectArray.length / (keyWW.length + this.state.CorrectArray.length)) * 100),
};
sectionUpsert.call(secData);
}
render() {
let keys = this.state.keys;
let clicked = this.state;
let filter = keys.filter( function(key) {
return keys.indexOf(key) % 2 === 0;
});
let KeyComponent = this.state.text.map(function(TxtBit, index) {
let match = false;
let checkMatch = function(TxtBit, filter) {
for (var y = filter.length - 1; y >= 0; y--) {
if ( TxtBit == filter[y] ) {
match = true
}
}
};
checkMatch(TxtBit, filter);
if( match ) {
targetState = 'KeyBtn' + index;
clicked[targetState] = false;
return <a href="#" key={ index } style={{ display: `inline` }} onClick={ this.state[index] ? () => this.handleUnclick(this, TxtBit, targetState) : () => this.handleClick(this, TxtBit, targetState) } name={ TxtBit } className={ this.state[index] ? 'clicked': 'unclicked' } > { " "+TxtBit+ " " }</a>;
} else {
return <div key={ index } style={{ display: `inline` }} className="TxtBit"> { " "+TxtBit+ " " }</div>;
}
}.bind(this));
console.log(this.state);
return(
<div className="content">
<div className="content-padded">
<div> {KeyComponent}</div>
<p> { this.state.CorrectArray.length } / { this.state.keys.length / 2 } </p>
</div>
</div>
);
}
};
Try to bind them:
this.handleUnclick(this, TxtBit, targetState).bind(this)
or use arrow functions on handlers...
example: https://blog.josequinto.com/2016/12/07/react-use-es6-arrow-functions-in-classes-to-avoid-binding-your-methods-with-the-current-this-object/
Regards!

Resources