描述
我有一个在文件系统上嵌套了分页的容器。我最近在我的中继环境中添加了一个缓存层,以减少我们发出的请求数量。打开文件夹会触发 queryRender,该查询一次获取 8 条边,直到文件夹完全加载。在第一次提取时打开文件夹会按预期加载所有边缘。关闭文件夹并打开它只加载前 8 个边缘,并在第二个请求时失败。
我收到此错误:index.js:1 警告:RelayConnectionHandler:在光标“MTo3”之后出现意外,必须从列表末尾(“Mjow”)获取边。
添加{ force: true }
解决了这个问题,但它会导致重新获取已经请求和存储的数据。
另一种方法是跟踪加载的文件计数,当第一次加载后触发 QueryRenderer 时,我可以传入加载的文件计数。
我只是想知道是否有更清洁的方法来实现我想要的?或者中继不支持我们的模式,我认为它应该的方式?
图片
首次加载成功
第一次加载得到 9 个边 失败第二次加载
代码
文件浏览器.js
文件浏览器的根查询
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
}
}`,
},
);
文件夹查询.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;