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

Load Content Blocks in parallel to data fetching on Field view #1077

Merged
merged 1 commit into from
May 2, 2019
Merged
Show file tree
Hide file tree
Changes from all 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
179 changes: 108 additions & 71 deletions packages/admin-ui/client/pages/Item/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/** @jsx jsx */
import { jsx } from '@emotion/core';
import { Component, Fragment, useMemo, useCallback } from 'react';
import { Component, Fragment, Suspense, useMemo, useCallback } from 'react';
import styled from '@emotion/styled';
import { Mutation, Query } from 'react-apollo';
import { withRouter } from 'react-router-dom';
import { withToastManager } from 'react-toast-notifications';
import memoizeOne from 'memoize-one';
import isPromise from 'p-is-promise';

import { Container } from '@arch-ui/layout';
import { Button } from '@arch-ui/button';
Expand Down Expand Up @@ -274,83 +275,119 @@ const ItemNotFound = ({ adminPath, errorMessage, list }) => (
</PageError>
);

const PreLoadFields = ({ children, list, loadField }) => (
<Suspense fallback={<PageLoading />}>
<Render>
{() => {
const initialisationPromises = list.fields
.map((item, index) => {
try {
loadField(item, index, list);
} catch (loadingPromiseOrError) {
// An actual error was thrown, so we want to bubble that up
if (!isPromise(loadingPromiseOrError)) {
throw loadingPromiseOrError;
}
// Return a Suspense promise
return loadingPromiseOrError;
}
})
.filter(Boolean);

if (initialisationPromises.length) {
// Trigger the suspense boundary and wait for all fields in
// parallel.
throw Promise.all(initialisationPromises);
}

return children();
}}
</Render>
</Suspense>
);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a future PR, I will move this component somewhere re-usable as the logic is identical for Cell/Filter.


const ItemPage = ({ list, itemId, adminPath, getListByKey, toastManager }) => {
const itemQuery = list.getItemQuery(itemId);
return (
<Fragment>
{/* network-only because the data we mutate with is important for display
in the UI, and may be different than what's in the cache */}
<Query query={itemQuery} fetchPolicy="network-only" errorPolicy="all">
{({ loading, error, data, refetch }) => {
if (loading) return <PageLoading />;

// Only show error page if there is no data
// (ie; there could be partial data + partial errors)
if (
error &&
(!data ||
!data[list.gqlNames.itemQueryName] ||
!Object.keys(data[list.gqlNames.itemQueryName]).length)
) {
return (
<Fragment>
<DocTitle>{list.singular} not found</DocTitle>
<ItemNotFound adminPath={adminPath} errorMessage={error.message} list={list} />
</Fragment>
);
}

const item = list.deserializeItemData(data[list.gqlNames.itemQueryName]);
const itemErrors = deconstructErrorsToDataShape(error)[list.gqlNames.itemQueryName] || {};

return item ? (
<main>
<DocTitle>
{item._label_} - {list.singular}
</DocTitle>
<Container id="toast-boundary">
<Mutation
mutation={list.updateMutation}
onError={updateError => {
const [title, ...rest] = updateError.message.split(/\:/);
const toastContent = rest.length ? (
<div>
<strong>{title.trim()}</strong>
<div>{rest.join('').trim()}</div>
</div>
) : (
updateError.message
);

toastManager.add(toastContent, {
appearance: 'error',
});
}}
>
{(updateItem, { loading: updateInProgress, error: updateError }) => {
return (
<ItemDetails
adminPath={adminPath}
item={item}
itemErrors={itemErrors}
key={itemId}
list={list}
getListByKey={getListByKey}
onUpdate={refetch}
toastManager={toastManager}
updateInProgress={updateInProgress}
updateErrorMessage={updateError && updateError.message}
updateItem={updateItem}
/>
);
}}
</Mutation>
</Container>
</main>
) : (
<ItemNotFound adminPath={adminPath} list={list} />
);
}}
{({ loading, error, data, refetch }) => (
<PreLoadFields list={list} loadField={field => field.initFieldView()}>
{() => {
if (loading) return <PageLoading />;

// Only show error page if there is no data
// (ie; there could be partial data + partial errors)
if (
error &&
(!data ||
!data[list.gqlNames.itemQueryName] ||
!Object.keys(data[list.gqlNames.itemQueryName]).length)
) {
return (
<Fragment>
<DocTitle>{list.singular} not found</DocTitle>
<ItemNotFound adminPath={adminPath} errorMessage={error.message} list={list} />
</Fragment>
);
}

const item = list.deserializeItemData(data[list.gqlNames.itemQueryName]);
const itemErrors =
deconstructErrorsToDataShape(error)[list.gqlNames.itemQueryName] || {};

return item ? (
<main>
<DocTitle>
{item._label_} - {list.singular}
</DocTitle>
<Container id="toast-boundary">
<Mutation
mutation={list.updateMutation}
onError={updateError => {
const [title, ...rest] = updateError.message.split(/\:/);
const toastContent = rest.length ? (
<div>
<strong>{title.trim()}</strong>
<div>{rest.join('').trim()}</div>
</div>
) : (
updateError.message
);

toastManager.add(toastContent, {
appearance: 'error',
});
}}
>
{(updateItem, { loading: updateInProgress, error: updateError }) => {
return (
<ItemDetails
adminPath={adminPath}
item={item}
itemErrors={itemErrors}
key={itemId}
list={list}
getListByKey={getListByKey}
onUpdate={refetch}
toastManager={toastManager}
updateInProgress={updateInProgress}
updateErrorMessage={updateError && updateError.message}
updateItem={updateItem}
/>
);
}}
</Mutation>
</Container>
</main>
) : (
<ItemNotFound adminPath={adminPath} list={list} />
);
}}
</PreLoadFields>
)}
</Query>
</Fragment>
);
Expand Down
1 change: 1 addition & 0 deletions packages/admin-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
"lodash.debounce": "^4.0.8",
"lodash.set": "^4.3.2",
"memoize-one": "^4.0.2",
"p-is-promise": "^2.1.0",
"preconstruct": "0.0.60",
"prop-types": "^15.7.2",
"raf-schd": "^4.0.0",
Expand Down
4 changes: 4 additions & 0 deletions packages/fields/src/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,8 @@ export default class FieldController {

// eslint-disable-next-line no-unused-vars
getDefaultValue = data => this.config.defaultValue || '';

initCellView = () => {};
initFieldView = () => {};
initFilterView = () => {};
}
2 changes: 2 additions & 0 deletions packages/fields/src/types/Content/views/Controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -124,4 +124,6 @@ export default class ContentController extends TextController {
}
`;
};

initFieldView = () => this.getBlocks();
}