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

Is it possible to construct InfiniteList HOC for RelayConnection? #1377

Closed
nodkz opened this Issue Sep 8, 2016 · 4 comments

Comments

Projects
None yet
3 participants
@nodkz
Contributor

nodkz commented Sep 8, 2016

In my app a have huge amount of connections for different types. So for reducing copy/pasting I want to create high order component for connections.

// React component with HOC wrapping

class MailOutboxList extends React.Component {
  ...
  render() {
    <div>
        {connection.edges.map(({ node }) => ( ... ))}
     </div>
  }
} 

export default infiniteScrollHOC({
  fragmentKey: 'viewer',
  onType: 'Viewer',
  connectionName: 'mailConnection',
  nodeFields: `
    id
    from
    subject
    args {
       ${SubComponent.getFragment('args')}  // Also I want to use nested fragments
    }
  `,
})(MailOutboxList);

Here is example of HOC. This code is almost entirely used in every React Connection Component file.

// infiniteScrollHOC

import React from 'react';
import Relay from 'react-relay';
import Loader from 'react-loader';

export default function infiniteScrollHOC({
  fragmentKey,
  onType,
  connectionName,
  nodeFields,
  PER_PAGE = 20,
  PIXEL_GAP = 500,
}) {
  return (BaseComponent) => {
    class InfiniteScrollComponent extends React.Component {
      static displayName = `InfiniteScroll<${BaseComponent.displayName || BaseComponent.name}>`;

      constructor(props) {
        super(props);

        this.state = {
          loading: false,
        };

        this.onScroll = this.onScroll.bind(this);
      }

      onScroll(e) {
        if (!this.state.loading) {
          this.loadNextItemsIfNeeded(e.target);
        }
      }

      loadNextItemsIfNeeded(elem) {
        if (elem) {
          const pxTillEnd = elem.scrollHeight - (elem.scrollTop + elem.offsetHeight);
          if (pxTillEnd < PIXEL_GAP) {
            this.loadNextItems();
          }
        }
      }

      loadNextItems() {
        this.setState({ loading: true }, () => {
          this.props.relay.setVariables({
            count: this.props.relay.variables.count + PER_PAGE,
          }, (readyState) => { // this gets called twice https://goo.gl/ZsQ3Dy
            if (readyState.done) {
              this.setState({ loading: false });
            }
          });
        });
      }

      render() {
        const fragmentData = this.props[fragmentKey];
        const props = { ...this.props };
        delete props[fragmentKey]; // will provide `connection` directly
        return (
          <div>
            <BaseComponent
              {...props}
              connection={fragmentData.connection}
              ref={(c) => { this._component = c; }}
            />

            { fragmentData.connection.pageInfo.hasNextPage &&
              <Loader />
            }
          </div>
        );
      }
    }

    const frm = `
      fragment on ${onType} {
        connection: ${connectionName}(first: $count) {
          pageInfo {
            hasNextPage
            hasPreviousPage
          }
          edges {
            cursor
            node {
              ${nodeFields}
            }
          }
        }
      }
    `;

    return Relay.createContainer(InfiniteScrollComponent, {
      initialVariables: {
        count: PER_PAGE,
      },
      fragments: {
        [fragmentKey]: () => Relay.QL([frm]), // problem with BABEL TRANSFORM
      },
    });
  };
}

Please help to construct fragment by hands, without Relay.QL and babel transform:

fragments: {
  [fragmentKey]: () => Relay.QL([frm]), // problem with BABEL TRANSFORM
},

Or point me to an existing solution.
Thanks.

@josephsavona

This comment has been minimized.

Show comment
Hide comment
@josephsavona

josephsavona Sep 9, 2016

Contributor

There are a few high-level approaches to make this work:

  • Build the API as described above, but require the user to specify the fragment manually instead of just the fragment type. Yes, you have to repeat the fragment, but all the infinite scroll behavior can be reused.
  • Create an interface that matches the shape of the connection type (i.e. has edges and pageInfo fields, where the edge type has a cursor and node: Node), and write the fragment against the interface not a specific type.
  • Write a babel plugin that expands some token or function call into a Relay.QL tagged expression, and then let the regular Relay plugin convert that template. For example you could write a plugin that finds all invocations of createConnectionFragment('<type>') and replaces them with Relay.QLfragment on { ... }``.
Contributor

josephsavona commented Sep 9, 2016

There are a few high-level approaches to make this work:

  • Build the API as described above, but require the user to specify the fragment manually instead of just the fragment type. Yes, you have to repeat the fragment, but all the infinite scroll behavior can be reused.
  • Create an interface that matches the shape of the connection type (i.e. has edges and pageInfo fields, where the edge type has a cursor and node: Node), and write the fragment against the interface not a specific type.
  • Write a babel plugin that expands some token or function call into a Relay.QL tagged expression, and then let the regular Relay plugin convert that template. For example you could write a plugin that finds all invocations of createConnectionFragment('<type>') and replaces them with Relay.QLfragment on { ... }``.
@nodkz

This comment has been minimized.

Show comment
Hide comment
@nodkz

nodkz Sep 15, 2016

Contributor

@josephsavona thanks for your reply. Right now I'm not ready to get such deep dive. Maybe in October or even November. Absolutely have not free time, should launch our commercial app in next month and needs time for my OSS graphql-compose.

Dear community, maybe somebody interested in implementing this HOC for RelayConnection?

Contributor

nodkz commented Sep 15, 2016

@josephsavona thanks for your reply. Right now I'm not ready to get such deep dive. Maybe in October or even November. Absolutely have not free time, should launch our commercial app in next month and needs time for my OSS graphql-compose.

Dear community, maybe somebody interested in implementing this HOC for RelayConnection?

@gre

This comment has been minimized.

Show comment
Hide comment
@gre

gre Sep 19, 2016

@nodkz It's funny, I was just blogging about this :) , we have used a roughly similar abstraction for months now in my company: http://greweb.me/2016/09/relay-scrolling-connections/ .
In this implementation I have not used HOC but just a simple component that renders children, and I figured out the only prop it needs is relay (it assumes you have a variable called first but there is another prop to customise it).
What I liked in using component instead of HOC is it remains separated of the component, you might not always want to make your "list of thing" component strongly coupled with the scroll mechanism (but maybe you want?). The component solution also have the advantage you can use it in various "inline" use case, for instance using material-ui List here: <InfiniteScrollable><List>{data.map(...)}</List></InfiniteScrollable>. Not sure how HOC would work here (List is not a Relay container).

Anyway, for the various use-cases we can have, I'm not so sure if this problem can easily abstracted out: for sure we can provide a solution for 90% users, but there also might be specific parts. (like implementing <Loader/>, if you really want to have a generic lib, this implementation needs to be externalised e.g. via prop).

What I think is important, (but I guess we all agree on this), is for Relay to continue focusing on solving the generic part which is not providing an implementation for this "how to handle pulling data when I scroll" problem.

gre commented Sep 19, 2016

@nodkz It's funny, I was just blogging about this :) , we have used a roughly similar abstraction for months now in my company: http://greweb.me/2016/09/relay-scrolling-connections/ .
In this implementation I have not used HOC but just a simple component that renders children, and I figured out the only prop it needs is relay (it assumes you have a variable called first but there is another prop to customise it).
What I liked in using component instead of HOC is it remains separated of the component, you might not always want to make your "list of thing" component strongly coupled with the scroll mechanism (but maybe you want?). The component solution also have the advantage you can use it in various "inline" use case, for instance using material-ui List here: <InfiniteScrollable><List>{data.map(...)}</List></InfiniteScrollable>. Not sure how HOC would work here (List is not a Relay container).

Anyway, for the various use-cases we can have, I'm not so sure if this problem can easily abstracted out: for sure we can provide a solution for 90% users, but there also might be specific parts. (like implementing <Loader/>, if you really want to have a generic lib, this implementation needs to be externalised e.g. via prop).

What I think is important, (but I guess we all agree on this), is for Relay to continue focusing on solving the generic part which is not providing an implementation for this "how to handle pulling data when I scroll" problem.

@nodkz

This comment has been minimized.

Show comment
Hide comment
@nodkz

nodkz Oct 27, 2016

Contributor

WOW! 💪
@gre awesome work!

Today I come back to solve this problem and found your great solution. Thanks!!!
PS. Sep 19 I was on vacation and miss github notification.

Contributor

nodkz commented Oct 27, 2016

WOW! 💪
@gre awesome work!

Today I come back to solve this problem and found your great solution. Thanks!!!
PS. Sep 19 I was on vacation and miss github notification.

@nodkz nodkz closed this Oct 27, 2016

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment