Skip to content

Instantly share code, notes, and snippets.

@akodkod
Created September 15, 2023 19:08
Show Gist options
  • Save akodkod/0d950824e23442510a9eb9340a6453be to your computer and use it in GitHub Desktop.
Save akodkod/0d950824e23442510a9eb9340a6453be to your computer and use it in GitHub Desktop.
Manage likes using Zustand
import { captureException } from "@sentry/nextjs"
import toast from "react-hot-toast"
import { create } from "zustand"
import likePhoto from "@/actions/likePhoto"
import { ServerAction } from "@/actions/withServerAction"
import SignInModal from "@/components/auth/SignInModal"
import { getBaseErrorMessage } from "@/lib/helpers/error"
import { currentUser } from "@/stores/currentUser"
import { openModal } from "@/stores/modal"
type Action = "add" | "remove" | "toggle"
const OppositeAction: Record<Action, Action> = {
add: "remove",
remove: "add",
toggle: "toggle",
}
type LikesStore = {
likes: Set<string>
loaded: boolean
fetch: () => void
add: (photoId: string) => void
remove: (photoId: string) => void
toggle: (photoId: string) => void
isLiked: (photoId: string) => boolean
perform: (action: Action, photoId: string, albumId?: string) => void
}
const store = create<LikesStore>((set, get) => ({
likes: new Set(),
loaded: false,
fetch: async () => {
try {
const response = await fetch("/api/likes/all")
const data = await response.json()
set({ likes: data, loaded: true })
} catch (error) {
captureException(error)
}
},
add: (photoId) => {
const likes = new Set(get().likes)
likes.add(photoId)
set({ likes })
},
remove: (photoId) => {
const likes = new Set(get().likes)
likes.delete(photoId)
set({ likes })
},
toggle: (photoId) => {
const likes = new Set(get().likes)
if (likes.has(photoId)) {
likes.delete(photoId)
} else {
likes.add(photoId)
}
set({ likes })
},
isLiked: (photoId) => new Set(get().likes).has(photoId),
perform: async (action, photoId, albumId) => {
if (!currentUser()) {
openModal(<SignInModal text="You need to sign in to like photos" />)
return
}
get()[action](photoId)
const rollback = () => get()[OppositeAction[action]](photoId)
try {
const [status, _, errors] = await likePhoto(photoId, albumId, action)
if (status === ServerAction.Failure) {
rollback()
toast.error(getBaseErrorMessage(errors) || "Something went wrong")
}
} catch (error) {
rollback()
captureException(error)
toast.error("Something went wrong")
}
},
}))
export const useIsLikesLoaded = () => store((state) => state.loaded)
export const useIsLiked = () => store((state) => state.isLiked)
export const fetchLikes = async () => store.getState().fetch()
export const addLike = (photoId: string, albumId?: string) => store.getState().perform("add", photoId, albumId)
export const removeLike = (photoId: string, albumId?: string) => store.getState().perform("remove", photoId, albumId)
export const toggleLike = (photoId: string, albumId?: string) => store.getState().perform("toggle", photoId, albumId)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment