Кэш-память ретрансляции и пагинация вызывают ошибку RelayConnectionHandler: неожиданно после курсора - PullRequest
1 голос
/ 27 марта 2020

Описание

У меня есть контейнер с вложенной пагинацией в файловой системе. Недавно я добавил слой кэширования в свою среду ретрансляции, чтобы уменьшить количество запросов, которые мы делаем. Открытие папки запускает 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...