Я возился с ag-grid, react-apollo, и вроде все работает нормально. Цель состоит в том, чтобы установить флажок и получить запрос на мутацию / сетевой запрос, изменяющий некоторые данные. Проблема, с которой я столкнулся, заключается в том, что он перерисовывает всю строку, что может быть очень медленным, но на самом деле я просто пытаюсь обновить саму ячейку, чтобы она была быстрой и удобной для пользователя. Одна мысль, которая у меня была, заключалась в том, чтобы сделать оптимистичное обновление и просто обновить мой кеш / использовать мой кеш. Какой у вас подход, ребята?
Данные столбцов и строк захватываются с помощью запроса apollo.
Вот код:
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.info(data);
// console.info(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;
Сама сетка:
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.info(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);





Я не знаю ag-grid, но ... в данном случае выполнение запросов приводит к перерисовке всей сетки (компонент UserRole).
Это нормально, когда вы передаете действия (дочерним элементам), влияющие на все родительское состояние (новые данные поступили в props => перерисовать).
Вы можете избежать этого с помощью shouldComponentUpdate () - ф.э. перерисовывать только при изменении количества строк.
Но есть еще одна проблема - вы вносите оптимистичные изменения (измените состояние флажка) - что, если мутация не удалась? Вы должны обработать ошибку apollo и принудительно перерисовать всю сетку - изменение было локальным (ячейка). Это можно сделать, например. установив флаг (используя setState) и дополнительное условие в shouldComponentUpdate.
Это путь, по которому я в итоге пошел, я принудительно выполняю повторную визуализацию при ошибке, потому что мне нужна точность.
Для меня лучший способ справиться с этим - выполнить shouldComponentUpdate со статусами сети в apollo, что потребовало некоторого времени, чтобы увидеть, что происходит:
/**
* 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
);
};
В основном он говорит, что если есть ошибка, просто повторите рендеринг, чтобы быть точным (и я думаю, что это нормально, если предположить, что ошибок не будет много, но вы никогда не узнаете), тогда я проверяю, не равен ли какой-либо из сетевых статусов 4 (повторная загрузка), если они Я не хочу повторного рендеринга, позвольте мне делать то, что я хочу, не вмешиваясь на этом уровне. (Как обновление дочернего компонента).
prevNetStatus !== netStatus
Эта часть кода просто говорит, что я хочу, чтобы начальная загрузка вызывала только обновление пользовательского интерфейса. Я считаю, что это работает от загрузки -> успеха в качестве статуса сети, а затем, если вы выполняете повторную загрузку из успеха -> повторно загружаете -> успех или что-то в этом роде.
По сути, я просто заглянул в свой реквизит в поисках запроса и увидел, с чем я могу работать.
После первоначального рендеринга вам нужно синхронизировать состояние сетки по внешним событиям, то есть кто-то обновил эти значения из другого места или новые значения поступили из бэкэнда?