Skip to content

Commit

Permalink
[expo] use fetchQuery to avoid Suspense fallback
Browse files Browse the repository at this point in the history
**Summary**

In v2.x implementation, we didn't dig into the detailed behavior of
Suspense and `usePreloadedQuery`, which prevent from showing RefreshControl
in proper timings.

This suspense limitation is written at https://relay.dev/docs/v15.0.0/guided-tour/rendering/loading-states/

From v3.0, we use `fetchQuery` to control refreshing state in UI until
Relay supports concurrent rendernig with the future version of React so that
we can render the feed items already on RelayStore while refreshing the latest.

**Test**

- expo

**Issue**

-#52
  • Loading branch information
yssk22 committed Nov 14, 2023
1 parent ba1a680 commit 046c5fd
Show file tree
Hide file tree
Showing 9 changed files with 282 additions and 132 deletions.
181 changes: 181 additions & 0 deletions expo/features/feed/context/FeedContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
import {
FeedContextQuery,
FeedContextQuery$data,
HPAssetType
} from '@hpapp/features/feed/context/__generated__/FeedContextQuery.graphql';
import { FeedContextQueryFragmentQuery } from '@hpapp/features/feed/context/__generated__/FeedContextQueryFragmentQuery.graphql';
import {
FeedContextQuery_helloproject_query_feed$data,
FeedContextQuery_helloproject_query_feed$key
} from '@hpapp/features/feed/context/__generated__/FeedContextQuery_helloproject_query_feed.graphql';
import * as date from '@hpapp/foundation/date';
import React, { createContext, useContext, useEffect, useMemo, useState } from 'react';
import { fetchQuery, graphql, usePaginationFragment, useRelayEnvironment } from 'react-relay';
import { usePaginationFragmentHookType } from 'react-relay/relay-hooks/usePaginationFragment';

const NUM_FEED_ITEMS_PER_LOAD = 20;
const DAYS_TO_CALCULATE_MIN_POST_AT_FOR_TAGGEING = 3 * date.N_DAYS;

const FeedContextQueryGraphQL = graphql`
query FeedContextQuery($params: HPFeedQueryParamsInput!, $first: Int, $after: Cursor) {
helloproject {
...FeedContextQuery_helloproject_query_feed
}
}
`;

const FeedContextQueryFragmentGraphQL = graphql`
fragment FeedContextQuery_helloproject_query_feed on HelloProjectQuery
@refetchable(queryName: "FeedContextQueryFragmentQuery") {
feed(params: $params, first: $first, after: $after) @connection(key: "FeedContextQuery_helloproject_query_feed") {
edges {
node {
id
...FeedListItemFragment
}
}
}
}
`;

type HPFeedItem = NonNullable<
NonNullable<NonNullable<NonNullable<FeedContextQuery_helloproject_query_feed$data['feed']>['edges']>[number]>['node']
>;

type FeedQueryParams = {
useMemberTaggings?: boolean;
memberIDs: string[];
assetTypes: HPAssetType[];
};

type FeedContextProviderProps = {
children: React.ReactElement;
} & FeedQueryParams;

type FeedContextModel = {
data: HPFeedItem[] | null;
isLoading: boolean;
hasNext: boolean;
reload: () => void;
loadNext: () => void;
};

function createFeedContext(): [(props: FeedContextProviderProps) => JSX.Element, () => FeedContextModel] {
const feedContext = createContext<FeedContextModel>({
data: null,
isLoading: false,
hasNext: false,
reload: () => {},
loadNext: () => {}
});

function FeedContextProvider({ children, assetTypes, memberIDs, useMemberTaggings }: FeedContextProviderProps) {
// TODO: #28 Revisit Tagged Feed Feature
// We use 30 days window when fetching tagged feed to get the items without timeout.
// but this prevents users from loading items older than 30 days.
const minPostAt = useMemberTaggings
? new Date(date.getToday().getTime() - DAYS_TO_CALCULATE_MIN_POST_AT_FOR_TAGGEING).toISOString()
: null;
// TODO: #52 Revisit the use of Relay and Suspense
// We currently don't use usePreloadedQuery or useLazyLoadQuery since it causes suspense fallback,
// which do not support concurrent rendering and it is hard to implement "Pull To Refresh" by VirtualizedList
// components such as FlatList, SectionList, ...etc.
// We intentionally use fetchQuery() with 'isLoading' state control for "Pull To Refresh".
const env = useRelayEnvironment();
const [isLoading, setIsLoading] = useState(true);
const [fetchCount, setFetchCount] = useState(0);
const [data, setData] = useState<FeedContextQuery$data | null>(null);
useEffect(() => {
(async () => {
setIsLoading(true);
const result = await fetchQuery<FeedContextQuery>(env, FeedContextQueryGraphQL, {
first: NUM_FEED_ITEMS_PER_LOAD,
params: {
assetTypes,
memberIDs,
useMemberTaggings,
minPostAt
}
}).toPromise();
setData(result!);
setIsLoading(false);
})();
}, [fetchCount, assetTypes, memberIDs, useMemberTaggings, minPostAt, env]);
const reload = () => {
setFetchCount(fetchCount + 1);
};
if (data === null) {
return (
<FeedContextRenderer data={null} isLoading={isLoading} reload={reload}>
{children}
</FeedContextRenderer>
);
}
return (
<FeedContextPaginationRenderer fragment={data} isLoading={isLoading} reload={reload}>
{children}
</FeedContextPaginationRenderer>
);
}
function FeedContextPaginationRenderer({
children,
isLoading,
reload,
fragment
}: {
children: React.ReactElement;
isLoading: boolean;
reload: () => void;
fragment: FeedContextQuery$data;
}) {
const data = usePaginationFragment<FeedContextQueryFragmentQuery, FeedContextQuery_helloproject_query_feed$key>(
FeedContextQueryFragmentGraphQL,
fragment.helloproject!
);
return (
<FeedContextRenderer data={data} isLoading={isLoading} reload={reload}>
{children}
</FeedContextRenderer>
);
}

function FeedContextRenderer({
children,
isLoading,
reload,
data
}: {
children: React.ReactElement;
isLoading: boolean;
reload: () => void;
data: usePaginationFragmentHookType<
FeedContextQueryFragmentQuery,
FeedContextQuery_helloproject_query_feed$key,
FeedContextQuery_helloproject_query_feed$data
> | null;
}) {
const ctxValue = useMemo(() => {
return {
data: data?.data.feed?.edges?.map((e) => e!.node!) ?? null,
isLoading,
hasNext: data?.hasNext ?? false,
reload,
loadNext: () => {
if (data?.hasNext) {
data.loadNext(NUM_FEED_ITEMS_PER_LOAD);
}
}
};
}, [data]);
return <feedContext.Provider value={ctxValue}>{children}</feedContext.Provider>;
}

function useFeed() {
const value = useContext(feedContext);
return value;
}

return [FeedContextProvider, useFeed];
}

export { createFeedContext, HPFeedItem, HPAssetType, FeedContextModel };

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 046c5fd

Please sign in to comment.