React JS datatable pagination

Imagem

Fala pessoal! nesse post vamos mostrar um exemplo de paginação em react Js de uma forma não tão simples mas bastante reaproveitável. 

Não ficou tão simples por que estamos utilizando Redux e Redux form. Estou utilizando o template bootstrap adm LTE mas como instalei a dependencia pura, css e js, esse componente pode ser adaptado para qualquer tipo de template.

no package.json temos as dependencias do admin-tle, axios e as dependências react, redux, react-redux e redux-form

 "dependencies": {
    "@testing-library/jest-dom": "^4.2.4",
    "@testing-library/react": "^9.5.0",
    "@testing-library/user-event": "^7.2.1",
    "admin-lte": "2.3.6",
    "axios": "^0.20.0",
    "font-awesome": "^4.7.0",
    "multiselect-react-dropdown": "^1.6.1",
    "prop-types": "^15.7.2",
    "react": "^16.13.1",
    "react-confirm-alert": "^2.6.2",
    "react-dom": "^16.13.1",
    "react-redux": "^7.2.1",
    "react-redux-toastr": "^7.6.5",
    "react-router-dom": "^5.2.0",
    "react-scripts": "3.4.3",
    "redux": "^4.0.5",
    "redux-form": "^8.3.6",
    "redux-multi": "^0.1.12",
    "redux-promise": "^0.6.0",
    "redux-thunk": "^2.3.0",
    "styled-components": "^5.2.0"
  }

Resolvi usar o componente tabs do admin LTE para fazer a troca de exibição dos compomentes entre listagem, formulário create e form update. Organizei as páginas na pasta Pages e dentro da pasta Pages/User temos o arquivo index.js que comporta o componente de abas e faz a transição entre os componentes do CRUD de usuários:

import React, { Component } from "react"
import Tabs from '../../common/template/tab/tabs'
import TabsContent from '../../common/template/tab/tabsContent'
import TabContent from '../../common/template/tab/tabContent'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import UserList from './userList'
import {init,showCreate,showUpdate,create,update,
        setSelectedPrivileges,setSelectedRoles,
        setMultiselectPrivileges,setMultiselectRoles} from './userActions'
import Form from './userForm'
import {setLoading,getList,setTotalPages,setCurrentPage,setOrderBy,setPaginationParams} from '../../common/template/pagination/paginationActions'
import { initialize  } from 'redux-form'

class User extends Component {
    
    componentDidMount() {
        this.props.init(this.props)
        initialize('UserFormList',this.props.paginationParams)
    }

    _selectTab(e){
        e.preventDefault()
        this.props.selectTab(this.props.target)
    }

    search(values){

        this.props.getList(this.props,this.props.currentPage,values);
    }

    render() {
        return (
            <Tabs> 
                <TabsContent>
                    <TabContent id='tabList'>
                        <UserList onSubmit={this.search.bind(this)}
                                  setPaginationParams={this.props.setPaginationParams.bind(this)} />
                    </TabContent>
                    <TabContent id='tabCreate'>
                        <Form onSubmit={this.props.create.bind(this,this.props)}
                              loading={this.props.loading}
                              history={this.props.history}
                              setLoading={this.props.setLoading.bind(this)}
                              setSelectedRoles={this.props.setSelectedRoles.bind(this)}
                              setSelectedPrivileges={this.props.setSelectedPrivileges.bind(this)}
                              multiselectRoles={this.props.multiselectRoles}
                              multiselectPrivileges={this.props.multiselectPrivileges}
                              setMultiselectPrivileges={this.props.setMultiselectPrivileges.bind(this)}
                              setMultiselectRoles={this.props.setMultiselectRoles.bind(this)}
                              submitLabel='Create'
                              submitIcon='plus'
                              submitClass='success' />
                    </TabContent>
                    <TabContent id='tabUpdate'>
                        <Form onSubmit={this.props.update.bind(this,this.props)}
                              loading={this.props.loading}
                              history={this.props.history}
                              setLoading={this.props.setLoading.bind(this)}
                              setSelectedRoles={this.props.setSelectedRoles.bind(this)}
                              setSelectedPrivileges={this.props.setSelectedPrivileges.bind(this)}
                              multiselectRoles={this.props.multiselectRoles}
                              multiselectPrivileges={this.props.multiselectPrivileges}
                              setMultiselectPrivileges={this.props.setMultiselectPrivileges.bind(this)}
                              setMultiselectRoles={this.props.setMultiselectRoles.bind(this)}
                              submitLabel='Update'
                              submitIcon='pencil'
                              submitClass='warning' />
                    </TabContent>
                </TabsContent> 
            </Tabs>
        );
    }
}

const mapDispatchToProps = dispatch => bindActionCreators({
                                                init,
                                                showCreate,
                                                showUpdate,
                                                create,
                                                update,
                                                setLoading,
                                                setTotalPages,
                                                setCurrentPage,
                                                setOrderBy,
                                                setSelectedPrivileges,
                                                setSelectedRoles,
                                                getList,
                                                setPaginationParams,
                                                setMultiselectPrivileges,
                                                setMultiselectRoles 
                                                }, 
                                                dispatch)

const mapStateToProps = state => ({endpoint:'/user/page',
                                   list: state.paginationReducer.list,
                                   paginationParams: state.paginationReducer.paginationParams,
                                   currentPage:state.paginationReducer.currentPage,
                                   totalPages:state.paginationReducer.totalPages,
                                   loading:state.paginationReducer.loading,
                                   selectedRoles:state.userReducer.selectedRoles,
                                   selectedPrivileges:state.userReducer.selectedPrivileges,
                                   multiselectRoles: state.userReducer.multiselectRoles,
                                   multiselectPrivileges: state.userReducer.multiselectPrivileges})

export default connect(mapStateToProps, mapDispatchToProps)(User)

 Todos os componemtes de paginação estão em common/template/pagination mas cada CRUD tem um componemte para exibir a listagem de forma diferente. No caso do CRUD de usuário temos o componente UserList que é chamado na primeira aba

import React, { Component } from "react";
import { withRouter } from "react-router-dom"
import Content from '../../common/template/content'
import PaginationButtons from '../../common/template/pagination/paginationButtons'
import {renderArrowDirection,toggleOrderBy} from '../../common/template/pagination/paginationUtils'
import {getList,setTotalPages,setLoading,
        setCurrentPage,setOrderBy} from '../../common/template/pagination/paginationActions'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import Row from '../../common/template/gridRow'
import Col from '../../common/template/gridCol'

import 'react-confirm-alert/src/react-confirm-alert.css'
import {remove,showUpdate,showCreate} from './userActions'
import { selectTab } from '../../common/template/tab/tabActions'
import { reduxForm, Field } from 'redux-form'
import GridAndInput from './../../common/form/GridAndInput'
import { getEmail } from "../../services/auth";
import { toastr } from "react-redux-toastr";
import {confirmAlertCustom} from '../../common/template/confirmAlert'
import {formValueSelector} from 'redux-form'

class UserList extends Component {
    
    componentDidMount(){
        this.props.getList(this.props,0,this.props.veluesFormSearch);
    }

    confirmDelete(id,email){
        
        if(email === getEmail()){
            toastr.error("Error","you can't delete yourself!");
            return false;
        }

        confirmAlertCustom(this.deleteIt.bind(this,id),
                           'You want to delete this record?',
                           'Yes, Delete it!');
    }

    deleteIt(id,closeConfirm){
        this.props.remove(this.props,id);
        closeConfirm();
    }

    renderRows() {

        const list = this.props.list || []
        if(!list.content){
            return  (<tr key='1'>
                        <td colSpan='4'>
                            no results
                        </td>
                    </tr>)
        }
        
        return list.content.map(row => (
            <tr key={row.id}>
                <td onClick={this.props.showUpdate.bind(this,row.id,this.props)}>
                    {(row != null ? row.id : '')}
                </td>
                <td onClick={this.props.showUpdate.bind(this,row.id,this.props)}>
                    {row != null ? row.name : ''}
                </td>
                <td onClick={this.props.showUpdate.bind(this,row.id,this.props)}>
                    {row != null ? row.email : ''}
                </td>
                <td>
                <button className='btn btn-warning btn-flat btn-xs' onClick={this.props.showUpdate.bind(this,row.id,this.props)}>
                    <i className='fa fa-pencil'></i>
                </button>    
                <button className='btn btn-danger btn-flat btn-xs' onClick={this.confirmDelete.bind(this,row.id,row.email)}>
                        <i className='fa fa-trash-o'></i>
                    </button>
                </td>
            </tr>
        ))
    }

    render() {
        const { handleSubmit } = this.props
        return (
            <Content title='User' loading={this.props.loading}>
                    <form onSubmit={handleSubmit.bind(this)}>
                        <Row>
                            <Field name='name' component={GridAndInput} 
                            label='Nome' cols='12 4' placeholder='name' />
                            <Field name='email' component={GridAndInput} 
                            label='Email' type='email' cols='12 4' placeholder='e-mail' />

                            <Col cols='6 2'>
                                <button type='submit' className='btn btn-primary btn-flat'>
                                <i className='fa fa-search'/> Search
                                </button>
                            </Col>
                            <Col cols='6 2'>
                                <button type='button'
                                        onClick={this.props.showCreate.bind(this)}
                                        className='btn btn-success btn-flat'>
                                    <i className='fa fa-plus'/> Create 
                                </button>
                            </Col>
                        </Row>
                    </form>
                    <table className='table table-bordered table-hover'>
                        <thead>
                            <tr>
                                <th onClick={toggleOrderBy.bind(this,this.props,"id")}>
                                    id {renderArrowDirection(this.props,'id')}
                                </th>
                                <th onClick={toggleOrderBy.bind(this,this.props,"name")}>
                                    name {renderArrowDirection(this.props,'name')}
                                </th>
                                <th onClick={toggleOrderBy.bind(this,this.props,"email")}>
                                    email {renderArrowDirection(this.props,'email')}
                                </th>
                                <th className='table-actions'>Actions</th>
                            </tr>
                        </thead>
                        <tbody>
                            {this.renderRows()}
                        </tbody>
                    </table>
                    <PaginationButtons 
                        endpoint={this.props.endpoint}
                        listActionType={this.props.listActionType}
                        paginationParams={this.props.paginationParams}
                        setPaginationParams={this.props.setPaginationParams}
                    />
            </Content>
        );
    }
}

const selector = formValueSelector('UserFormList')
const mapStateToProps = state => ({endpoint:'/user/page',
                                   list: state.paginationReducer.list,
                                   paginationParams: state.paginationReducer.paginationParams,
                                   currentPage:state.paginationReducer.currentPage,
                                   totalPages:state.paginationReducer.totalPages,
                                   loading:state.paginationReducer.loading,
                                   veluesFormSearch:selector(state, 'name','email')})

const mapDispatchToProps = dispatch => bindActionCreators({getList,
                            setLoading,
                            setTotalPages,
                            setCurrentPage,
                            setOrderBy,
                            remove,
                            showUpdate,
                            showCreate,    
                            selectTab}, dispatch)

UserList = reduxForm({ form: 'UserFormList', destroyOnUnmount: false })(UserList)

const ShowTheLocationWithRouter = withRouter(UserList)
export default connect(mapStateToProps, mapDispatchToProps)(ShowTheLocationWithRouter)

O state endpoint é passado diretamente em cada componente de listagem e propagado para os subcomponentes. A action que fica em src/common/template/pagination/PaginationAction.js é reaproveitada em todas as outras páginas. Aumentando o nível de reaproveitamento de componente.

O componente form também é reaproveitado tanta para inserir como para atualizar os registros. E em src/common/template/pagination/paginationUtils.js temos métodos utilitários para encapsular métodos de ordenação das colunas. 

Tanto a ordenação como a paginação na consulta são feitas pelo backend (Spring boot) 

Repositório frontend https://github.com/davifelipems/react-js-frontend-template/tree/master/src/common/template/pagination

Repositório backend https://github.com/davifelipems/spring-backend-template

 

Comentários

 

Quem Sou

Graduado em ADS (Análise e desenvolvimento de sistemas).

Não sou "devoto" de nenhuma linguagem de programação. Procuro aproveitar o melhor de cada uma de acordo com a necessidade do projeto. Prezo por uma arquitetura bem feita, código limpo, puro e simples!