From e0a732cfa4ffa2dd339eff4cb4ae2a1da458b52b Mon Sep 17 00:00:00 2001 From: zkirby Date: Wed, 6 Dec 2023 15:28:33 -0800 Subject: [PATCH 1/6] replace all user related API calls --- apps/expo/src/app/(auth)/Loader.tsx | 22 ++++++ apps/expo/src/app/(auth)/UserLoader.tsx | 35 +++++++++ apps/expo/src/app/(tabs)/(alerts)/index.tsx | 13 ++-- apps/expo/src/app/(tabs)/(profile)/index.tsx | 9 ++- apps/expo/src/app/(tabs)/_layout.tsx | 8 +- apps/expo/src/app/_layout.tsx | 11 ++- apps/expo/src/app/hooks/useUser.ts | 21 ------ apps/nextjs/src/app/_components/Button.tsx | 5 +- .../Calendar/Weekly/WeeklyCalendar.tsx | 10 +-- apps/nextjs/src/app/_components/Dropdown.tsx | 7 +- apps/nextjs/src/app/_components/Loader.tsx | 5 +- apps/nextjs/src/app/_components/Modal.tsx | 5 +- apps/nextjs/src/app/_components/Pill.tsx | 3 +- apps/nextjs/src/app/_components/SideNav.tsx | 9 +-- .../nextjs/src/app/_components/UserLoaded.tsx | 1 + .../nextjs/src/app/_components/UserLoader.tsx | 35 +++++++++ apps/nextjs/src/app/alerts/AlertsList.tsx | 5 +- apps/nextjs/src/app/layout.tsx | 7 +- apps/nextjs/src/app/route.ts | 2 +- apps/nextjs/src/app/teams/TeamsList.tsx | 9 +-- apps/nextjs/src/hooks/useUser.ts | 21 ------ packages/api/src/router/user/create.ts | 74 +++++++++++++++++++ packages/api/src/router/user/index.ts | 2 + packages/api/src/router/user/me.ts | 38 +--------- packages/db/index.ts | 9 ++- 25 files changed, 229 insertions(+), 137 deletions(-) create mode 100644 apps/expo/src/app/(auth)/Loader.tsx create mode 100644 apps/expo/src/app/(auth)/UserLoader.tsx delete mode 100644 apps/expo/src/app/hooks/useUser.ts create mode 100644 apps/nextjs/src/app/_components/UserLoaded.tsx create mode 100644 apps/nextjs/src/app/_components/UserLoader.tsx delete mode 100644 apps/nextjs/src/hooks/useUser.ts create mode 100644 packages/api/src/router/user/create.ts diff --git a/apps/expo/src/app/(auth)/Loader.tsx b/apps/expo/src/app/(auth)/Loader.tsx new file mode 100644 index 00000000..98c16d4a --- /dev/null +++ b/apps/expo/src/app/(auth)/Loader.tsx @@ -0,0 +1,22 @@ +import { ActivityIndicator } from 'react-native'; + +interface Status { + loading: boolean; + // TODO(@zkirby) + // isError: boolean; + // isSuccess: boolean; +} + +const Loader = ({ + status, + className, + children, +}: React.PropsWithChildren<{ + status: Status; + className?: string; +}>) => { + if (status.loading) return ; + return children; +}; + +export default Loader; diff --git a/apps/expo/src/app/(auth)/UserLoader.tsx b/apps/expo/src/app/(auth)/UserLoader.tsx new file mode 100644 index 00000000..740a1ae0 --- /dev/null +++ b/apps/expo/src/app/(auth)/UserLoader.tsx @@ -0,0 +1,35 @@ +import { useEffect } from 'react'; +import { api } from '../../utils/api'; +import Loader from './Loader'; + +/** + * This component is responsible for ensuring that the user *in our DB* + * is created. This is different from (but 1:1 with) the Clerk user. + * + * It's important that this mounts high in the component tree and + * prevents any children that might make API calls from rendering + * since almost every API call relies on the User being created + * in our DB. + * + * TODO(@zkirby): Think up a better solution here. We're essentially blocking + * the entire app from rendering for no reason (other than when the user is first created). + * Likely, we're not using Clerk correctly or under utilizing some functionality. + */ +const UserLoader = ({ children }: React.PropsWithChildren) => { + const ensureUserIsCreated = api.user.create.useMutation(); + + useEffect(() => { + ensureUserIsCreated.mutate(); + }, []); + + return ( + // NOTE(@zkirby): Keep spinning until we're sure the user is created, + // if we use 'isFetching' here, we'll render sub-tree once until the + // api calls kicks off. + + {children} + + ); +}; + +export default UserLoader; diff --git a/apps/expo/src/app/(tabs)/(alerts)/index.tsx b/apps/expo/src/app/(tabs)/(alerts)/index.tsx index 7efe16f3..2519b607 100644 --- a/apps/expo/src/app/(tabs)/(alerts)/index.tsx +++ b/apps/expo/src/app/(tabs)/(alerts)/index.tsx @@ -11,7 +11,6 @@ import { SafeAreaView } from 'react-native-safe-area-context'; import { Stack } from 'expo-router'; import { api } from '~/utils/api'; -import { useUser } from '../../hooks/useUser'; import AlertListItem from './_components/AlertListItem'; import type { Alert } from './alerts.types'; import { useSearch } from './hooks/useSearch'; @@ -33,7 +32,7 @@ const AlertListPage = () => { }, ], }); - const user = useUser(); + const user = api.user.me.useQuery(); const updateAlert = api.alert.update.useMutation(); const update = useCallback(async (id: string, alert: Partial) => { @@ -42,8 +41,8 @@ const AlertListPage = () => { }, []); const isLoading = useMemo( - () => updateAlert.isPending || alerts.isFetching, - [alerts.isFetching, updateAlert.isPending], + () => updateAlert.isPending || alerts.isFetching || user.isFetching, + [alerts.isFetching, updateAlert.isPending, user.isFetching], ); return ( @@ -68,7 +67,7 @@ const AlertListPage = () => { ALERTS - {isLoading || !user ? ( + {isLoading ? ( ) : ( { renderItem={({ item }) => ( update(item.id, { status: 'ACKED' })} onReopen={() => update(item.id, { status: 'OPEN' })} onClose={() => update(item.id, { status: 'CLOSED' })} onSelfAssign={() => - update(item.id, { assignedToId: user.id }) + update(item.id, { assignedToId: user.data?.user?.id }) } /> )} diff --git a/apps/expo/src/app/(tabs)/(profile)/index.tsx b/apps/expo/src/app/(tabs)/(profile)/index.tsx index 0dab4632..c20dc7a9 100644 --- a/apps/expo/src/app/(tabs)/(profile)/index.tsx +++ b/apps/expo/src/app/(tabs)/(profile)/index.tsx @@ -2,7 +2,8 @@ import { useAuth } from '@clerk/clerk-expo'; import { Stack } from 'expo-router'; import { Button, Text, View } from 'react-native'; -import { useUser } from '../../hooks/useUser'; +import { useMemo } from 'react'; +import { api } from '../../../utils/api'; const SignOut = () => { const { isLoaded, signOut } = useAuth(); @@ -23,7 +24,11 @@ const SignOut = () => { }; const ProfilePage = () => { - const user = useUser(); + const apiUser = api.user.me.useQuery(); + + const user = useMemo(() => { + return apiUser.data?.user; + }, [apiUser.data]); return ( diff --git a/apps/expo/src/app/(tabs)/_layout.tsx b/apps/expo/src/app/(tabs)/_layout.tsx index 4c9ee506..97320fc1 100644 --- a/apps/expo/src/app/(tabs)/_layout.tsx +++ b/apps/expo/src/app/(tabs)/_layout.tsx @@ -2,8 +2,8 @@ import FeatherIcons from '@expo/vector-icons/Feather'; import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; import { Text, View } from 'react-native'; +import { api } from '../../utils/api'; import { useNotification } from '../hooks/useNotification'; -import { useUser } from '../hooks/useUser'; import AlertListPage from './(alerts)'; import ProfilePage from './(profile)'; @@ -11,7 +11,7 @@ const Tab = createBottomTabNavigator(); const TabsLayout = () => { useNotification(); - const user = useUser(); + const user = api.user.me.useQuery(); return ( { options={{ tabBarIcon: ({ size }) => { const initials = - (user?.firstName?.charAt(0) ?? '') + - (user?.lastName?.charAt(0) ?? ''); + (user.data?.user?.firstName?.charAt(0) ?? '') + + (user.data?.user?.lastName?.charAt(0) ?? ''); return ( { > - - - - + + + + + + diff --git a/apps/expo/src/app/hooks/useUser.ts b/apps/expo/src/app/hooks/useUser.ts deleted file mode 100644 index 70142b05..00000000 --- a/apps/expo/src/app/hooks/useUser.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { useEffect, useState } from 'react'; - -import type { RouterOutputs } from '~/utils/api'; -import { api } from '~/utils/api'; - -/** - * Hook for retrieving or creating the user - */ -export function useUser() { - const [user, setUser] = useState(); - - const createOrGetUser = api.user.me.useMutation({ - onSuccess: (result) => result?.user && setUser(result.user), - }); - - useEffect(() => { - createOrGetUser.mutate(); - }, []); - - return user; -} diff --git a/apps/nextjs/src/app/_components/Button.tsx b/apps/nextjs/src/app/_components/Button.tsx index debfe458..eb6ea2c1 100644 --- a/apps/nextjs/src/app/_components/Button.tsx +++ b/apps/nextjs/src/app/_components/Button.tsx @@ -14,13 +14,12 @@ const Button = ({ onClick, disabled, children, -}: { +}: React.PropsWithChildren<{ type?: ButtonType; className?: string; onClick?: () => void | Promise; disabled?: boolean; - children: React.ReactNode | React.ReactNode[]; -}) => ( +}>) => (