Use pagination container

This commit is contained in:
Chi Vinh Le
2018-07-02 19:13:13 -03:00
parent 4494ac8900
commit 796d24db3d
11 changed files with 148 additions and 43 deletions
@@ -1,10 +1,23 @@
import {
ConnectionConfig,
ConnectionConfig as OrigConnectionConfig,
createPaginationContainer,
GraphQLTaggedNode,
PageInfo,
RelayPaginationProp,
} from "react-relay";
import { InferableComponentEnhancerWithProps } from "recompose";
import { Overwrite } from "talk-framework/types";
// TODO: (cvle) Fix this type definition upstream.
interface ConnectionData {
edges?: Readonly<any>;
pageInfo?: PageInfo;
}
type ConnectionConfig<T> = Overwrite<
OrigConnectionConfig<T>,
{ getConnectionFromProps?(props: T): ConnectionData | undefined | null }
>;
/**
* withPaginationContainer is a curried version of `createPaginationContainers`
+2 -10
View File
@@ -4,17 +4,11 @@ import { StatelessComponent } from "react";
import { Center } from "talk-ui/components";
import AssetListContainer from "../containers/AssetListContainer";
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
import StreamContainer from "../containers/StreamContainer";
import Logo from "./Logo";
export interface AppProps {
assets?: any | null;
asset?: {
id: string;
isClosed: boolean;
comments: any | null;
} | null;
asset?: {} | null;
}
const App: StatelessComponent<AppProps> = props => {
@@ -24,9 +18,7 @@ const App: StatelessComponent<AppProps> = props => {
if (props.asset) {
return (
<Center>
<Logo gutterBottom />
<StreamContainer comments={props.asset.comments} />
<PostCommentFormContainer assetID={props.asset.id} />
<StreamContainer asset={props.asset} />
</Center>
);
}
@@ -7,6 +7,7 @@
margin-bottom: calc(2px * $spacing-unit);
}
.postButton {
float: right;
}
.postButtonContainer {
display: flex;
justify-content: flex-end;
}
@@ -39,11 +39,13 @@ const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
</div>
)}
</Field>
<Localized id="postCommentForm-submit">
<Button className={styles.postButton} disabled={submitting} primary>
Post
</Button>
</Localized>
<div className={styles.postButtonContainer}>
<Localized id="postCommentForm-submit">
<Button disabled={submitting} primary>
Post
</Button>
</Localized>
</div>
</form>
)}
</Form>
+20 -2
View File
@@ -1,18 +1,36 @@
import * as React from "react";
import { StatelessComponent } from "react";
import CommentContainer from "../containers/CommentContainer";
import Logo from "talk-stream/components/Logo";
import CommentContainer from "talk-stream/containers/CommentContainer";
import PostCommentFormContainer from "talk-stream/containers/PostCommentFormContainer";
import { Button } from "talk-ui/components";
export interface StreamProps {
comments: ReadonlyArray<{ id: string }>;
id: string;
isClosed: boolean;
comments: ReadonlyArray<{ id: string }> | null;
onLoadMore: () => void;
hasMore: boolean;
}
const Stream: StatelessComponent<StreamProps> = props => {
if (props.comments === null) {
// TODO: (@cvle) What's the reason for this being null?
return <div>Comments unavailable</div>;
}
return (
<div>
<Logo gutterBottom />
<PostCommentFormContainer assetID={props.id} />
{props.comments.map(comment => (
<CommentContainer key={comment.id} data={comment} gutterBottom />
))}
{props.hasMore && (
<Button onClick={props.onLoadMore} secondary fullWidth>
Load More
</Button>
)}
</div>
);
};
@@ -26,11 +26,7 @@ const enhanced = withFragmentContainer<{ data: Data }>(
...AssetListContainer_assets
}
asset(id: $assetID) @skip(if: $showAssetList) {
id
isClosed
comments {
...StreamContainer_comments
}
...StreamContainer_asset
}
}
`
@@ -25,6 +25,8 @@ class PostCommentFormContainer extends Component<InnerProps> {
if (error instanceof BadUserInputError) {
return error.invalidArgsLocalized;
}
// tslint:disable-next-line:no-console
console.error(error);
}
return undefined;
};
@@ -1,32 +1,108 @@
import React, { StatelessComponent } from "react";
import { graphql } from "react-relay";
import React from "react";
import { graphql, RelayPaginationProp } from "react-relay";
import { withFragmentContainer } from "talk-framework/lib/relay";
import { withPaginationContainer } from "talk-framework/lib/relay";
import { PropTypesOf } from "talk-framework/types";
import { StreamContainer_comments as Data } from "talk-stream/__generated__/StreamContainer_comments.graphql";
import { StreamContainer_asset as Data } from "talk-stream/__generated__/StreamContainer_asset.graphql";
import Stream from "../components/Stream";
interface InnerProps {
comments: Data;
asset: Data;
relay: RelayPaginationProp;
}
const StreamContainer: StatelessComponent<InnerProps> = props => {
const comments = props.comments.edges.map(edge => edge.node);
return <Stream comments={comments} />;
};
class StreamContainer extends React.Component<InnerProps> {
public render() {
const comments = this.props.asset.comments
? this.props.asset.comments.edges.map(edge => edge.node)
: null;
return (
<Stream
{...this.props.asset}
comments={comments}
onLoadMore={this.loadMore}
hasMore={this.props.relay.hasMore()}
/>
);
}
const enhanced = withFragmentContainer<{ comments: Data }>(
private loadMore = () => {
if (!this.props.relay.hasMore() || this.props.relay.isLoading()) {
return;
}
this.props.relay.loadMore(
10, // Fetch the next 10 feed items
error => {
if (error) {
// tslint:disable-next-line:no-console
console.error(error);
}
}
);
};
}
const enhanced = withPaginationContainer<{ asset: Data }, InnerProps>(
graphql`
fragment StreamContainer_comments on CommentsConnection {
edges {
node {
id
...CommentContainer
fragment StreamContainer_asset on Asset
@argumentDefinitions(
count: { type: "Int", defaultValue: 5 }
cursor: { type: "Cursor" }
orderBy: { type: "COMMENT_SORT", defaultValue: CREATED_AT_DESC }
) {
id
isClosed
comments(first: $count, after: $cursor, orderBy: $orderBy)
@connection(key: "Stream_comments") {
edges {
node {
id
...CommentContainer
}
}
}
}
`
`,
{
direction: "forward",
getConnectionFromProps(props) {
return props.asset && props.asset.comments;
},
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
getFragmentVariables(prevVars, totalCount) {
return {
...prevVars,
count: totalCount,
};
},
getVariables(props, { count, cursor }, fragmentVariables) {
return {
count,
cursor,
orderBy: fragmentVariables.orderBy,
// assetID isn't specified as an @argument for the fragment, but it should be a
// variable available for the fragment under the query root.
assetID: props.asset.id,
};
},
query: graphql`
# Pagination query to be fetched upon calling 'loadMore'.
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
query StreamContainerPaginationQuery(
$count: Int!
$cursor: Cursor
$orderBy: COMMENT_SORT!
$assetID: ID!
) {
asset(id: $assetID) {
...StreamContainer_asset
@arguments(count: $count, cursor: $cursor, orderBy: $orderBy)
}
}
`,
}
)(StreamContainer);
export type StreamContainerProps = PropTypesOf<typeof enhanced>;
@@ -232,7 +232,10 @@ type PageInfo {
"""
Indicates that there are more nodes after this subset.
"""
hasNextPage: Boolean!
hasNextPage: Boolean
hasPreviousPage: Boolean
startCursor: Cursor
endCursor: Cursor
}
"""
+1
View File
@@ -280,6 +280,7 @@ async function retrieveConnection(
edges,
pageInfo: {
hasNextPage,
endCursor: (nodes.length && nodes[nodes.length - 1].created_at) || null,
},
};
+1
View File
@@ -7,6 +7,7 @@ export interface Edge<T> {
export interface PageInfo {
hasNextPage: boolean;
endCursor: Cursor;
}
export interface Connection<T> {