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.
There are a few high-level approaches to make this work:
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.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 @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?
@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.
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.
Most helpful comment
@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 calledfirstbut 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.