Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[RFR] Add withDataProvider HOC and Query/Mutation components to ease custom queries #2899

Merged
merged 20 commits into from
Feb 28, 2019
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
430 changes: 255 additions & 175 deletions docs/Actions.md

Large diffs are not rendered by default.

24 changes: 18 additions & 6 deletions docs/_layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -692,31 +692,43 @@

<li class="chapter {% if page.path == 'Actions.md' %}active{% endif %}">
<a href="./Actions.html">
<b>14.</b> Writing Actions
<b>14.</b> Querying the API
</a>
<ul class="articles" {% if page.path !='Actions.md' %}style="display:none" {% endif %}>
<li class="chapter">
<a href="#the-simple-way">The Simple Way</a>
<a href="#the-simple-way-using-fetch">The Simple Way: Using <code>fetch</code></a>
</li>
<li class="chapter">
<a href="#using-a-data-provider-instead-of-fetch">Using a Data Provider</a>
<a href="#using-the-data-provider-instead-of-fetch">Using the <code>dataProvider</code></a>
</li>
<li class="chapter">
<a href="#using-a-custom-action-creator">Using a Custom Action Creator</a>
<a href="#using-the-withdataprovider-decorator">Using <code>withDataProvider</code></a>
</li>
<li class="chapter">
<a href="#handling-side-effects">Handling Side Effects</a>
</li>
<li class="chapter">
<a href="#success-and-failure-side-effects">Success and Failure Side Effects</a>
<a href="#optimistic-rendering-and-undo">Optimistic Rendering and Undo</a>
</li>
<li class="chapter">
<a href="#optimistic-rendering-and-undo">Optimistic Rendering and Undo</a>
<a href="#query-and-mutation-components"><code>&lt;Query&gt;</code> and <code>&lt;Mutation&gt;</code></a>
</li>
<li class="chapter">
<a href="#using-a-custom-action-creator">Using a Custom Action Creator</a>
</li>
<li class="chapter">
<a href="#adding-side-effects-to-actions">Adding Side Effects to Actions</a>
</li>
<li class="chapter">
<a href="#making-an-action-undoable">Undoable Action</a>
</li>
<li class="chapter">
<a href="#altering-the-form-values-before-submitting">Altering the Form Values before
Submitting</a>
</li>
<li class="chapter">
<a href="#custom-side-effects">Custom Side Effects</a>
</li>
<li class="chapter">
<a href="#custom-sagas">Custom Sagas</a>
</li>
Expand Down
224 changes: 115 additions & 109 deletions examples/demo/src/dashboard/Dashboard.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
import React, { Component } from 'react';
import { GET_LIST, GET_MANY, Responsive } from 'react-admin';
import { GET_LIST, GET_MANY, Responsive, withDataProvider } from 'react-admin';
import compose from 'recompose/compose';
import { connect } from 'react-redux';

import Welcome from './Welcome';
import MonthlyRevenue from './MonthlyRevenue';
import NbNewOrders from './NbNewOrders';
import PendingOrders from './PendingOrders';
import PendingReviews from './PendingReviews';
import NewCustomers from './NewCustomers';
import dataProviderFactory from '../dataProvider';

const styles = {
flex: { display: 'flex' },
Expand All @@ -21,118 +22,116 @@ class Dashboard extends Component {
state = {};

componentDidMount() {
this.fetchData();
}

componentDidUpdate(prevProps) {
// handle refresh
if (this.props.version !== prevProps.version) {
this.fetchData();
}
}

fetchData() {
this.fetchOrders();
this.fetchReviews();
this.fetchCustomers();
}

async fetchOrders() {
const { dataProvider } = this.props;
const aMonthAgo = new Date();
aMonthAgo.setDate(aMonthAgo.getDate() - 30);
const { data: recentOrders } = await dataProvider(
GET_LIST,
'commands',
{
filter: { date_gte: aMonthAgo.toISOString() },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 50 },
}
);
const aggregations = recentOrders
.filter(order => order.status !== 'cancelled')
.reduce(
(stats, order) => {
if (order.status !== 'cancelled') {
stats.revenue += order.total;
stats.nbNewOrders++;
}
if (order.status === 'ordered') {
stats.pendingOrders.push(order);
}
return stats;
},
{
revenue: 0,
nbNewOrders: 0,
pendingOrders: [],
}
);
this.setState({
revenue: aggregations.revenue.toLocaleString(undefined, {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}),
nbNewOrders: aggregations.nbNewOrders,
pendingOrders: aggregations.pendingOrders,
});
const { data: customers } = await dataProvider(GET_MANY, 'customers', {
ids: aggregations.pendingOrders.map(order => order.customer_id),
});
this.setState({
pendingOrdersCustomers: customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {}),
});
}

dataProviderFactory(process.env.REACT_APP_DATA_PROVIDER).then(
dataProvider => {
dataProvider(GET_LIST, 'commands', {
filter: { date_gte: aMonthAgo.toISOString() },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 50 },
})
.then(response =>
response.data
.filter(order => order.status !== 'cancelled')
.reduce(
(stats, order) => {
if (order.status !== 'cancelled') {
stats.revenue += order.total;
stats.nbNewOrders++;
}
if (order.status === 'ordered') {
stats.pendingOrders.push(order);
}
return stats;
},
{
revenue: 0,
nbNewOrders: 0,
pendingOrders: [],
}
)
)
.then(({ revenue, nbNewOrders, pendingOrders }) => {
this.setState({
revenue: revenue.toLocaleString(undefined, {
style: 'currency',
currency: 'USD',
minimumFractionDigits: 0,
maximumFractionDigits: 0,
}),
nbNewOrders,
pendingOrders,
});
return pendingOrders;
})
.then(pendingOrders =>
pendingOrders.map(order => order.customer_id)
)
.then(customerIds =>
dataProvider(GET_MANY, 'customers', {
ids: customerIds,
})
)
.then(response => response.data)
.then(customers =>
customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {})
)
.then(customers =>
this.setState({ pendingOrdersCustomers: customers })
);

dataProvider(GET_LIST, 'reviews', {
filter: { status: 'pending' },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
})
.then(response => response.data)
.then(reviews => {
const nbPendingReviews = reviews.reduce(nb => ++nb, 0);
const pendingReviews = reviews.slice(
0,
Math.min(10, reviews.length)
);
this.setState({ pendingReviews, nbPendingReviews });
return pendingReviews;
})
.then(reviews => reviews.map(review => review.customer_id))
.then(customerIds =>
dataProvider(GET_MANY, 'customers', {
ids: customerIds,
})
)
.then(response => response.data)
.then(customers =>
customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {})
)
.then(customers =>
this.setState({ pendingReviewsCustomers: customers })
);
async fetchReviews() {
const { dataProvider } = this.props;
const { data: reviews } = await dataProvider(GET_LIST, 'reviews', {
filter: { status: 'pending' },
sort: { field: 'date', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
});
const nbPendingReviews = reviews.reduce(nb => ++nb, 0);
const pendingReviews = reviews.slice(0, Math.min(10, reviews.length));
this.setState({ pendingReviews, nbPendingReviews });
const { data: customers } = await dataProvider(GET_MANY, 'customers', {
ids: pendingReviews.map(review => review.customer_id),
});
this.setState({
pendingReviewsCustomers: customers.reduce((prev, customer) => {
prev[customer.id] = customer; // eslint-disable-line no-param-reassign
return prev;
}, {}),
});
}

dataProvider(GET_LIST, 'customers', {
filter: {
has_ordered: true,
first_seen_gte: aMonthAgo.toISOString(),
},
sort: { field: 'first_seen', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
})
.then(response => response.data)
.then(newCustomers => {
this.setState({ newCustomers });
this.setState({
nbNewCustomers: newCustomers.reduce(nb => ++nb, 0),
});
});
async fetchCustomers() {
const { dataProvider } = this.props;
const aMonthAgo = new Date();
aMonthAgo.setDate(aMonthAgo.getDate() - 30);
const { data: newCustomers } = await dataProvider(
GET_LIST,
'customers',
{
filter: {
has_ordered: true,
first_seen_gte: aMonthAgo.toISOString(),
},
sort: { field: 'first_seen', order: 'DESC' },
pagination: { page: 1, perPage: 100 },
}
);
this.setState({
newCustomers,
nbNewCustomers: newCustomers.reduce(nb => ++nb, 0),
});
}

render() {
Expand Down Expand Up @@ -222,4 +221,11 @@ class Dashboard extends Component {
}
}

export default Dashboard;
const mapStateToProps = state => ({
version: state.admin.ui.viewVersion,
});

export default compose(
connect(mapStateToProps),
withDataProvider
)(Dashboard);
Loading