add React Query and hook up to existing functionality (#1358)
* add React Query and hook up to existing functionality * wire in remote data, add error message * remove hard-coded feeds * oops fix logic * add loading state * fix loading on mobile --------- Co-authored-by: Eric Bailey <git@esb.lol>zio/stable
parent
84b7edd9db
commit
188d4893f9
|
@ -53,6 +53,7 @@
|
||||||
"@segment/analytics-react-native": "^2.10.1",
|
"@segment/analytics-react-native": "^2.10.1",
|
||||||
"@segment/sovran-react-native": "^0.4.5",
|
"@segment/sovran-react-native": "^0.4.5",
|
||||||
"@sentry/react-native": "5.5.0",
|
"@sentry/react-native": "5.5.0",
|
||||||
|
"@tanstack/react-query": "^4.33.0",
|
||||||
"@tiptap/core": "^2.0.0-beta.220",
|
"@tiptap/core": "^2.0.0-beta.220",
|
||||||
"@tiptap/extension-document": "^2.0.0-beta.220",
|
"@tiptap/extension-document": "^2.0.0-beta.220",
|
||||||
"@tiptap/extension-hard-break": "^2.0.3",
|
"@tiptap/extension-hard-break": "^2.0.3",
|
||||||
|
|
|
@ -16,6 +16,8 @@ import * as notifications from 'lib/notifications/notifications'
|
||||||
import * as analytics from 'lib/analytics/analytics'
|
import * as analytics from 'lib/analytics/analytics'
|
||||||
import * as Toast from './view/com/util/Toast'
|
import * as Toast from './view/com/util/Toast'
|
||||||
import {handleLink} from './Navigation'
|
import {handleLink} from './Navigation'
|
||||||
|
import {QueryClientProvider} from '@tanstack/react-query'
|
||||||
|
import {queryClient} from 'lib/react-query'
|
||||||
|
|
||||||
SplashScreen.preventAutoHideAsync()
|
SplashScreen.preventAutoHideAsync()
|
||||||
|
|
||||||
|
@ -51,6 +53,7 @@ const App = observer(function AppImpl() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<ThemeProvider theme={rootStore.shell.colorMode}>
|
<ThemeProvider theme={rootStore.shell.colorMode}>
|
||||||
<RootSiblingParent>
|
<RootSiblingParent>
|
||||||
<analytics.Provider>
|
<analytics.Provider>
|
||||||
|
@ -62,6 +65,7 @@ const App = observer(function AppImpl() {
|
||||||
</analytics.Provider>
|
</analytics.Provider>
|
||||||
</RootSiblingParent>
|
</RootSiblingParent>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@ import {Shell} from './view/shell/index'
|
||||||
import {ToastContainer} from './view/com/util/Toast.web'
|
import {ToastContainer} from './view/com/util/Toast.web'
|
||||||
import {ThemeProvider} from 'lib/ThemeContext'
|
import {ThemeProvider} from 'lib/ThemeContext'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
|
import {QueryClientProvider} from '@tanstack/react-query'
|
||||||
|
import {queryClient} from 'lib/react-query'
|
||||||
|
|
||||||
const App = observer(function AppImpl() {
|
const App = observer(function AppImpl() {
|
||||||
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
|
const [rootStore, setRootStore] = useState<RootStoreModel | undefined>(
|
||||||
|
@ -30,6 +32,7 @@ const App = observer(function AppImpl() {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<QueryClientProvider client={queryClient}>
|
||||||
<ThemeProvider theme={rootStore.shell.colorMode}>
|
<ThemeProvider theme={rootStore.shell.colorMode}>
|
||||||
<RootSiblingParent>
|
<RootSiblingParent>
|
||||||
<analytics.Provider>
|
<analytics.Provider>
|
||||||
|
@ -42,6 +45,7 @@ const App = observer(function AppImpl() {
|
||||||
</analytics.Provider>
|
</analytics.Provider>
|
||||||
</RootSiblingParent>
|
</RootSiblingParent>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -148,110 +148,3 @@ export const HITSLOP_10 = createHitslop(10)
|
||||||
export const HITSLOP_20 = createHitslop(20)
|
export const HITSLOP_20 = createHitslop(20)
|
||||||
export const HITSLOP_30 = createHitslop(30)
|
export const HITSLOP_30 = createHitslop(30)
|
||||||
export const BACK_HITSLOP = HITSLOP_30
|
export const BACK_HITSLOP = HITSLOP_30
|
||||||
|
|
||||||
export const RECOMMENDED_FEEDS = [
|
|
||||||
{
|
|
||||||
did: 'did:plc:hsqwcidfez66lwm3gxhfv5in',
|
|
||||||
rkey: 'aaaf2pqeodmpy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:gekdk2nd47gkk3utfz2xf7cn',
|
|
||||||
rkey: 'aaap4tbjcfe5y',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:5rw2on4i56btlcajojaxwcat',
|
|
||||||
rkey: 'aaao6g552b33o',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:jfhpnnst6flqway4eaeqzj2a',
|
|
||||||
rkey: 'for-science',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:7q4nnnxawajbfaq7to5dpbsy',
|
|
||||||
rkey: 'bsky-news',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:jcoy7v3a2t4rcfdh6i4kza25',
|
|
||||||
rkey: 'astro',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:tenurhgjptubkk5zf5qhi3og',
|
|
||||||
rkey: 'h-nba',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:vpkhqolt662uhesyj6nxm7ys',
|
|
||||||
rkey: 'devfeed',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:cndfx4udwgvpjaakvxvh7wm5',
|
|
||||||
rkey: 'flipboard-tech',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:w4xbfzo7kqfes5zb7r6qv3rw',
|
|
||||||
rkey: 'blacksky',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:lptjvw6ut224kwrj7ub3sqbe',
|
|
||||||
rkey: 'aaaotfjzjplna',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:gkvpokm7ec5j5yxls6xk4e3z',
|
|
||||||
rkey: 'formula-one',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:q6gjnaw2blty4crticxkmujt',
|
|
||||||
rkey: 'positivifeed',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:l72uci4styb4jucsgcrrj5ap',
|
|
||||||
rkey: 'aaao5dzfm36u4',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:k3jkadxv5kkjgs6boyon7m6n',
|
|
||||||
rkey: 'aaaavlyvqzst2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:nkahctfdi6bxk72umytfwghw',
|
|
||||||
rkey: 'aaado2uvfsc6w',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:epihigio3d7un7u3gpqiy5gv',
|
|
||||||
rkey: 'aaaekwsc7zsvs',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:qiknc4t5rq7yngvz7g4aezq7',
|
|
||||||
rkey: 'aaaejxlobe474',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:mlq4aycufcuolr7ax6sezpc4',
|
|
||||||
rkey: 'aaaoudweck6uy',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:rcez5hcvq3vzlu5x7xrjyccg',
|
|
||||||
rkey: 'aaadzjxbcddzi',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:lnxbuzaenlwjrncx6sc4cfdr',
|
|
||||||
rkey: 'aaab2vesjtszc',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:x3cya3wkt4n6u4ihmvpsc5if',
|
|
||||||
rkey: 'aaacynbxwimok',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:abv47bjgzjgoh3yrygwoi36x',
|
|
||||||
rkey: 'aaagt6amuur5e',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:ffkgesg3jsv2j7aagkzrtcvt',
|
|
||||||
rkey: 'aaacjerk7gwek',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:geoqe3qls5mwezckxxsewys2',
|
|
||||||
rkey: 'aaai43yetqshu',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
did: 'did:plc:2wqomm3tjqbgktbrfwgvrw34',
|
|
||||||
rkey: 'authors',
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
import {QueryClient} from '@tanstack/react-query'
|
||||||
|
|
||||||
|
export const queryClient = new QueryClient()
|
|
@ -1,5 +1,5 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import {FlatList, StyleSheet, View} from 'react-native'
|
import {ActivityIndicator, FlatList, StyleSheet, View} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints'
|
import {TabletOrDesktop, Mobile} from 'view/com/util/layouts/Breakpoints'
|
||||||
|
@ -10,7 +10,10 @@ import {Button} from 'view/com/util/forms/Button'
|
||||||
import {RecommendedFeedsItem} from './RecommendedFeedsItem'
|
import {RecommendedFeedsItem} from './RecommendedFeedsItem'
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {RECOMMENDED_FEEDS} from 'lib/constants'
|
import {useQuery} from '@tanstack/react-query'
|
||||||
|
import {useStores} from 'state/index'
|
||||||
|
import {CustomFeedModel} from 'state/models/feeds/custom-feed'
|
||||||
|
import {ErrorMessage} from 'view/com/util/error/ErrorMessage'
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
next: () => void
|
next: () => void
|
||||||
|
@ -18,8 +21,31 @@ type Props = {
|
||||||
export const RecommendedFeeds = observer(function RecommendedFeedsImpl({
|
export const RecommendedFeeds = observer(function RecommendedFeedsImpl({
|
||||||
next,
|
next,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
|
const store = useStores()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const {isTabletOrMobile} = useWebMediaQueries()
|
const {isTabletOrMobile} = useWebMediaQueries()
|
||||||
|
const {isLoading, data: recommendedFeeds} = useQuery({
|
||||||
|
staleTime: Infinity, // fixed list rn, never refetch
|
||||||
|
queryKey: ['onboarding', 'recommended_feeds'],
|
||||||
|
async queryFn() {
|
||||||
|
try {
|
||||||
|
const {
|
||||||
|
data: {feeds},
|
||||||
|
success,
|
||||||
|
} = await store.agent.app.bsky.feed.getSuggestedFeeds()
|
||||||
|
|
||||||
|
if (!success) return
|
||||||
|
|
||||||
|
return (feeds.length ? feeds : []).map(feed => {
|
||||||
|
return new CustomFeedModel(store, feed)
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasFeeds = recommendedFeeds && recommendedFeeds.length
|
||||||
|
|
||||||
const title = (
|
const title = (
|
||||||
<>
|
<>
|
||||||
|
@ -86,12 +112,20 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({
|
||||||
horizontal
|
horizontal
|
||||||
titleStyle={isTabletOrMobile ? undefined : {minWidth: 470}}
|
titleStyle={isTabletOrMobile ? undefined : {minWidth: 470}}
|
||||||
contentStyle={{paddingHorizontal: 0}}>
|
contentStyle={{paddingHorizontal: 0}}>
|
||||||
|
{hasFeeds ? (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={RECOMMENDED_FEEDS}
|
data={recommendedFeeds}
|
||||||
renderItem={({item}) => <RecommendedFeedsItem {...item} />}
|
renderItem={({item}) => <RecommendedFeedsItem item={item} />}
|
||||||
keyExtractor={item => item.did + item.rkey}
|
keyExtractor={item => item.uri}
|
||||||
style={{flex: 1}}
|
style={{flex: 1}}
|
||||||
/>
|
/>
|
||||||
|
) : isLoading ? (
|
||||||
|
<View>
|
||||||
|
<ActivityIndicator size="large" />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<ErrorMessage message="Failed to load recommended feeds" />
|
||||||
|
)}
|
||||||
</TitleColumnLayout>
|
</TitleColumnLayout>
|
||||||
</TabletOrDesktop>
|
</TabletOrDesktop>
|
||||||
<Mobile>
|
<Mobile>
|
||||||
|
@ -106,12 +140,20 @@ export const RecommendedFeeds = observer(function RecommendedFeedsImpl({
|
||||||
pinned feeds.
|
pinned feeds.
|
||||||
</Text>
|
</Text>
|
||||||
|
|
||||||
|
{hasFeeds ? (
|
||||||
<FlatList
|
<FlatList
|
||||||
data={RECOMMENDED_FEEDS}
|
data={recommendedFeeds}
|
||||||
renderItem={({item}) => <RecommendedFeedsItem {...item} />}
|
renderItem={({item}) => <RecommendedFeedsItem item={item} />}
|
||||||
keyExtractor={item => item.did + item.rkey}
|
keyExtractor={item => item.uri}
|
||||||
style={{flex: 1}}
|
style={{flex: 1}}
|
||||||
/>
|
/>
|
||||||
|
) : isLoading ? (
|
||||||
|
<View>
|
||||||
|
<ActivityIndicator size="large" />
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<ErrorMessage message="Failed to load recommended feeds" />
|
||||||
|
)}
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onPress={next}
|
onPress={next}
|
||||||
|
|
|
@ -8,22 +8,17 @@ import {UserAvatar} from 'view/com/util/UserAvatar'
|
||||||
import * as Toast from 'view/com/util/Toast'
|
import * as Toast from 'view/com/util/Toast'
|
||||||
import {HeartIcon} from 'lib/icons'
|
import {HeartIcon} from 'lib/icons'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {useCustomFeed} from 'lib/hooks/useCustomFeed'
|
|
||||||
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
|
||||||
import {makeRecordUri} from 'lib/strings/url-helpers'
|
|
||||||
import {sanitizeHandle} from 'lib/strings/handles'
|
import {sanitizeHandle} from 'lib/strings/handles'
|
||||||
|
import {CustomFeedModel} from 'state/models/feeds/custom-feed'
|
||||||
|
|
||||||
export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({
|
export const RecommendedFeedsItem = observer(function RecommendedFeedsItemImpl({
|
||||||
did,
|
item,
|
||||||
rkey,
|
|
||||||
}: {
|
}: {
|
||||||
did: string
|
item: CustomFeedModel
|
||||||
rkey: string
|
|
||||||
}) {
|
}) {
|
||||||
const {isMobile} = useWebMediaQueries()
|
const {isMobile} = useWebMediaQueries()
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const uri = makeRecordUri(did, 'app.bsky.feed.generator', rkey)
|
|
||||||
const item = useCustomFeed(uri)
|
|
||||||
if (!item) return null
|
if (!item) return null
|
||||||
const onToggle = async () => {
|
const onToggle = async () => {
|
||||||
if (item.isSaved) {
|
if (item.isSaved) {
|
||||||
|
|
15
yarn.lock
15
yarn.lock
|
@ -6005,6 +6005,19 @@
|
||||||
"@svgr/plugin-svgo" "^5.5.0"
|
"@svgr/plugin-svgo" "^5.5.0"
|
||||||
loader-utils "^2.0.0"
|
loader-utils "^2.0.0"
|
||||||
|
|
||||||
|
"@tanstack/query-core@4.33.0":
|
||||||
|
version "4.33.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/query-core/-/query-core-4.33.0.tgz#7756da9a75a424e521622b1d84eb55b7a2b33715"
|
||||||
|
integrity sha512-qYu73ptvnzRh6se2nyBIDHGBQvPY1XXl3yR769B7B6mIDD7s+EZhdlWHQ67JI6UOTFRaI7wupnTnwJ3gE0Mr/g==
|
||||||
|
|
||||||
|
"@tanstack/react-query@^4.33.0":
|
||||||
|
version "4.33.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/@tanstack/react-query/-/react-query-4.33.0.tgz#e927b0343a6ecaa948fee59e9ca98fe561062638"
|
||||||
|
integrity sha512-97nGbmDK0/m0B86BdiXzx3EW9RcDYKpnyL2+WwyuLHEgpfThYAnXFaMMmnTDuAO4bQJXEhflumIEUfKmP7ESGA==
|
||||||
|
dependencies:
|
||||||
|
"@tanstack/query-core" "4.33.0"
|
||||||
|
use-sync-external-store "^1.2.0"
|
||||||
|
|
||||||
"@testing-library/jest-native@^5.4.1":
|
"@testing-library/jest-native@^5.4.1":
|
||||||
version "5.4.2"
|
version "5.4.2"
|
||||||
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-5.4.2.tgz#6b0c987cc57f8d900763e763025d00d26ccbc85f"
|
resolved "https://registry.yarnpkg.com/@testing-library/jest-native/-/jest-native-5.4.2.tgz#6b0c987cc57f8d900763e763025d00d26ccbc85f"
|
||||||
|
@ -19293,7 +19306,7 @@ use-sidecar@^1.1.2:
|
||||||
detect-node-es "^1.1.0"
|
detect-node-es "^1.1.0"
|
||||||
tslib "^2.0.0"
|
tslib "^2.0.0"
|
||||||
|
|
||||||
use-sync-external-store@^1.0.0:
|
use-sync-external-store@^1.0.0, use-sync-external-store@^1.2.0:
|
||||||
version "1.2.0"
|
version "1.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
|
||||||
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
|
||||||
|
|
Loading…
Reference in New Issue