Описание
У меня есть контейнер с вложенной пагинацией в файловой системе. Недавно я добавил слой кэширования в свою среду ретрансляции, чтобы уменьшить количество запросов, которые мы делаем. Открытие папки запускает queryRender, который запрашивает 8 ребер за раз, пока папка не будет полностью загружена. Открытие папки при первом извлечении загружает все ребра, как и ожидалось. Закрытие папки и ее открытие загружает только первые 8 ребер и завершается неудачно при втором запросе.
Я получаю эту ошибку: index. js: 1 Предупреждение: RelayConnectionHandler: неожиданно после курсора 'MTo3', ребра должны извлекается из конца списка ('Mjow').
Добавление { force: true }
решает проблему, но вызывает повторное получение данных, которые уже были запрошены и сохранены.
Альтернатива для отслеживания количества загруженных файлов, и когда QueryRenderer запускается после первой загрузки, я могу передать количество загруженных файлов.
Мне просто интересно, есть ли более чистый способ добиться того, чего я хочу? или реле не поддерживает нашу схему так, как мне кажется?
Изображения
успешная первая загрузка
Первая загрузка получает 9 ребер ошибка второй загрузки
2-й загрузке не удается выполнить разбиение на страницы из кэша
Код
FileBrowser. js
Root запрос для файлового браузера
export default createPaginationContainer(
FileBrowser,
{
repository: graphql`
fragment FileBrowser_repository on Project {
files(
first: $first,
after: $cursor
) @connection(
key: "FileBrowser_files",
filters: [$cursor]
) @skip (if: $fileSkip) {
edges {
node {
... on ProjectFile {
id
namespace
repositoryName
key
modifiedAt
sizeBytes
downloadURL
}
... on ProjectDirectory {
id
key
}
}
}
pageInfo{
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
__typename
}`,
},
{
direction: 'forward',
getConnectionFromProps(props) {
return props.repository && props.repository.files;
},
getFragmentVariables(prevVars, first) {
return {
...prevVars,
first,
};
},
getVariables(props, { count, cursor }, fragmentVariables) {
const { namespace, repositoryName } = props;
return {
...fragmentVariables,
first: count,
cursor,
repositoryName,
namespace,
filter: [],
};
},
query: graphql`
query FileBrowserQuery(
$namespace: String!,
$repositoryName: String!,
$first: Int!,enter code here
$cursor: String,
$fileSkip: Boolean!
) {
repository(
namespace: $namespace,
repositoryName: $repositoryName
) {
...FileBrowser_repository
}
}`,
},
);
FolderQuery. js
Контейнер разбиения на страницы для папки
// @flow
// vendor
import React, { Component } from 'react';
import {
createPaginationContainer,
graphql,
} from 'react-relay';
// components
import Folder from './Folder';
// assets
import './Folder.scss';
const FolderPaginationContainer = createPaginationContainer(
Folder,
{
contents: graphql`
fragment Folder_contents on ProjectDirectory @relay(mask: false) {
id
key
contents(
first: $first,
after: $cursor,
) @connection(
key: "Folder_contents",
filters: []
) {
edges {
node {
... on ProjectFile {
id
namespace
repositoryName
key
modifiedAt
sizeBytes
}
... on ProjectDirectory {
id
key
}
}
cursor
}
pageInfo {
endCursor
hasNextPage
hasPreviousPage
startCursor
}
}
}`,
},
{
direction: 'forward',
getConnectionFromProps(props) {
return props.node && props.node.contents;
},
getFragmentVariables(prevVars, first) {
return {
...prevVars,
first,
};
},
getVariables(props, { count, cursor }, fragmentVariables) {
const { id } = props.node;
console.log(props, fragmentVariables);
return {
...fragmentVariables,
id,
first: count,
cursor,
filter: [],
};
},
query: graphql`
query FolderQuery(
$id: ID!,
$first: Int!,
$cursor: String,
) {
node(
id: $id,
) {
... on ProjectDirectory {
id
key
namespace
repositoryName
...Folder_contents
}
}
}`,
},
);
export default FolderPaginationContainer;
FolderSection. js
// @flow
// vendor
import React, { Component, Fragment } from 'react';
// components
import File from './File';
import RefetchFolder from './RefetchFolder';
type Props = {
namespace: string,
repositoryName: string,
sort: string,
reverse: bool,
index: number,
files: {
edges: Array<Object>,
},
query: Object,
__typename: string,
skipSort: boolean,
linkedDatasets: Object,
}
class FolderSection extends Component<Props> {
render() {
const {
namespace,
repositoryName,
sort,
reverse,
index,
files,
__typename,
query,
skipSort,
linkedDatasets,
} = this.props;
return (
<Fragment>
{ files.map((edge) => {
const {
key,
} = edge.node;
const isDir = (edge.node.__typename === `${__typename}Directory`);
const isFile = (edge.node.__typename === `${__typename}File`);
if (isDir) {
return (
<RefetchFolder
FolderSection={FolderSection}
name={key}
key={key}
edge={edge}
setState={this._setState}
rowStyle={{}}
sort={sort}
reverse={reverse}
rootFolder
index={index}
node={edge.node}
query={query}
__typename={__typename}
linkedDatasets={linkedDatasets}
namespace={namespace}
repositoryName={repositoryName}
/>
);
}
if (isFile) {
return (
<File
name={key}
key={key}
edge={edge}
isExpanded
index={index}
__typename={__typename}
/>
);
}
return (
<div>
Loading
</div>
);
})}
</Fragment>
);
}
}
export default FolderSection;
RefetchFolder. js
Содержимое каждой папки запрашивается в папке QueryRenderer
// @flow
// vendor
import React, { Component } from 'react';
import uuidv4 from 'uuid/v4';
import {
QueryRenderer,
graphql,
} from 'react-relay';
// environment
import environment from 'JS/createRelayEnvironment';
// component
import Folder from './FolderProjectWrapper';
type Props = {
node: {
id: String
},
relay: {
refetch: Function,
},
edge: {
node: Object
},
FolderSection: React.Node,
index: Number,
__typename: string,
sort: string,
linkedDatasets: Object,
reverse: boolean,
}
const RefetchFolderQuery = graphql`query RefetchFolderQuery(
$id: ID!,
$first: Int!,
$cursor: String,
) {
node(
id: $id,
) {
...Folder_contents @relay(mask: false)
}
}`;
class RefetchFolder extends Component<Props> {
state = {
isExpanded: false,
id: null,
progress: 0,
currentProgress: 0,
step: 0.01,
}
static getDerivedStateFromProps(props, state) {
const childNode = (props.node && props.node.contents)
? props.node
: props.edge.node;
return {
...state,
node: childNode,
};
}
/**
* @param {Object} evt
* sets item to expanded
* @return {}
*/
_expandFolder = () => {
this.setState((state) => {
const isExpanded = !state.isExpanded;
const id = (state.id === null)
? uuidv4()
: state.id;
return {
isExpanded,
id,
};
});
}
render() {
const {
FolderSection,
index,
sort,
reverse,
linkedDatasets,
} = this.props;
const { isExpanded, node, progress } = this.state;
const { __typename } = this.props;
if (!isExpanded) {
return (
<Folder
{...this.props}
FolderSection={FolderSection}
expandFolder={this._expandFolder}
node={node}
id={node.id}
index={index}
isExpanded={isExpanded}
__typename={__typename}
/>
);
}
return (
<QueryRenderer
query={RefetchFolderQuery}
environment={environment}
fetchPolicy="store-and-network"
variables={{
id: node.id,
first: 8,
cursor: null,
}}
render={({ props, error }) => {
if (props) {
const { hasNextPage } = props && props.node
&& props.node.contents && props.node.contents.pageInfo;
if (!hasNextPage) {
clearInterval(this.timeout);
}
return (
<Folder
FolderSection={FolderSection}
expandFolder={this._expandFolder}
node={props.node}
index={index}
isExpanded={isExpanded}
sort={sort}
reverse={reverse}
{...props}
__typename={__typename}
isLoading={hasNextPage}
progress={progress}
linkedDatasets={linkedDatasets}
/>
);
}
if (error) {
return (
<div>Error</div>
);
}
return (
<Folder
FolderSection={FolderSection}
expandFolder={this._expandFolder}
node={node}
index={index}
isExpanded={isExpanded}
{...this.props}
__typename={__typename}
isLoading
progress={progress}
linkedDatasets={linkedDatasets}
/>
);
}}
/>
);
}
}
export default RefetchFolder;
. js
// vendor
import React, { Component } from 'react';
import classNames from 'classnames';
// ga
import ga from 'JS/ga';
// components
import LinkedDataset from './LinkedDataset';
// assets
import './Folder.scss';
type Props = {
namespace: string,
repositoryName: string,
index: number,
node: Object,
relay: {
refetchConnection: Function,
environment: Object,
loadMore: Function,
isLoading: Function,
},
expandFolder: Function,
isExpanded: Boolean,
FolderSection: React.node,
__typename: string,
}
class Folder extends Component<Props> {
componentDidMount() {
const { node } = this.props;
if (node.contents && node.contents.pageInfo && node.contents.pageInfo.hasNextPage) {
this._loadMore();
}
}
componentDidUpdate() {
const { node } = this.props;
if (node.contents && node.contents.pageInfo && node.contents.pageInfo.hasNextPage) {
this._loadMore();
}
}
/**
* @param {string} key
* @param {string || boolean} value - updates key value in state
* update state of component for a given key value pair
* @return {}
*/
_setState = (key, value) => {
this.setState({ [key]: value });
}
/**
* @param {}
* refetches component looking for new edges to insert at the top of the activity feed
* @return {}
*/
_loadMore = () => {
const { loadMore, isLoading } = this.props.relay;
if (!isLoading()) {
loadMore(
8,
() => {},
// { force: true } commented out because it causes the cache to be ignored, but
// fixes the issue
);
}
}
/**
* @param {Object} evt
* sets item to expanded
* @return {}
*/
_refetch = () => {
const { refetchConnection } = this.props.relay;
this.setState((state) => {
const isExpanded = !state.isExpanded;
return {
isExpanded,
};
});
refetchConnection(
8,
() => {
},
{
id: this.props.node.id,
},
);
}
render() {
const {
namespace,
repositoryName,
index,
node,
expandFolder,
isExpanded,
FolderSection,
__typename,
} = this.props;
const newIndex = index + 2;
const { key } = node;
const folderName = getName(key);
// declare css here
const folderRowCSS = classNames({
Folder__row: true,
});
const folderChildCSS = classNames({
Folder__child: true,
hidden: !isExpanded,
});
const folderNameCSS = classNames({
'Folder__cell Folder__cell--name': true,
'Folder__cell--open': isExpanded,
});
return (
<div className="Folder relative">
<div
className={folderRowCSS}
onClick={evt => expandFolder(evt)}
role="presentation"
>
<div className={folderNameCSS}>
<div className="Folder__icon" />
<div className="Folder__name">
{folderName}
</div>
</div>
<div className="Folder__cell Folder__cell--size" />
<div className="Folder__cell Folder__cell--date" />
</div>
<div className={folderChildCSS}>
<FolderSection
namespace={namespace}
repositoryName={repositoryName}
files={node.contents}
node={node}
index={newIndex}
linkedDatasets={linkedDatasets}
__typename={__typename}
/>
</div>
</div>
);
}
}
export default Folder;