Skip to content

Commit bcee815

Browse files
authored
Merge pull request #5 from unaisdev/react-query-refactor
React query refactor
2 parents ed08f70 + c4e52c2 commit bcee815

17 files changed

Lines changed: 463 additions & 368 deletions

File tree

App.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ import MyStatusBar from '@app/features/commons/components/StatusBar';
1515

1616
import {BottomSheetHomeProvider} from '@app/containers/BottomSheetHomeContext';
1717
import GlobalContext from '@app/containers';
18-
19-
export const queryClient = new QueryClient();
18+
import {queryClient} from '@app/lib/react-query';
2019

2120
const App = () => {
2221
return (

android/fastlane/Fastfile

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,6 @@ platform :android do
2323

2424
desc "Deploy a new version to the Google Play"
2525
lane :deploy do |options|
26-
# Convertir el tipo de versión a número
27-
version_map = {
28-
'major' => '1',
29-
'minor' => '2',
30-
'patch' => '3'
31-
}
32-
version_type = version_map[options[:version_type]] || '3'
33-
34-
# Ejecutar el script con el tipo de versión como argumento
35-
sh "echo #{version_type} | ./bump-release-numbers.sh"
36-
3726
# Leer las variables de GitHub Secrets directamente
3827
store_file = ENV["MYAPP_UPLOAD_STORE_FILE"]
3928
store_password = ENV["MYAPP_UPLOAD_STORE_PASSWORD"]
@@ -68,5 +57,26 @@ platform :android do
6857
skip_upload_images: true,
6958
skip_upload_screenshots: true
7059
)
60+
61+
# --- AHORA ACTUALIZAMOS LA VERSIÓN PARA LA SIGUIENTE BUILD ---
62+
version_map = {
63+
'major' => '1',
64+
'minor' => '2',
65+
'patch' => '3'
66+
}
67+
version_type = version_map[options[:version_type]] || '3'
68+
69+
# Ejecutar el script con el tipo de versión como argumento
70+
sh "echo #{version_type} | ./bump-release-numbers.sh"
71+
72+
# --- SUBIR LOS CAMBIOS A GIT ---
73+
commit_message = "🔖 Bump versión de desarrollo automática después de release"
74+
branch_name = sh("git rev-parse --abbrev-ref HEAD").strip
75+
76+
sh "git add ."
77+
sh "git commit -m '#{commit_message}'"
78+
sh "git push origin #{branch_name}"
79+
80+
puts "✅ Versión subida a Google Play y número de versión actualizado en Git."
7181
end
7282
end

android/fastlane/bump-release-numbers.sh

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ fi
3030
IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT_VERSION_NAME"
3131

3232
# Lee la opción desde stdin
33+
echo "Seleccione el tipo de incremento:"
34+
echo "1) Major"
35+
echo "2) Minor"
36+
echo "3) Patch"
3337
read -r CHOICE
3438

3539
# Valida y procesa la opción
@@ -57,19 +61,23 @@ esac
5761

5862
# Forma la nueva versión
5963
NEW_VERSION_NAME="$MAJOR.$MINOR.$PATCH"
60-
NEW_VERSION_CODE=$((CURRENT_VERSION_CODE + 1))
64+
NEW_VERSION_CODE="$MAJOR$MINOR$PATCH"
6165

6266
# Crea un archivo temporal
6367
TMP_FILE=$(mktemp)
6468

65-
# Actualiza el archivo build.gradle usando sed con un archivo temporal
69+
# Actualiza el archivo build.gradle usando sed con compatibilidad universal
6670
sed "s/versionCode $CURRENT_VERSION_CODE/versionCode $NEW_VERSION_CODE/" "$GRADLE_FILE" > "$TMP_FILE"
67-
sed -i "s/versionName \"$CURRENT_VERSION_NAME\"/versionName \"$NEW_VERSION_NAME\"/" "$TMP_FILE"
71+
if [[ "$OSTYPE" == "darwin"* ]]; then
72+
sed -i '' "s/versionName \"$CURRENT_VERSION_NAME\"/versionName \"$NEW_VERSION_NAME\"/" "$TMP_FILE"
73+
else
74+
sed -i "s/versionName \"$CURRENT_VERSION_NAME\"/versionName \"$NEW_VERSION_NAME\"/" "$TMP_FILE"
75+
fi
6876

6977
# Mueve el archivo temporal al original
7078
mv "$TMP_FILE" "$GRADLE_FILE"
7179

7280
echo "versionCode actualizado de $CURRENT_VERSION_CODE a $NEW_VERSION_CODE"
7381
echo "versionName actualizado de $CURRENT_VERSION_NAME a $NEW_VERSION_NAME"
7482

75-
exit 0
83+
exit 0
Lines changed: 7 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,15 @@
1-
import React, {useEffect, useMemo, useState} from 'react';
2-
1+
import {useMemo} from 'react';
32
import {useUserContext} from '@app/containers/UserContext';
43
import {useTheme} from '@app/features/commons/theme/hooks/useTheme';
54
import {usePlaylistAllTracks} from '@app/features/commons/hooks/usePlaylistAllTracks';
6-
import {
7-
isSavedPlaylistForNotify,
8-
removePlaylistForNotify,
9-
savePlaylistForNotify,
10-
} from '@app/services/playlist';
11-
import {queryClient} from '../../../../../../../App';
5+
import {useToggleNotify} from '@app/features/commons/hooks/useToggleNotify';
126

137
export const useNotifyMeButton = (playlistId: string) => {
148
const {isDarkMode} = useTheme();
15-
16-
const [isSaved, setIsSaved] = useState(false);
17-
const [loadingToggle, setLoadingToggle] = useState(false);
18-
const [checkingSaved, setCheckingSaved] = useState(false);
199
const {tracks, hasNextPage} = usePlaylistAllTracks(playlistId);
20-
const {user} = useUserContext();
21-
22-
const checkPlaylistForNotify = async () => {
23-
if (user) {
24-
setCheckingSaved(true);
25-
const isSaved = await isSavedPlaylistForNotify(playlistId, user?.id);
26-
setIsSaved(isSaved ?? false);
27-
setCheckingSaved(false);
28-
return isSaved;
29-
}
30-
};
3110

32-
useEffect(() => {
33-
const init = async () => {
34-
const isSaved = await checkPlaylistForNotify();
35-
setIsSaved(isSaved ?? false);
36-
};
37-
38-
init();
39-
}, []);
11+
const {toggleNotify, isSaved, checkingSaved, loadingToggle} =
12+
useToggleNotify(playlistId);
4013

4114
const iconProps = useMemo(() => {
4215
if (isSaved)
@@ -49,37 +22,18 @@ export const useNotifyMeButton = (playlistId: string) => {
4922
color: 'gray',
5023
iconName: 'material-symbols:notifications-off-outline-rounded',
5124
};
52-
}, [isSaved]);
25+
}, [isSaved, isDarkMode]);
5326

5427
const togglePlaylistSave = async () => {
55-
if (user) {
56-
setLoadingToggle(true); // Show loading indicator
57-
58-
try {
59-
if (!isSaved) {
60-
// Always save the playlist, regardless of hasNextPage
61-
await savePlaylistForNotify(playlistId, tracks, user?.id);
62-
queryClient.invalidateQueries({queryKey: ['userPlaylists']});
63-
setIsSaved(true);
64-
} else {
65-
await removePlaylistForNotify(playlistId, user?.id);
66-
queryClient.invalidateQueries({queryKey: ['userPlaylists']});
67-
setIsSaved(false);
68-
}
69-
} catch (error) {
70-
console.error('Error saving/removing playlist:', error);
71-
}
72-
73-
setLoadingToggle(false); // Hide loading indicator
74-
}
28+
toggleNotify(tracks);
7529
};
7630

7731
return {
7832
loadingToggle,
7933
checkingSaved,
8034
iconProps,
8135
canSavePlaylist: hasNextPage,
82-
checkPlaylistForNotify,
36+
isSaved,
8337
togglePlaylistSave,
8438
};
8539
};

app/features/commons/components/Header/NotifyMeButton/index.tsx

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,17 +24,20 @@ const NotifyMeButton = ({id}: Props) => {
2424
checkingSaved,
2525
} = useNotifyMeButton(id);
2626

27-
if (checkingSaved)
27+
const showLoading = checkingSaved || canSavePlaylist;
28+
const showSavingLoading = loadingToggle || checkingSaved || canSavePlaylist;
29+
30+
if (showLoading)
2831
return (
2932
<ActivityIndicator style={styles.inline} size="small" color="gray" />
3033
);
3134

3235
return (
3336
<View style={styles.inline}>
34-
{loadingToggle && <ActivityIndicator size="small" color="gray" />}
37+
{showSavingLoading && <ActivityIndicator size="small" color="gray" />}
3538
<TouchableOpacity
36-
onPress={togglePlaylistSave}
37-
disabled={canSavePlaylist || loadingToggle}>
39+
disabled={showSavingLoading}
40+
onPress={togglePlaylistSave}>
3841
<Monicon name={iconProps.iconName} size={26} color={iconProps.color} />
3942
</TouchableOpacity>
4043
</View>
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import {useMutation, useQuery, useQueryClient} from '@tanstack/react-query';
2+
import {
3+
removePlaylistForNotify,
4+
savePlaylistForNotify,
5+
isSavedPlaylistForNotify,
6+
} from '@app/services/playlist';
7+
import {PlaylistItem, UserAddedPlaylistsResponse} from '@app/services/types';
8+
import {QUERY_KEYS} from '@app/lib/queryKeys';
9+
import {useUserContext} from '@app/containers/UserContext';
10+
11+
export const useNotifyStatus = (playlistId: string) => {
12+
const {user} = useUserContext();
13+
14+
return useQuery({
15+
queryKey: QUERY_KEYS.NOTIFY.playlist(playlistId, user?.id),
16+
queryFn: () => isSavedPlaylistForNotify(playlistId, user?.id ?? ''),
17+
enabled: !!user?.id,
18+
staleTime: 5 * 60 * 1000, // 5 minutos
19+
});
20+
};
21+
22+
export const useSaveNotify = (playlistId: string) => {
23+
const {user} = useUserContext();
24+
const queryClient = useQueryClient();
25+
26+
return useMutation({
27+
mutationFn: async ({tracks}: {tracks: PlaylistItem[]}) => {
28+
if (!user?.id) throw new Error('User ID is required');
29+
// Actualización optimista inmediata
30+
return savePlaylistForNotify(playlistId, tracks, user?.id);
31+
},
32+
onSuccess: () => {
33+
queryClient.setQueryData(
34+
QUERY_KEYS.NOTIFY.playlist(playlistId, user?.id),
35+
true,
36+
);
37+
queryClient.refetchQueries([QUERY_KEYS.USER_NOTIFIED_PLAYLISTS]);
38+
},
39+
onError: () => {
40+
queryClient.setQueryData(
41+
QUERY_KEYS.NOTIFY.playlist(playlistId, user?.id),
42+
false,
43+
);
44+
},
45+
});
46+
};
47+
48+
export const useRemoveNotify = (playlistId: string) => {
49+
const {user} = useUserContext();
50+
const queryClient = useQueryClient();
51+
52+
return useMutation({
53+
mutationFn: async () => {
54+
if (!user?.id) throw new Error('User ID is required');
55+
queryClient.setQueryData(
56+
QUERY_KEYS.NOTIFY.playlist(playlistId, user?.id),
57+
false,
58+
);
59+
60+
return removePlaylistForNotify(playlistId, user?.id);
61+
},
62+
onSuccess: () => {
63+
console.log(
64+
'onsuccedss',
65+
queryClient.getQueryData([QUERY_KEYS.USER_NOTIFIED_PLAYLISTS]),
66+
);
67+
68+
queryClient.setQueryData<UserAddedPlaylistsResponse[]>(
69+
[QUERY_KEYS.USER_NOTIFIED_PLAYLISTS],
70+
oldData =>
71+
oldData?.filter(item => item.playlistId !== playlistId) ?? [],
72+
);
73+
},
74+
onError: () => {
75+
console.log('onError');
76+
77+
queryClient.setQueryData(
78+
QUERY_KEYS.NOTIFY.playlist(playlistId, user?.id),
79+
true,
80+
);
81+
82+
queryClient.setQueryData<UserAddedPlaylistsResponse[]>(
83+
[QUERY_KEYS.USER_NOTIFIED_PLAYLISTS],
84+
oldData =>
85+
oldData?.filter(item => item.playlistId !== playlistId) ?? [],
86+
);
87+
},
88+
});
89+
};

app/features/commons/hooks/usePlaylist.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,18 @@
11
import {useQuery} from '@tanstack/react-query';
22
import {getPlaylist} from '../../../services/playlist';
3+
import {QUERY_KEYS} from '@app/lib/queryKeys';
34

45
interface Props {
56
playlistId: string;
67
}
78

89
export const usePlaylist = ({playlistId}: Props) => {
910
const playlistReq = useQuery({
10-
queryKey: ['playlist', playlistId],
11+
queryKey: [QUERY_KEYS.PLAYLIST_DETAIL, playlistId],
1112
queryFn: () => getPlaylist(playlistId),
1213
retry: 3,
14+
staleTime: 5 * 60 * 1000, // 5 minutos
15+
cacheTime: 30 * 60 * 1000, // 30 minutos
1316
});
1417

1518
return playlistReq;

app/features/commons/hooks/usePlaylistAllTracks.ts

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
import {useInfiniteQuery} from '@tanstack/react-query';
22
import {getPlaylistTracks} from '../../../services/playlist';
33

4-
export const usePlaylistAllTracks = (playlistId: string) => {
5-
const fetchTracks = async ({pageParam = ''}) => {
6-
const res = await getPlaylistTracks(playlistId, pageParam);
7-
return res;
8-
};
4+
import React, {useEffect} from 'react';
5+
import {QUERY_KEYS} from '@app/lib/queryKeys';
96

7+
export const usePlaylistAllTracks = (playlistId: string) => {
108
const {
119
data,
1210
fetchNextPage,
@@ -16,18 +14,26 @@ export const usePlaylistAllTracks = (playlistId: string) => {
1614
error,
1715
refetch,
1816
} = useInfiniteQuery({
19-
queryKey: ['playlistAllTracks', playlistId],
20-
queryFn: fetchTracks,
21-
getNextPageParam: (lastPage, allPages) => {
22-
return lastPage?.next;
23-
},
17+
queryKey: QUERY_KEYS.playlistTracks(playlistId),
18+
queryFn: ({pageParam = ''}) => getPlaylistTracks(playlistId, pageParam),
19+
getNextPageParam: lastPage => lastPage?.next || undefined,
2420
keepPreviousData: true,
21+
staleTime: 5 * 60 * 1000, // 5 minutos
22+
cacheTime: 30 * 60 * 1000, // 30 minutos
2523
});
2624

27-
if (hasNextPage) fetchNextPage();
25+
// Memoizar el flatMap para evitar recálculos innecesarios
26+
const tracks = React.useMemo(
27+
() => data?.pages.flatMap(page => page?.items ?? []) ?? [],
28+
[data?.pages],
29+
);
2830

29-
//flatMapping data for getting only tracks items
30-
const tracks = data?.pages.flatMap(page => page?.items ?? []) ?? [];
31+
// Cargar siguiente página solo si hay más y no estamos ya cargando
32+
useEffect(() => {
33+
if (hasNextPage && !isFetching) {
34+
fetchNextPage();
35+
}
36+
}, [hasNextPage, isFetching, fetchNextPage]);
3137

3238
return {
3339
tracks,

0 commit comments

Comments
 (0)