Dealing with ag grid react and rendering a grid of checkboxes - reactjs

I'm messing with ag-grid, react-apollo, and everything seems to be working fine. The goal here is to click a check box and have a mutation / network request occur modifying some data. The issue i'm having is that it redraws the entire row which can be really slow but im really just trying to update the cell itself so its quick and the user experience is better. One thought i had was to do a optimistic update and just update my cache / utilize my cache. What are some approach you guys have taken.
Both the columns and row data are grabbed via a apollo query.
Heres some code:
CheckboxRenderer
import React, { Component } from "react";
import Checkbox from "#material-ui/core/Checkbox";
import _ from "lodash";
class CheckboxItem extends Component {
constructor(props) {
super(props);
this.state = {
value: false
};
this.handleCheckboxChange = this.handleCheckboxChange.bind(this);
}
componentDidMount() {
this.setDefaultState();
}
setDefaultState() {
const { data, colDef, api } = this.props;
const { externalData } = api;
if (externalData && externalData.length > 0) {
if (_.find(data.roles, _.matchesProperty("name", colDef.headerName))) {
this.setState({
value: true
});
}
}
}
updateGridAssociation(checked) {
const { data, colDef } = this.props;
// const { externalData, entitySpec, fieldSpec } = this.props.api;
// console.log(data);
// console.log(colDef);
if (checked) {
this.props.api.assign(data.id, colDef.id);
return;
}
this.props.api.unassign(data.id, colDef.id);
return;
}
handleCheckboxChange(event) {
const checked = !this.state.value;
this.updateGridAssociation(checked);
this.setState({ value: checked });
}
render() {
return (
<Checkbox
checked={this.state.value}
onChange={this.handleCheckboxChange}
/>
);
}
}
export default CheckboxItem;
Grid itself:
import React, { Component } from "react";
import { graphql, compose } from "react-apollo";
import gql from "graphql-tag";
import Grid from "#material-ui/core/Grid";
import _ from "lodash";
import { AgGridReact } from "ag-grid-react";
import { CheckboxItem } from "../Grid";
import "ag-grid/dist/styles/ag-grid.css";
import "ag-grid/dist/styles/ag-theme-material.css";
class UserRole extends Component {
constructor(props) {
super(props);
this.api = null;
}
generateColumns = roles => {
const columns = [];
const initialColumn = {
headerName: "User Email",
editable: false,
field: "email"
};
columns.push(initialColumn);
_.forEach(roles, role => {
const roleColumn = {
headerName: role.name,
editable: false,
cellRendererFramework: CheckboxItem,
id: role.id,
suppressMenu: true,
suppressSorting: true
};
columns.push(roleColumn);
});
if (this.api.setColumnDefs && roles) {
this.api.setColumnDefs(columns);
}
return columns;
};
onGridReady = params => {
this.api = params.api;
this.columnApi = params.columnApi;
this.api.assign = (userId, roleId) => {
this.props.assignRole({
variables: { userId, roleId },
refetchQueries: () => ["allUserRoles", "isAuthenticated"]
});
};
this.api.unassign = (userId, roleId) => {
this.props.unassignRole({
variables: { userId, roleId },
refetchQueries: () => ["allUserRoles", "isAuthenticated"]
});
};
params.api.sizeColumnsToFit();
};
onGridSizeChanged = params => {
const gridWidth = document.getElementById("grid-wrapper").offsetWidth;
const columnsToShow = [];
const columnsToHide = [];
let totalColsWidth = 0;
const allColumns = params.columnApi.getAllColumns();
for (let i = 0; i < allColumns.length; i++) {
const column = allColumns[i];
totalColsWidth += column.getMinWidth();
if (totalColsWidth > gridWidth) {
columnsToHide.push(column.colId);
} else {
columnsToShow.push(column.colId);
}
}
params.columnApi.setColumnsVisible(columnsToShow, true);
params.columnApi.setColumnsVisible(columnsToHide, false);
params.api.sizeColumnsToFit();
};
onCellValueChanged = params => {};
render() {
console.log(this.props);
const { users, roles } = this.props.userRoles;
if (this.api) {
this.api.setColumnDefs(this.generateColumns(roles));
this.api.sizeColumnsToFit();
this.api.externalData = roles;
this.api.setRowData(_.cloneDeep(users));
}
return (
<Grid
item
xs={12}
sm={12}
className="ag-theme-material"
style={{
height: "80vh",
width: "100vh"
}}
>
<AgGridReact
onGridReady={this.onGridReady}
onGridSizeChanged={this.onGridSizeChanged}
columnDefs={[]}
enableSorting
pagination
paginationAutoPageSize
enableFilter
enableCellChangeFlash
rowData={_.cloneDeep(users)}
deltaRowDataMode={true}
getRowNodeId={data => data.id}
onCellValueChanged={this.onCellValueChanged}
/>
</Grid>
);
}
}
const userRolesQuery = gql`
query allUserRoles {
users {
id
email
roles {
id
name
}
}
roles {
id
name
}
}
`;
const unassignRole = gql`
mutation($userId: String!, $roleId: String!) {
unassignUserRole(userId: $userId, roleId: $roleId) {
id
email
roles {
id
name
}
}
}
`;
const assignRole = gql`
mutation($userId: String!, $roleId: String!) {
assignUserRole(userId: $userId, roleId: $roleId) {
id
email
roles {
id
name
}
}
}
`;
export default compose(
graphql(userRolesQuery, {
name: "userRoles",
options: { fetchPolicy: "cache-and-network" }
}),
graphql(unassignRole, {
name: "unassignRole"
}),
graphql(assignRole, {
name: "assignRole"
})
)(UserRole);

I don't know ag-grid but ... in this case making requests results in entire grid (UserRole component) redraw.
This is normal when you pass actions (to childs) affecting entire parent state (new data arrived in props => redraw).
You can avoid this by shouldComponentUpdate() - f.e. redraw only if rows amount changes.
But there is another problem - you're making optimistic changes (change checkbox state) - what if mutation fails? You have to handle apollo error and force redraw of entire grid - change was local (cell). This can be done f.e. by setting flag (using setState) and additional condition in shouldComponentUpdate.

The best way for me to deal with this was to do a shouldComponentUpdate with network statuses in apollo, which took some digging around to see what was happening:
/**
* Important to understand that we use network statuses given to us by apollo to take over, if either are 4 (refetch) we hack around it by not updating
* IF the statuses are also equal it indicates some sort of refetching is trying to take place
* #param {obj} nextProps [Next props passed into react lifecycle]
* #return {[boolean]} [true if should update, else its false to not]
*/
shouldComponentUpdate = nextProps => {
const prevNetStatus = this.props.userRoles.networkStatus;
const netStatus = nextProps.userRoles.networkStatus;
const error = nextProps.userRoles.networkStatus === 8;
if (error) {
return true;
}
return (
prevNetStatus !== netStatus && prevNetStatus !== 4 && netStatus !== 4
);
};
It basically says if there is a error, just rerender to be accurate (and i think this ok assuming that errors wont happen much but you never know) then I check to see if any of the network statuses are not 4 (refetch) if they are I dont want a rerender, let me do what I want without react interfering at that level. (Like updating a child component).
prevNetStatus !== netStatus
This part of the code is just saying I want the initial load only to cause a UI update. I believe it works from loading -> success as a network status and then if you refetch from success -> refetch -> success or something of that nature.
Essentially I just looked in my props for the query and saw what I could work with.

Related

Problem with Mobx (TS) using with React Functional Components

Here is the problem.
I have simple todo store:
import { makeAutoObservable } from "mobx";
import { Todo } from "./../types";
class Todos {
todoList: Todo[] = [
{ id: 0, description: "Погулять с собакой", completed: false },
{ id: 1, description: "Полить цветы", completed: false },
{ id: 2, description: "Покормить кота", completed: false },
{ id: 3, description: "Помыть посуду", completed: true },
];
// Input: Add Task
taskInput: string = "";
// Filter: query
query: string = "";
// Filter: showOnlyCompletedTasks
showOnlyCompleted: boolean = false;
constructor() {
makeAutoObservable(this);
}
setShowOnlyCompletedState(value: boolean) {
this.showOnlyCompleted = value;
}
changeCompletionState(id: number) {
const task = this.todoList.find((todo) => todo.id === id);
if (task) task.completed = !task.completed;
}
addTask(text: string) {
if (text !== "") {
const newTodo: Todo = {
id: Number(new Date()),
description: text,
completed: false,
};
this.todoList.push(newTodo);
}
}
taskChangeInput(value: string) {
this.taskInput = value;
}
queryChangeInput(value: string) {
this.query = value;
}
}
export default new Todos();
In the app I have some tasks, which I can make completed or not-completed (by clicking on it) and also I do have some filters to filter my todo_list.
Here is the code:
of posts:
import { Todo } from "../types";
import { useMemo } from "react";
function useFilterByQuery (list: Todo[], query: string):Todo[] {
const filteredList = useMemo(()=>{
if (!query) return list
return list.filter(todo => todo.description.toLowerCase().includes(query.toLowerCase()))
}, [list, query])
return filteredList
}
export function useFilterByAllFilters (list:Todo[], query: string, showOnlyCompleted: boolean):Todo[] {
const filteredByQuery = useFilterByQuery(list, query)
const filteredList = useMemo(()=>{
if(!showOnlyCompleted) return filteredByQuery
return filteredByQuery.filter(todo => todo.completed)
}, [filteredByQuery, showOnlyCompleted])
return filteredList
}
So the description of the problem is so: when I choose show me only-Completed-tasks (setting showOnlyCompleted to true), I get expected list of tasks which are all 'completed'.
But, when I change the state of 'todo' right now, the shown list isn't changing (uncompleted task doesn't filter immediatly), it's changing only after I set showOnlyCompleted to false and back to true.
I assume it's not happening, because I don't 'update' the todoList for MobX, which I provide (using function useFilterByAllFilters) by props in My TodoList component.
In my opinion the problem is with the useMemo or with something in Mobx.
Please, help me out.
Yes, it's because of useMemo. useFilterByQuery only has [list, query] deps, but when you change some todo object to be (un)completed you don't really change the whole list, only one object inside of it. And that is why this useMemo does not rerun/recompute, so you still have the old list. Same with useFilterByAllFilters.
What you are doing is not really idiomatic MobX I would say! MobX is designed to work with mutable data and React hooks are designed to work with immutable data, so to make it work with hooks you need to do some workarounds, but it would be easier to just rewrite it like that:
class Todos {
// ... your other code
// Add computed getter property to calculate filtered list
get listByQuery() {
if (!this.query) return list
return this.todoList.filter(todo => todo.description.toLowerCase().includes(this.query.toLowerCase()))
}
// Another one for all filters
get listByAllFilters() {
if(!this.showOnlyCompleted) return this.listByQuery
return this.listByQuery.filter(todo => todo.completed)
}
}
And just use this two computed properties in your React components, that's it! No need for hooks. And these properties are cached/optimized, i.e. will only run when some of their observables change.
More info about computeds

How to access dragged object fields in react drag and drop?

So basically I'm trying to use dnd in a simple game, but i feel like i don't quite understand the point of some params. I have two components being used as a source and target. The source is receiving a unit prop from the parent and i'd like the Field component(target) to know which unit is being dragged atm and after the drop display it inside it. I've seen i can specify some fields in beginDrag(), but i think it may not be it. Is is possible or is there a workaround?
const BenchTileSource = {
beginDrag(props) {
// Return the data describing the dragged item
const item = { unit: props.unit };
return item;
}
};
const TileBackground = styled.div`
...
`;
function UnitBenchTile({ unit }) {
const [{ isDragging }, drag] = useDrag({
item: { type: TileTypes.BENCH_TILE },
collect: monitor => ({
isDragging: !!monitor.isDragging()
})
});
return unit ? (
<TileBackground ref={drag}>{unit.name}</TileBackground>
) : (
<TileBackground />
);
}
export default BenchTileSource(
TileTypes.BENCH_TILE,
BenchTileSource
)(UnitBenchTile);
import React from "react";
import styled from "styled-components";
import { connect } from "react-redux";
import { TileTypes } from "../dragTypes/TileTypes";
import { useDrop } from "react-dnd";
const mapStateToProps = function(state) {
return {
currentPosition: state.currentPosition,
unitsPositions: state.unitsPositions
};
};
...
function Field({ index, children, dispatch }) {
const unitsPositions = useSelector(
state => state.unitsPositionsReducer.unitsPositions
);
const [{ isOver }, drop] = useDrop({
accept: TileTypes.BENCH_TILE,
drop: monitor => setCurrentPosition([x, y], monitor.getItem()),
collect: monitor => ({
isOver: !!monitor.isOver(),
item: monitor.item
})
});
const setCurrentPosition = (newPosition, unit) => {
if (!unitsPositions[index].unit) {
const result = unitsPositions;
result[index] = { position: newPosition, unit: unit };
dispatch({ type: "UPDATE_UNITS_POSITIONS", result });
}
};
const isRight = (index - 9) % 10 === 0;
const isBottom = index >= 50;
const x = index % 10;
const y = Math.floor(index / 6);
if (isRight && isBottom)
return <FrameBottomRight ref={drop}>{children}</FrameBottomRight>;
else if (isRight) return <FrameRight ref={drop}>{children}</FrameRight>;
else if (isBottom) return <FrameBottom ref={drop}>{children}</FrameBottom>;
else return <Frame ref={drop}>{children}</Frame>;
}
export default connect(mapStateToProps)(Field);
All the information that is needed can be accessed by the monitors of react-dnd. In this case, you want to access the currently dragged source in the target so you have to make use of the DropTargetMonitor's method getItem() and it will return all the properties defined in the item property of the dragged source. No need to supply additional fields when a drag operation begins if you already have an id defined in your item, that field is enough to identify which item is being dragged.
UPDATE
if you really want to pass additional fields, you can simply merge the item data by "spreading" it to a new object together with your additional data in your begin method.
return {
...monitor.item,
additional: data
}

React: An component attribute is not properly storing the data (from a query) that I want

I recently started learning react and I have encountered something that I do not understand. So when I declare a component I also declare an attribute in the constructor. Then, after executing the first query (I am using Apollo client - GraphQL ) I want to store the result (which I know that will be always an email) in the attribute declared so I can use it as a parameter in the second query.
The app logic is that I want to show all the orders of a given email, but first I get the email with a query.
Here is the code:
export default class Orders extends Component {
constructor(){
super();
this.email = '';
}
render() {
return (
<div>
<Query query = { GET_MAIL_QUERY }>
{({data, loading}) => {
if (loading) return "Loading...";
this.email = data.me.email;
return <h1>{this.email}</h1>
}}
At this point a header containing the email is returned, so all good. But when I execute the second query (or try to display the email in the second header for that matter) it seems that the value is not properly stored.
</Query>
<h1>{this.email}</h1>
<Query query = { GET_ORDERS_QUERY }
variables = {{
email: this.email
}}>
{({data, loading}) => {
if (loading) return "Loading...";
console.log(data);
let orders = data.ordersByEmail.data;
console.log(orders);
return orders.map(order =>
<div>
<h1>{order.date}</h1>
<h1>{order.price}</h1>
<h1>{order.conference.conferenceName}</h1>
<h1>{order.user.email}</h1>
<br></br>
</div>)
}}
</Query>
</div>
)
}
}
const GET_MAIL_QUERY = gql`
query getMyMail{
me{
email
}
}
`;
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference{
conferenceName
}
}
}
}
`;
I would love an explanation for this and maybe a solution (to store a value returned from a query to use it in another)
Thanks in anticipation :)
In my experience, you should use useQuery imported from #apollo/react-hooks with functional component because it's easy to use, it makes your code more cleaner
If your want to use <Query/> component with class component, it's ok. But, if you want to store data received from server, you should create a variable in state of constructor and when you want to update to state, you should use this.setState({email: data.me.email}). Don't use this.state.email = data.me.email, it's anti-pattern, React will not trigger re-render when you use it to update your state.
This is the code:
import React, { useState } from 'react'
import gql from 'graphql-tag'
import { useQuery, useMutation } from '#apollo/react-hooks'
const GET_MAIL_QUERY = gql`
query getMyMail {
me {
email
}
}
`
const GET_ORDERS_QUERY = gql`
query getOrdersByEmail($email: String!) {
ordersByEmail(email: $email) {
data {
gid
date
price
user {
email
}
conference {
conferenceName
}
}
}
}
`
const Orders = () => {
const [email, setEmail] = useState('')
const { data: getMailQueryData, loading, error } = useQuery(GET_MAIL_QUERY, {
onCompleted: data => {
setEmail(data.me.email)
},
onError: err => alert(err),
})
const { data: getOrdersQueryData } = useQuery(GET_ORDERS_QUERY, {
variables: { email: email },
})
if (loading) return <div>Loading...</div>
if (error) return <div>Error...</div>
return ...
}

Why does react Apollo on update subscription returns the already updated value as prev?

I'm using apollo for a react project and with subscription on the creation, update and deletion of object. I use the subscribeToMore functionality to start the subscriptions. The prev value of the updateQuery callback is the correct value for the creation and deletion subscription. But for the update subscription the prev value already contains the updated value. Overall this is really nice, as I don't need to add my custom implementation on how to update the object and can just return prev, but I don't understand why this happens. From my understanding the previous value should be returned. Is this just backed in functionality of apollo or is this some weird bug?
Here is the component which implements the subscriptions:
import { useEffect } from 'react';
import { useQuery } from '#apollo/react-hooks';
import * as Entities from './entities';
import * as Queries from './queries';
interface IAppointmentData {
appointments: Entities.IAppointment[];
error: boolean;
loading: boolean;
}
function getAppointmentsFromData(data) {
return (data && data.appointments) || [];
}
export function useAllAppointments(): IAppointmentData {
const initialResult = useQuery(Queries.GET_APPOINTMENTS);
const { data, error, loading, subscribeToMore } = initialResult;
useEffect(() => {
const unsubscribeNewAppointments = subscribeToMore({
document: Queries.NEW_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const { newAppointment } = subscriptionData.data;
return Object.assign({}, prev, {
appointments: [...prev.appointments, newAppointment],
});
},
});
const unsubscribeUpdateAppointment = subscribeToMore({
document: Queries.UPDATE_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
return prev
},
});
const unsubscribeDeleteAppointments = subscribeToMore({
document: Queries.DELETE_APPOINTMENTS_SUB,
variables: {},
updateQuery: (prev, { subscriptionData }) => {
if (!subscriptionData.data) {
return prev;
}
const { deleteAppointment: {
id: deletedAppointmentId
} } = subscriptionData.data;
return Object.assign({}, prev, {
appointments: prev.appointments.filter(item => item.id !== deletedAppointmentId),
});
},
});
return function unsubscribe() {
unsubscribeNewAppointments()
unsubscribeDeleteAppointments()
unsubscribeUpdateAppointment()
}
}, [subscribeToMore]);
return {
appointments: getAppointmentsFromData(data),
error: !!error,
loading,
};
}
And these are my graphql queries / subscriptions:
import { gql } from 'apollo-boost';
export const GET_APPOINTMENTS = gql`
{
appointments {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const NEW_APPOINTMENTS_SUB = gql`
subscription newAppointment {
newAppointment {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const UPDATE_APPOINTMENTS_SUB = gql`
subscription updateAppointment {
updateAppointment {
id
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
companions {
id
user {
id
firstName
lastName
}
}
time {
plannedStart
plannedEnd
}
type
}
}
`;
export const DELETE_APPOINTMENTS_SUB = gql`
subscription deleteAppointment {
deleteAppointment {
id
}
}
`;
According to the official Apollo documentation
Note that the updateQuery callback must return an object of the same
shape as the initial query data, otherwise the new data won't be
merged.
After playing around for a bit it seems that if the id you are returning is the same as in your cache and the object has the same shape, the update happens automatically.
But if you actually follow the CommentsPage example in the link I provided you will see that the id that is returned is new, that is why they explicity assign the object with the new data.
if (!subscriptionData.data) return prev;
const newFeedItem = subscriptionData.data.commentAdded;
return Object.assign({}, prev, {
entry: {
comments: [newFeedItem, ...prev.entry.comments]
}
});
Ive tested this with my own message app Im working on, when receiving new messages with new id I have to return the merged data myself. But when im updating the chatRooms to display which chatRoom should be at the top of my screen (which one has the newest message), then my update happens automatically and I just return prev.
If however you want a work around to explicitly check the data before updating it you could try this workaround I found on GitHub. You will just need to use a reverse lookup id or just a different variable other than id.
This is what I would do in your example to achieve this:
updateAppointment {
// was id
idUpdateAppointment // im sure updateAppointmentId should also work... i think
responsibleCustomer {
id
user {
id
firstName
lastName
}
}
}

Get filtred data, react-bootstrap-table2

Is there any global table option that return the filtred rows? Ignore pagination. All rows matching one or several textFilter?
I need a value in the header showin the average value of the filtred data.
I don't find any on https://react-bootstrap-table.github.io/react-bootstrap-table2/docs/table-props.html
There is the onDataSizeChange, but it only gives the prop dataSize (nr of rows), also only available when pagination is not used.
Update to second question in comments:
class App extends Component<IProps, IState> {
constructor(props: IProps) {
super(props);
this.state = {
data: [...]
filtredData: null
};
};
const factory = patchFilterFactory(filterFactory, (filteredData) => {
this.setState({filteredData}); // causes maximum update exceeded..
});
render() {
return (
<div>
<BootstrapTable
keyField='id'
striped
hover
bootstrap4
data={anbuds}
filter={factory()}
columns={columns}/>
</div>
);
}
}
Kinda.
One way you could do that is by providing a different implementation of the filter prop, and get the data that you need there.
import BootstrapTable from "react-bootstrap-table-next";
import filterFactory, { textFilter } from "react-bootstrap-table2-filter";
function patchFilterFactory(filterFactory, onFilteredData) {
return (...args) => {
const { createContext, options } = filterFactory(...args)
return {
createContext: (...args) => {
const { Provider: BaseProvider, Consumer } = createContext(...args)
const Provider = class FilterProvider extends BaseProvider {
componentDidUpdate() {
onFilteredData(this.data)
}
}
return { Provider, Consumer }
},
options
}
}
}
patchFilterFactory will just sit in between the original filter provider and your code, allowing you to get the data that you need.
How to use it:
function Table() {
const columns = [
{
dataField: "id",
text: "Product ID"
},
{
dataField: "name",
text: "Product Name",
filter: textFilter({
delay: 0
})
},
{
dataField: "price",
text: "Product Price",
filter: textFilter({
delay: 0
})
}
];
const factory = patchFilterFactory(filterFactory, (filteredData) => {
console.log('on filter data', filteredData)
})
return (
<BootstrapTable
keyField="id"
data={props.products}
columns={columns}
filter={factory()}
/>
);
}
I agree, that's far from ideal, but as far as I was able to assess, it may be the only way at the moment.
If you want to change state in the same component, I would recommend:
const [filteredData, setFilteredData] = React.useState([])
const factory = patchFilterFactory(filterFactory, data => {
setFilteredData(prevData => {
if (JSON.stringify(prevData) !== JSON.stringify(data)) {
return data
}
return prevData
})
})
My 2¢: after some investigation (and intentionally avoiding implementation of filter factory wrapper suggested by #federkun) I found out I can access current filter context of rendered table.
In order to access table properties, I had to add React Ref:
class MyDataTable extends React.Component {
constructor(props) {
super(props);
this.state = {
data: props.data
};
this.node = React.createRef();
}
...
render() {
return (
<Card>
<CardBody>
<BootstrapTable
ref={n => this.node = n}
keyField="id"
data={this.state.data}
...
/>
<Button name="click-me" onClick={() => this.handleClick()}>Click me!</Button>
</CardBody>
</Card>
)
}
}
Now when it is possible to reference <BootstrapTable> from code using this.node, I can get to all filtered data (without paging):
// member of MyDataTable component
handleClick() {
console.log(this.node.filterContext.data);
}
Please note that if you access data this way, entries won't be sorted as you see it in the table, so if you want to go really crazy, you can get data filtered and sorted this way:
// member of MyDataTable component
handleClick() {
const table = this.node;
const currentDataView =
(table.paginationContext && table.paginationContext.props.data)
|| (table.sortContext && table.sortContext.props.data) // <- .props.data (!)
|| (table.filterContext && table.filterContext.data) // <- .data (!)
|| this.state.data; // <- fallback
console.log(currentDataView);
}
... however this is becoming pretty wild. Important thing here is to start with paginationContext - anything before it is already sliced into pages. You can check how contexts are put together here: react-bootstrap-table2/src/contexts/index.js.
Nevertheless, this approach is hacky - completely avoiding public API, intercepting context pipeline, reading inputs for each layer. Things may change in newer releases, or there may be issue with this approach I haven't discovered yet - just be aware of that.

Resources