While building a blog app, I want to create a user interaction such as like, dislike and share. However, I want the users to be anonymous (not authenticated). How can I achieve this to display counts for like, dislike and share in Vue 3 JS? I want to fetch the interactions from the backend, as the app mounts (onMounted) lifescycle hook. But when I refresh the page it displays the initial values like(0), dislike (0) and share(0).
SocialMediaButtons.vue:
<template>
<div>
<button @click="likePost" :disabled="isLiked">
<font-awesome-icon icon="thumbs-up" style="color: green; font-size: 24px; margin-right: 50px;" /> Like ({{ interactions.like }})
</button>
<button @click="dislikePost" :disabled="isDisliked">
<font-awesome-icon icon="thumbs-down" style="color: red; font-size: 24px; margin-right: 50px;" /> Dislike ({{ interactions.dislike }})
</button>
<button @click="sharePost" :disabled="isShared">
<font-awesome-icon icon="fa-solid fa-share" style="color: #0077B5; font-size: 24px; margin-right: 50px;" /> Share ({{ interactions.share }})
</button>
<div class="social-media-icons" style="display: flex; justify-content: center; align-items: center; height: 10vh;">
Share this post on:
<button @click="shareOnFacebook">
<font-awesome-icon :icon="facebookIcon" class="fa-2x icon-facebook" style="margin-right: 50px;" />
</button>
<button @click="shareOnLinkedIn">
<font-awesome-icon :icon="['fab', 'linkedin']" class="fa-2x icon-linkedin" style="margin-right: 50px;" />
</button>
</div>
</div>
</template>
<script setup>
import { ref, computed, watch, onMounted } from 'vue';
import { useRoute } from 'vue-router';
import { useQuery } from '@vue/apollo-composable';
import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome';
import { faThumbsUp, faThumbsDown, faShareSquare } from '@fortawesome/free-solid-svg-icons';
import { faFacebookSquare, faLinkedin } from '@fortawesome/free-brands-svg-icons';
import { defineProps } from 'vue';
import { useInteractionStore } from '@/stores/interactionStore';
import { GET_POST_BY_SLUG} from '@/graphql/queries/postQuery';
const props = defineProps({
postId: String, // Define the type of `postId` prop if necessary
});
const facebookIcon = ref(faFacebookSquare);
const linkedinIcon = ref(faLinkedin);
// Construct the post URL
const shareUrl = computed(() => `${window.location.origin}/post/${props.postId}`);
const shareOnLinkedIn = () => {
console.log('Sharing on LinkedIn:', shareUrl.value);
window.open(`https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl.value)}`, '_blank');
};
const shareOnFacebook = () => {
console.log('Sharing on Facebook:', shareUrl.value);
window.open(`https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl.value)}`, '_blank');
};
const post = ref(null);
const route = useRoute();
const slug = ref(route.params.slug);
const { result, loading, error } = useQuery(GET_POST_BY_SLUG, { slug: slug.value });
console.log('Initial useQuery state:', {
result: result.value,
loading: loading.value,
error: error.value,
});
const interactionStore = useInteractionStore();
/// Local state to keep track of interactions
const interactions = ref({ like: 0, dislike: 0, share: 0 });
const updateLocalInteractions = () => {
interactions.value = { ...interactionStore.interactions };
};
// Fetch initial interactions when component mounts
onMounted(async () => {
await interactionStore.fetchInteractions(props.postId);
updateLocalInteractions();
});
// Watch for changes in the interactionStore.interactions and update local interactions
watch(
() => interactionStore.interactions,
(newInteractions) => {
interactions.value = { ...newInteractions };
}
);
// Functions to handle interactions
const getLocalStorageKey = (action) => `post_${props.postId}_${action}`;
const isLiked = ref(localStorage.getItem(getLocalStorageKey('like')) === 'true');
const isDisliked = ref(localStorage.getItem(getLocalStorageKey('dislike')) === 'true');
const isShared = ref(localStorage.getItem(getLocalStorageKey('share')) === 'true');
const updateLocalStorage = (action) => {
localStorage.setItem(getLocalStorageKey('like'), action === 'like' ? 'true' : 'false');
localStorage.setItem(getLocalStorageKey('dislike'), action === 'dislike' ? 'true' : 'false');
localStorage.setItem(getLocalStorageKey('share'), action === 'share' ? 'true' : 'false');
isLiked.value = action === 'like';
isDisliked.value = action === 'dislike';
isShared.value = action === 'share';
};
const likePost = async () => {
try {
await interactionStore.updateInteraction(props.postId, 'like');
updateLocalInteractions();
updateLocalStorage('like');
} catch (error) {
console.error('Error updating like:', error);
}
};
const dislikePost = async () => {
try {
await interactionStore.updateInteraction(props.postId, 'dislike');
updateLocalInteractions();
updateLocalStorage('dislike');
} catch (error) {
console.error('Error updating dislike:', error);
}
};
const sharePost = async () => {
try {
await interactionStore.updateInteraction(props.postId, 'share');
updateLocalInteractions();
updateLocalStorage('share');
} catch (error) {
console.error('Error updating share:', error);
}
};
</script>
Mutations.py:
# mutations.py
import graphene
from graphene import ObjectType
from . import models
class InteractionsType(graphene.ObjectType):
like = graphene.Int()
dislike = graphene.Int()
share = graphene.Int()
class UpdateInteractions(graphene.Mutation):
class Arguments:
id = graphene.ID(required=True)
action = graphene.String(required=True)
success = graphene.Boolean()
message = graphene.String()
interactions = graphene.Field(InteractionsType)
def mutate(self, info, id, action):
try:
post = models.Post.objects.get(id=id)
except models.Post.DoesNotExist:
return UpdateInteractions(success=False, message="Post not found")
user_interactions, created = models.Interaction.objects.get_or_create(post=post)
if action == "like":
user_interactions.like = user_interactions.like + 1
elif action == "dislike":
user_interactions.dislike = user_interactions.dislike + 1
elif action == "share":
user_interactions.share = user_interactions.share + 1
else:
return UpdateInteractions(success=False, message="Invalid action")
user_interactions.save()
return UpdateInteractions(
success=True,
message="Interactions updated successfully",
interactions=user_interactions
)
class Mutation(ObjectType):
update_interactions = UpdateInteractions.Field()
interactionStore.js
// interactionStore.js
import { defineStore } from 'pinia';
import apolloClient from '@/apolloClient';
import { UPDATE_INTERACTIONS } from '../graphql/mutations/postMutation';
import { FETCH_INTERACTIONS } from '../graphql/queries/postQuery';
export const useInteractionStore = defineStore('interactionStore', {
state: () => ({
interactions: {
like: 0,
dislike: 0,
share: 0,
},
}),
actions: {
async fetchInteractions(postId) {
try {
const response = await apolloClient.query({
query: FETCH_INTERACTIONS,
variables: {
id: postId,
},
});
// Check if the response data exists and contains the interactions
if (response.data && response.data.interactions) {
this.interactions = response.data.interactions;
} else {
console.error('Query failed: Response does not contain interactions', response.errors);
}
} catch (error) {
console.error('An error occurred during query:', error);
}
},
async updateInteraction(postId, action) {
try {
const response = await apolloClient.mutate({
mutation: UPDATE_INTERACTIONS,
variables: {
id: postId,
action,
},
});
if (response.data.updateInteractions.success) {
this.interactions = response.data.updateInteractions.interactions;
} else {
console.error('Mutation failed:', response.data.updateInteractions.message);
throw new Error('Failed to update interactions');
}
} catch (error) {
console.error('An error occurred during mutation:', error);
throw new Error('Failed to update interactions');
}
},
},
});
fetch_interactions
export const FETCH_INTERACTIONS = gql`
query GetInteractions($id: ID!) {
postById(id: $id) {
interactions {
like
dislike
share
}
}
}
`;