Reputation: 11
I'm trying to implement apollo to feed my customer's table component.
import CustomersTable from 'components/Customer/CustomersTable';
This table have to be filterable, sortable and paginated. I have 200.000 customers in the MySQL table. That's why filters, sorts and pagination are compute on server side. I need to query separately customers total count for pagination, and the customers list.
import GET_CUSTOMERS_PAGINATED_QUERY from './getCustomersPaginated.graphql';
import GET_CUSTOMERS_PAGINATED_COUNT_QUERY from './getCustomersPaginatedCount.graphql';
Unexpectedly, when the filtersInput are changed the refetch function is called twice. The first time with the correct new variables, and the second with initial variables. So the total count of customers is overwritten.
const initialFilters = {
filterId: null,
filterSiren: null,
filterName: null,
filterEmail: null,
};
const getCustomersPaginatedCountOptions = {
name: 'customersPaginatedCount',
options() {
return {
variables: {
...initialFilters,
},
fetchPolicy: 'network-only',
};
},
props({ customersPaginatedCount }) {
return {
customersPaginatedCount: customersPaginatedCount,
};
},
};
const getCustomersPaginatedOptions = {
name: 'customersPaginated',
options({ offset, limit }) {
return {
variables: {
offset: offset,
limit: limit,
...initialFilters,
},
fetchPolicy: 'network-only',
};
},
props({ customersPaginated }) {
return {
customersPaginated: customersPaginated,
};
},
};
These two queries are composed as advices here (for no errors):
@compose(
graphql(GET_CUSTOMERS_PAGINATED_QUERY, getCustomersPaginatedOptions),
graphql(GET_CUSTOMERS_PAGINATED_COUNT_QUERY, getCustomersPaginatedCountOptions),
)
export default class CustomersTableContainer extends React.PureComponent {
state = {
offset: this.props.offset,
limit: this.props.limit,
pageSize: 10,
currentPage: 0,
filters: initialFilters,
currentOnFilterChangeTimeoutID: null,
};
constructor(props) {
super(props);
this.onCurrentPageChange = this.onCurrentPageChange.bind(this);
this.onFiltersChange = this.onFiltersChange.bind(this);
}
onCurrentPageChange(newPage) {
const { customersPaginated } = this.props;
const { limit, filters } = this.state;
customersPaginated.refetch({
offset: newPage * limit,
...filters,
});
this.setState({ currentPage: newPage });
}
onFiltersChange(args) {
const { customersPaginated, customersPaginatedCount } = this.props;
const { limit } = this.state;
const newFilters = Object.assign({}, initialFilters);
for ( const i in args ) {
newFilters['filter' + ucfirst(args[i].columnName)] = args[i].value;
}
customersPaginated.refetch({
offset: 0 * limit,
...newFilters,
});
// --- >> THE REFETCH FUNCTION IS TRIGGERED TWICE HERE ! << ---
customersPaginatedCount.refetch({
...newFilters,
});
// here 'test' is displayed once, so onFiltersChange is called once too as expected
console.log('test');
this.setState({
currentPage: 0,
filters: newFilters,
});
}
render () {
const { customersPaginated, customersPaginatedCount } = this.props;
const { currentPage, pageSize } = this.state;
if (customersPaginated.error) console.error( customersPaginated.error );
if (customersPaginatedCount.error) console.error( customersPaginatedCount.error );
return (
<div>
{(customersPaginated.error || customersPaginatedCount.error) && (
<Typography color="error" gutterBottom>
Une erreur est survenue.
</Typography>
)}
<div>
<CustomersTable
customers={customersPaginated.customersPaginated}
currentPage={currentPage}
onCurrentPageChange={this.onCurrentPageChange}
onFiltersChange={this.onFiltersChange}
pageSize={pageSize}
totalCount={customersPaginatedCount.customersPaginatedCount || 0}
/>
{(customersPaginated.loading || customersPaginatedCount.loading) && <Loading />}
</div>
</div>
);
}
static propTypes = {
customersPaginated: PropTypes.object.isRequired,
customersPaginatedCount: PropTypes.object.isRequired,
offset: PropTypes.number.isRequired,
limit: PropTypes.number.isRequired,
};
}
My console logs on component load in an expected behavior :
{variables: {filterId: null, filterSiren: null, filterName: null, filterEmail: null}, operationName: "getCustomersPaginatedCount"
{variables: {filterId: null, filterSiren: null, filterName: null, filterEmail: null}, operationName: "getCustomersPaginated"
My console logs on a filter input change in an unexpected behavior :
{variables: {filterId: null, filterSiren: null, filterName: "example of customer name", filterEmail: null}, operationName: "getCustomersPaginated"
{variables: {filterId: null, filterSiren: null, filterName: "example of customer name", filterEmail: null}, operationName: "getCustomersPaginatedCount"
{variables: {filterId: null, filterSiren: null, filterName: null, filterEmail: null}, operationName: "getCustomersPaginatedCount"
getCustomersPaginated.graphql :
query getCustomersPaginated(
$filterId: Int,
$filterSiren: String,
$filterName: String,
$filterEmail: String,
$offset: Int,
$limit: Int
) {
customersPaginated(
filterId: $filterId,
filterSiren: $filterSiren,
filterName: $filterName,
filterEmail: $filterEmail,
offset: $offset,
limit: $limit
) {
id
name
siren
email
activity {
id
name
shortName
code
}
salesFollower {
id
username
firstname
lastname
email
initials
enabled
}
customerGroup {
id
name
code
enabled
}
coreBusiness {
id
name
codeApe
codeNaf
}
}
}
getCustomersPaginatedCount.graphql :
query getCustomersPaginatedCount(
$filterId: Int,
$filterSiren: String,
$filterName: String,
$filterEmail: String
) {
customersPaginatedCount(
filterId: $filterId,
filterSiren: $filterSiren,
filterName: $filterName,
filterEmail: $filterEmail,
)
}
My environnement :
Front : reactjs with react-apollo
Back : PHP 7 with Symfony3 and Youshido\GraphQLBundle
I begun react this year and apollo this month. Maybe I'm not using refetch like I should, maybe there is a better way, or maybe there is a bug (I updated apollo-client-preset from 1.0.2 to 1.0.3 without seeing any changes). Maybe there's a solution on Youshido's side to be able to fetch the customers's list and customers's count in one query.
Thanks for your help.
Upvotes: 0
Views: 246
Reputation: 11
In some situations the refetch function is not necessary. Thanks to @stelmakh for his help on this issue !!
My new code : Child :
import React from 'react';
import PropTypes from 'prop-types';
import { compose, graphql } from 'react-apollo';
import { ucfirst } from 'utils/string';
import CustomersTable from 'components/Customer/CustomersTable';
import Typography from 'material-ui/Typography';
import Loading from 'components/Loading/Loading';
import GET_CUSTOMERS_PAGINATED_QUERY from './getCustomersPaginated.graphql';
import GET_CUSTOMERS_PAGINATED_COUNT_QUERY from './getCustomersPaginatedCount.graphql';
const getCustomersPaginatedCountOptions = {
name: 'customersPaginatedCount',
options({ variables }) {
return {
variables: variables,
fetchPolicy: 'network-only',
};
},
props({ customersPaginatedCount }) {
return { customersPaginatedCount: customersPaginatedCount };
},
};
const getCustomersPaginatedOptions = {
name: 'customersPaginated',
options({ variables }) {
return {
variables: variables,
fetchPolicy: 'network-only',
};
},
props({ customersPaginated }) {
return { customersPaginated: customersPaginated };
},
};
@compose(
graphql(GET_CUSTOMERS_PAGINATED_QUERY, getCustomersPaginatedOptions),
graphql(GET_CUSTOMERS_PAGINATED_COUNT_QUERY, getCustomersPaginatedCountOptions),
)
export default class CustomersTableContainer extends React.PureComponent {
state = {
currentOnFilterChangeTimeoutID: null,
};
constructor(props) {
super(props);
this.onCurrentPageChange = this.onCurrentPageChange.bind(this);
this.onSortingChange = this.onSortingChange.bind(this);
this.onFiltersChange = this.onFiltersChange.bind(this);
}
onCurrentPageChange(newPage) {
const { onChange, variables } = this.props;
onChange({
currentPage: newPage,
'offset': newPage * variables.limit,
});
}
onFiltersChange(args) {
clearTimeout(this.state.currentOnFilterChangeTimeoutID);
const newCurrentOnFilterChangeTimeoutID = setTimeout(() => {
const { onChange, variables } = this.props;
const newVariables = Object.assign({}, variables);
if (args.length > 0) {
for ( const i in args ) {
newVariables['filter' + ucfirst(args[i].columnName)] = args[i].value;
}
} else {
for ( const i in newVariables ) {
if (i.substr(0, 6) === 'filter') newVariables[i] = null;
}
}
onChange({
...newVariables,
'currentPage': 0,
'offset': 0 * variables.limit,
});
}, 1000);
this.setState({ currentOnFilterChangeTimeoutID: newCurrentOnFilterChangeTimeoutID });
}
render () {
const { variables, customersPaginated, customersPaginatedCount } = this.props;
if (customersPaginated.error) console.error( customersPaginated.error );
if (customersPaginatedCount.error) console.error( customersPaginatedCount.error );
return (
<div>
{(customersPaginated.error || customersPaginatedCount.error) && (
<Typography color="error" gutterBottom>
Une erreur est survenue.
</Typography>
)}
<div>
<CustomersTable
customers={customersPaginated.customersPaginated}
currentPage={variables.currentPage}
onCurrentPageChange={this.onCurrentPageChange}
onSortingChange={this.onSortingChange}
onFiltersChange={this.onFiltersChange}
pageSize={variables.pageSize}
totalCount={customersPaginatedCount.customersPaginatedCount || 0}
/>
{(customersPaginated.loading || customersPaginatedCount.loading) && <Loading />}
</div>
</div>
);
}
static propTypes = {
customersPaginated: PropTypes.object.isRequired,
customersPaginatedCount: PropTypes.object.isRequired,
variables: PropTypes.object.isRequired,
onChange: PropTypes.func.isRequired,
};
}
Parent :
import React from 'react';
import Typography from 'material-ui/Typography';
import Button from 'material-ui/Button';
import AddIcon from 'material-ui-icons/Add';
import CustomersTableContainer from 'containers/Customer/CustomersTableContainer';
export default class CustomersPage extends React.PureComponent {
constructor(props) {
super(props);
this.state = {
customersTableVariables: {
filterId: null,
filterSiren: null,
filterName: null,
filterEmail: null,
pageSize: 10,
currentPage: 0,
offset: 0,
limit: 10,
},
};
this.onCustomersChange = this.onCustomersChange.bind(this);
}
onCustomersChange (newVariables) {
this.setState({
customersTableVariables: Object.assign({}, this.state.customersTableVariables, newVariables)
});
}
render () {
const { customersTableVariables } = this.state;
return (
<div>
<Typography align="right">
<Button fab color="primary" aria-label="add" href="/customer/new">
<AddIcon />
</Button>
</Typography>
<Typography type="title" gutterBottom>
Clients/Prospects
</Typography>
<CustomersTableContainer variables={customersTableVariables} onChange={this.onCustomersChange} />
</div>
);
}
}
Upvotes: 1