Project Templates
Social Network
Fil et interactions pour votre réseau social
40 min
introduction dans ce tutoriel, vous apprendrez à créer un fil d'actualités pour un réseau social avec des fonctionnalités interactives telles que des publications, des commentaires et des likes en utilisant back4app comme service backend ces fonctionnalités constituent le cœur de toute application de réseau social, permettant aux utilisateurs de partager du contenu et d'interagir avec d'autres utilisateurs back4app est une plateforme backend as a service (baas) construite sur parse server qui fournit aux développeurs une infrastructure robuste pour créer des applications évolutives sans gérer le code côté serveur ses capacités de base de données en temps réel et sa gestion des utilisateurs intégrée en font un excellent choix pour le développement d'applications de réseaux sociaux à la fin de ce tutoriel, vous aurez mis en œuvre un fil social entièrement fonctionnel qui permet aux utilisateurs de créer des publications avec du texte et des images voir les publications d'autres utilisateurs dans un fil aimer les publications commenter les publications voir les détails des publications avec les commentaires vous apprendrez à structurer efficacement vos classes de base de données, à mettre en œuvre des mises à jour en temps réel et à créer une interface utilisateur réactive qui offre une expérience fluide sur tous les appareils prérequis pour compléter ce tutoriel, vous aurez besoin de un compte back4app vous pouvez vous inscrire pour un compte gratuit sur back4app com https //www back4app com un projet back4app configuré vous pouvez apprendre à créer un nouveau projet en suivant notre guide de démarrage avec back4app https //www back4app com/docs/get started/welcome node js installé sur votre machine locale connaissances de base en javascript et react js un système d'authentification fonctionnel si vous ne l'avez pas encore configuré, suivez notre système d'authentification pour les réseaux sociaux tutoriel d'abord familiarité avec les concepts modernes de développement web (composants, gestion d'état, etc ) projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 1 – comprendre le modèle de données avant d'écrire du code, il est important de comprendre le modèle de données de notre réseau social nous aurons besoin de plusieurs classes parse pour construire notre fonctionnalité de fil d'actualités classe post la post classe stockera toutes les informations liées aux publications auteur un pointeur vers l'utilisateur qui a créé la publication contenu le contenu textuel de la publication image un fichier image optionnel likes un nombre représentant combien de likes la publication a commentaires un tableau de pointeurs vers des objets comment (optionnel, peut être interrogé séparément) créé le géré automatiquement par back4app mis à jour le géré automatiquement par back4app classe commentaire le commentaire class stockera les commentaires sur les publications auteur un pointeur vers l'utilisateur qui a écrit le commentaire publication un pointeur vers la publication commentée contenu le contenu textuel du commentaire crééà géré automatiquement par back4app misàjour géré automatiquement par back4app suivi des j'aime nous avons deux options pour suivre les j'aime compteur simple stocker un compte de j'aime sur chaque publication (plus facile mais moins détaillé) enregistrements de j'aime créer une classe séparée j'aime qui suit chaque j'aime individuel (plus détaillé) pour ce tutoriel, nous allons mettre en œuvre les deux approches, en commençant par le compteur simple puis en montrant comment mettre en œuvre des enregistrements de j'aime individuels pour des fonctionnalités plus avancées commençons par configurer ces classes dans back4app étape 2 – configuration du modèle de données dans back4app dans cette étape, vous allez créer les classes nécessaires dans votre base de données back4app création de la classe post connectez vous à votre tableau de bord back4app et accédez à votre projet dans la barre latérale gauche, cliquez sur "base de données" pour ouvrir le navigateur de base de données cliquez sur le bouton "créer une classe" en haut de la page dans la fenêtre modale qui apparaît, entrez "post" comme nom de classe et sélectionnez "personnalisé" comme type ensuite, cliquez sur "créer la classe" \[image back4app créer une classe modal avec "post" entré comme nom de classe] maintenant, ajoutez les colonnes suivantes à votre classe post cliquez sur "ajouter une colonne" créez les colonnes suivantes auteur (type pointeur, classe cible user) contenu (type chaîne) image (type fichier) likes (type nombre, valeur par défaut 0) création de la classe commentaire cliquez à nouveau sur "créer une classe" entrez "commentaire" comme nom de classe et sélectionnez "personnalisé" comme type ajoutez les colonnes suivantes auteur (type pointeur, classe cible user) post (type pointeur, classe cible post) contenu (type chaîne) configuration des autorisations au niveau de la classe pour des raisons de sécurité, configurons les autorisations appropriées pour nos classes dans le navigateur de base de données, sélectionnez la classe post cliquez sur le bouton "sécurité" configurer les autorisations lecture publique oui (tout le monde peut voir les publications) écriture publique non (seuls les utilisateurs authentifiés peuvent créer des publications) ajout de champ public non trouver, obtenir, créer, mettre à jour, supprimer nécessite une authentification utilisateur répétez des paramètres similaires pour la classe comment maintenant que nous avons configuré notre modèle de données, mettons en œuvre les composants front end pour notre fil d'actualités sur les réseaux sociaux la page de fil d'actualité est le cœur de tout réseau social elle affiche les publications des utilisateurs et permet l'interaction créons un composant fil qui récupère et affiche les publications créez un fichier appelé src/pages/feedpage js import react, { usestate, useeffect, useref } from 'react'; import { usenavigate } from 'react router dom'; import { box, flex, vstack, heading, text, button, input, simplegrid, avatar, hstack, iconbutton, textarea, spinner, center, image, } from '@chakra ui/react'; import { link as routerlink } from 'react router dom'; import parse from 'parse/dist/parse min js'; import { toaster } from ' /components/ui/toaster'; function feedpage() { const \[searchquery, setsearchquery] = usestate(''); const \[newpostcontent, setnewpostcontent] = usestate(''); const \[posts, setposts] = usestate(\[]); const \[isloading, setisloading] = usestate(true); const \[isposting, setisposting] = usestate(false); const \[currentuser, setcurrentuser] = usestate(null); // new state variables for image upload const \[selectedimage, setselectedimage] = usestate(null); const \[imagepreview, setimagepreview] = usestate(null); const fileinputref = useref(null); const navigate = usenavigate(); // check if user is authenticated useeffect(() => { const checkauth = async () => { try { const user = await parse user current(); if (!user) { // redirect to login if not authenticated navigate('/login'); return; } setcurrentuser(user); } catch (error) { console error('error checking authentication ', error); navigate('/login'); } }; checkauth(); }, \[navigate]); // handle image selection const handleimagechange = (e) => { if (e target files && e target files\[0]) { const file = e target files\[0]; setselectedimage(file); // create a preview url const reader = new filereader(); reader onloadend = () => { setimagepreview(reader result); }; reader readasdataurl(file); } }; // clear selected image const handleclearimage = () => { setselectedimage(null); setimagepreview(null); if (fileinputref current) { fileinputref current value = ''; } }; // fetch posts useeffect(() => { const fetchposts = async () => { if (!currentuser) { console log('no current user, skipping post fetch'); return; } setisloading(true); console log('fetching posts for user ', currentuser id); try { // create a query for the post class const query = new parse query('post'); console log('created post query'); // include the user who created the post query include('author'); console log('including author in query'); // sort by creation date, newest first query descending('createdat'); // limit to 20 posts query limit(20); // execute the query console log('executing query '); const results = await query find(); console log('query results received ', results length, 'posts found'); // convert parse objects to plain objects const fetchedposts = \[]; for (let i = 0; i < results length; i++) { const post = results\[i]; try { const author = post get('author'); console log(`processing post ${i+1}/${results length}, author `, author ? author id 'null'); if (!author) { console warn(`post ${post id} has no author, skipping`); continue; } const postobj = { id post id, content post get('content'), author { id author id, username author get('username'), avatar author get('avatar') ? author get('avatar') url() null }, image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat') }; fetchedposts push(postobj); } catch (posterror) { console error(`error processing post ${i+1} `, posterror); console error('post data ', post tojson()); } } console log('successfully processed', fetchedposts length, 'posts'); setposts(fetchedposts); } catch (error) { console error('error fetching posts ', error); console error('error details ', error code, error message); toaster create({ title 'error loading posts', description error message, type 'error', }); } finally { setisloading(false); console log('post loading completed'); } }; fetchposts(); }, \[currentuser, navigate]); // function to create a new post with image const handlecreatepost = async () => { if (!newpostcontent trim() && !selectedimage) return; setisposting(true); console log('creating new post '); try { // create a new post object const post = parse object extend('post'); const post = new post(); // set post data post set('content', newpostcontent); post set('author', currentuser); post set('likes', 0); // handle image upload if an image is selected if (selectedimage) { console log('uploading image '); const parsefile = new parse file(selectedimage name, selectedimage); await parsefile save(); post set('image', parsefile); console log('image uploaded successfully'); } console log('post object created, saving to database '); // save the post const savedpost = await post save(); console log('post saved successfully with id ', savedpost id); // add the new post to the state const newpost = { id savedpost id, content savedpost get('content'), author { id currentuser id, username currentuser get('username'), avatar currentuser get('avatar') ? currentuser get('avatar') url() null }, image savedpost get('image') ? savedpost get('image') url() null, likes 0, createdat savedpost get('createdat') }; console log('adding new post to state'); setposts(\[newpost, posts]); setnewpostcontent(''); setselectedimage(null); setimagepreview(null); toaster create({ title 'post created', description 'your post has been published successfully!', type 'success', }); } catch (error) { console error('error creating post ', error); console error('error details ', error code, error message); toaster create({ title 'error creating post', description error message, type 'error', }); } finally { setisposting(false); console log('post creation completed'); } }; // function to like a post const handlelikepost = async (postid) => { try { // get the post const query = new parse query('post'); const post = await query get(postid); // increment likes post increment('likes'); await post save(); // update the post in the state setposts(posts map(p => { if (p id === postid) { return { p, likes p likes + 1 }; } return p; })); } catch (error) { console error('error liking post ', error); toaster create({ title 'error', description 'could not like the post please try again ', type 'error', }); } }; // function to logout const handlelogout = async () => { try { await parse user logout(); navigate('/login'); } catch (error) { console error('error logging out ', error); } }; const handlesearchsubmit = (e) => { e preventdefault(); if (searchquery trim()) { navigate(`/search?q=${encodeuricomponent(searchquery)}`); } }; // format date const formatdate = (date) => { return new date(date) tolocalestring(); }; return ( \<flex direction="row" h="100vh"> {/ left sidebar (navigation) /} \<box w={\['0px', '250px']} bg="gray 700" p={4} display={\['none', 'block']} borderright="1px solid" bordercolor="gray 600" \> \<vstack align="stretch" spacing={4}> \<heading size="md">social network\</heading> \<button as={routerlink} to="/feed" variant="ghost" justifycontent="flex start"> home \</button> \<button as={routerlink} to="/search" variant="ghost" justifycontent="flex start"> search \</button> \<button as={routerlink} to="/messages" variant="ghost" justifycontent="flex start"> messages \</button> \<button as={routerlink} to="/profile" variant="ghost" justifycontent="flex start"> profile \</button> \<button onclick={handlelogout} variant="ghost" colorscheme="red" justifycontent="flex start"> logout \</button> \</vstack> \</box> {/ main content (feed) /} \<box flex="1" p={4} overflowy="auto"> \<form onsubmit={handlesearchsubmit}> \<input placeholder="search " value={searchquery} onchange={(e) => setsearchquery(e target value)} mb={4} /> \</form> {/ create post with image upload /} \<box border="1px solid" bordercolor="gray 600" p={4} borderradius="md" mb={6}> \<textarea placeholder="what's on your mind?" value={newpostcontent} onchange={(e) => setnewpostcontent(e target value)} mb={2} resize="none" /> {/ image preview /} {imagepreview && ( \<box position="relative" mb={2}> \<image src={imagepreview} alt="preview" maxh="200px" borderradius="md" /> \<button position="absolute" top="2" right="2" size="sm" colorscheme="red" onclick={handleclearimage} \> remove \</button> \</box> )} \<box> \<input type="file" accept="image/ " onchange={handleimagechange} style={{ display 'none' }} ref={fileinputref} id="image upload" /> \<button as="label" htmlfor="image upload" cursor="pointer" variant="outline" size="sm" mr={2} mb={0} \> 📷 add photo \</button> \</box> \<button colorscheme="blue" onclick={handlecreatepost} isloading={isposting} disabled={(!newpostcontent trim() && !selectedimage) || isposting} \> post \</button> \</box> {/ posts feed with images /} {isloading ? ( \<center py={10}> \<spinner size="xl" /> \</center> ) posts length > 0 ? ( \<vstack align="stretch" spacing={4}> {posts map(post => ( \<box key={post id} border="1px solid" bordercolor="gray 600" p={4} borderradius="md" \> \<hstack mb={2}> \<avatar src={post author avatar} name={post author username} size="sm" /> \<text fontweight="bold">{post author username}\</text> \<text fontsize="sm" color="gray 400">• {formatdate(post createdat)}\</text> \</hstack> \<text mb={post image ? 2 4}>{post content}\</text> {/ display post image if available /} {post image && ( \<box mb={4}> \<image src={post image} alt="post image" borderradius="md" maxh="400px" w="auto" /> \</box> )} \<hstack spacing={4}> \<button variant="ghost" size="sm" onclick={() => handlelikepost(post id)} \> ❤️ {post likes} \</button> \<button variant="ghost" size="sm" as={routerlink} to={`/post/${post id}`} \> 💬 comment \</button> \<button variant="ghost" size="sm"> 🔄 share \</button> \</hstack> \</box> ))} \</vstack> ) ( \<text>no posts yet follow users or create your first post!\</text> )} \</box> {/ right sidebar (trending hashtags) /} \<box w={\['0px', '250px']} bg="gray 700" p={4} display={\['none', 'block']} borderleft="1px solid" bordercolor="gray 600" \> \<heading size="md" mb={4}> trending today \</heading> \<simplegrid columns={1} spacing={2}> \<button variant="outline" size="sm" colorscheme="whitealpha">#travel\</button> \<button variant="outline" size="sm" colorscheme="whitealpha">#tech\</button> \<button variant="outline" size="sm" colorscheme="whitealpha">#foodie\</button> \</simplegrid> \</box> \</flex> ); } export default feedpage; ce composant feed comprend plusieurs fonctionnalités clés vérification d'authentification assure que seuls les utilisateurs connectés peuvent voir le fil création de publication permet aux utilisateurs de créer de nouvelles publications avec du texte et des images facultatives téléchargement d'image gère la sélection, l'aperçu et le téléchargement d'images en utilisant parse file affichage du fil affiche les publications de tous les utilisateurs dans l'ordre chronologique inverse fonctionnalité de like permet aux utilisateurs d'aimer les publications en utilisant une approche de compteur simple navigation fournit des liens vers d'autres parties de l'application examinons comment back4app nous aide à mettre en œuvre ces fonctionnalités création de publication avec parse back4app rend la création de publications avec des images simple // create a new post object const post = parse object extend('post'); const post = new post(); // set post data post set('content', newpostcontent); post set('author', currentuser); post set('likes', 0); // handle image upload if an image is selected if (selectedimage) { const parsefile = new parse file(selectedimage name, selectedimage); await parsefile save(); post set('image', parsefile); } // save the post const savedpost = await post save(); parse file gère automatiquement les téléchargements de fichiers, le stockage et la génération d'url, ce qui facilite l'ajout d'images aux publications récupération des publications avec parse query le système de requêtes de back4app facilite la récupération et l'affichage des publications // create a query for the post class const query = new parse query('post'); // include the user who created the post query include('author'); // sort by creation date, newest first query descending('createdat'); // limit to 20 posts query limit(20); // execute the query const results = await query find(); la include('author') méthode est particulièrement puissante, car elle inclut automatiquement l'objet utilisateur référencé avec chaque publication, réduisant ainsi le besoin de multiples requêtes projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 4 – mise en œuvre de la vue des détails du post avec des commentaires maintenant, créons une page de détails du post qui affiche un seul post avec ses commentaires et permet aux utilisateurs d'ajouter de nouveaux commentaires créez un fichier appelé src/pages/postdetailspage js import react, { usestate, useeffect } from 'react'; import { useparams, usenavigate, link as routerlink } from 'react router dom'; import { box, flex, vstack, heading, text, button, avatar, hstack, textarea, spinner, center, image } from '@chakra ui/react'; import parse from 'parse/dist/parse min js'; import { toaster } from ' /components/ui/toaster'; function postdetailspage() { const { id } = useparams(); const navigate = usenavigate(); const \[post, setpost] = usestate(null); const \[comments, setcomments] = usestate(\[]); const \[newcomment, setnewcomment] = usestate(''); const \[isloading, setisloading] = usestate(true); const \[iscommenting, setiscommenting] = usestate(false); const \[currentuser, setcurrentuser] = usestate(null); // check if user is authenticated useeffect(() => { const checkauth = async () => { try { const user = await parse user current(); if (!user) { navigate('/login'); return; } setcurrentuser(user); } catch (error) { console error('error checking authentication ', error); navigate('/login'); } }; checkauth(); }, \[navigate]); // fetch post and comments useeffect(() => { const fetchpostdetails = async () => { if (!currentuser) return; setisloading(true); try { // get the post const query = new parse query('post'); query include('author'); const postobject = await query get(id); // get comments const commentquery = new parse query('comment'); commentquery equalto('post', postobject); commentquery include('author'); commentquery ascending('createdat'); const commentresults = await commentquery find(); // convert post to plain object const fetchedpost = { id postobject id, content postobject get('content'), author { id postobject get('author') id, username postobject get('author') get('username'), avatar postobject get('author') get('avatar') ? postobject get('author') get('avatar') url() null }, likes postobject get('likes') || 0, createdat postobject get('createdat'), image postobject get('image') ? postobject get('image') url() null }; // convert comments to plain objects const fetchedcomments = commentresults map(comment => ({ id comment id, content comment get('content'), author { id comment get('author') id, username comment get('author') get('username'), avatar comment get('author') get('avatar') ? comment get('author') get('avatar') url() null }, createdat comment get('createdat') })); setpost(fetchedpost); setcomments(fetchedcomments); } catch (error) { console error('error fetching post details ', error); toaster create({ title 'error loading post', description error message, type 'error', }); navigate('/feed'); } finally { setisloading(false); } }; if (id) { fetchpostdetails(); } }, \[id, currentuser, navigate]); // function to add a comment const handleaddcomment = async () => { if (!newcomment trim()) return; setiscommenting(true); try { // get the post object const postquery = new parse query('post'); const postobject = await postquery get(id); // create a new comment object const comment = parse object extend('comment'); const comment = new comment(); // set comment data comment set('content', newcomment); comment set('author', currentuser); comment set('post', postobject); // save the comment await comment save(); // add the new comment to the state const newcommentobj = { id comment id, content comment get('content'), author { id currentuser id, username currentuser get('username'), avatar currentuser get('avatar') ? currentuser get('avatar') url() null }, createdat comment get('createdat') }; setcomments(\[ comments, newcommentobj]); setnewcomment(''); toaster create({ title 'comment added', description 'your comment has been posted successfully!', type 'success', }); } catch (error) { console error('error adding comment ', error); toaster create({ title 'error adding comment', description error message, type 'error', }); } finally { setiscommenting(false); } }; // function to like the post const handlelikepost = async () => { try { // get the post const query = new parse query('post'); const postobject = await query get(id); // increment likes postobject increment('likes'); await postobject save(); // update the post in the state setpost({ post, likes post likes + 1 }); } catch (error) { console error('error liking post ', error); toaster create({ title 'error', description 'could not like the post please try again ', type 'error', }); } }; // format date const formatdate = (date) => { return new date(date) tolocalestring(); }; if (isloading) { return ( \<center h="100vh"> \<spinner size="xl" /> \</center> ); } if (!post) { return ( \<center h="100vh"> \<vstack> \<text>post not found\</text> \<button as={routerlink} to="/feed" colorscheme="blue"> back to feed \</button> \</vstack> \</center> ); } return ( \<box maxw="800px" mx="auto" p={4}> \<button as={routerlink} to="/feed" mb={4} variant="ghost"> ← back to feed \</button> {/ post /} \<box border="1px solid" bordercolor="gray 600" p={6} borderradius="md" mb={6}> \<hstack mb={2}> \<avatar src={post author avatar} name={post author username} size="sm" /> \<vstack align="start" spacing={0}> \<text fontweight="bold">{post author username}\</text> \<text fontsize="sm" color="gray 400">{formatdate(post createdat)}\</text> \</vstack> \</hstack> \<text fontsize="lg" my={post image ? 2 4}>{post content}\</text> {/ display post image if available /} {post image && ( \<box my={4}> \<image src={post image} alt="post image" borderradius="md" maxh="500px" w="auto" /> \</box> )} \<hstack spacing={4}> \<button variant="ghost" onclick={handlelikepost} \> ❤️ {post likes} \</button> \<button variant="ghost"> 🔄 share \</button> \</hstack> \</box> {/ comments section /} \<box> \<heading size="md" mb={4}> comments ({comments length}) \</heading> {/ add comment /} \<box mb={6}> \<textarea placeholder="write a comment " value={newcomment} onchange={(e) => setnewcomment(e target value)} mb={2} /> \<button colorscheme="blue" onclick={handleaddcomment} isloading={iscommenting} disabled={!newcomment trim()} \> post comment \</button> \</box> \<hr style={{ marginbottom "1rem" }} /> {/ comments list /} {comments length > 0 ? ( \<vstack align="stretch" spacing={4}> {comments map(comment => ( \<box key={comment id} p={4} bg="gray 700" borderradius="md"> \<hstack mb={2}> \<avatar src={comment author avatar} name={comment author username} size="sm" /> \<text fontweight="bold">{comment author username}\</text> \<text fontsize="sm" color="gray 400">• {formatdate(comment createdat)}\</text> \</hstack> \<text>{comment content}\</text> \</box> ))} \</vstack> ) ( \<text color="gray 400">no comments yet be the first to comment!\</text> )} \</box> \</box> ); } export default postdetailspage; this post detail page provides several important features 1\ post display shows the complete post with author information, content, image, and likes 2\ comment creation allows users to add new comments to the post 3\ comment listing displays all comments in chronological order 4\ like functionality enables users to like the post let's examine the key aspects of how back4app is helping us implement these features \### fetching post details with related objects back4app makes it easy to fetch a post with related information ```javascript // get the post const query = new parse query('post'); query include('author'); const postobject = await query get(id); le include('author') méthode récupère automatiquement l'objet utilisateur associé avec le post, éliminant le besoin de requêtes séparées interroger les commentaires associés le système de requête de back4app nous permet de trouver tous les commentaires liés à un post spécifique // get comments const commentquery = new parse query('comment'); commentquery equalto('post', postobject); commentquery include('author'); commentquery ascending('createdat'); const commentresults = await commentquery find(); le equalto('post', postobject) méthode filtre les commentaires pour ne garder que ceux liés au post actuel créer des commentaires avec des pointeurs créer un commentaire avec une relation à un post est simple // create a new comment object const comment = parse object extend('comment'); const comment = new comment(); // set comment data comment set('content', newcomment); comment set('author', currentuser); comment set('post', postobject); // save the comment await comment save(); le post le champ est défini comme un pointeur vers l'objet post, établissant la relation entre le commentaire et le post projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 5 – mise en œuvre de la fonctionnalité de like avancée avec parse le compteur de likes simple que nous avons mis en œuvre jusqu'à présent fonctionne bien pour une fonctionnalité de base, mais il a des limitations les utilisateurs peuvent aimer un post plusieurs fois un utilisateur ne peut pas "ne plus aimer" un post nous ne pouvons pas montrer qui a aimé un post créons un système de like plus avancé en utilisant une classe like pour suivre les likes individuels configuration de la classe like tout d'abord, créez une nouvelle classe dans votre tableau de bord back4app cliquez sur "créer une classe" dans le navigateur de base de données entrez "like" comme nom de classe et sélectionnez "personnalisé" comme type ajoutez les colonnes suivantes utilisateur (type pointeur, classe cible user) publication (type pointeur, classe cible post) mise en œuvre de la gestion des likes dans notre code maintenant, mettons à jour notre fonctionnalité de liking de publication dans le postdetailspage js fichier // add this state at the beginning of the component const \[hasliked, sethasliked] = usestate(false); // add this to the useeffect that fetches post details // check if current user has liked this post const likequery = new parse query('like'); likequery equalto('user', currentuser); likequery equalto('post', postobject); const userlike = await likequery first(); sethasliked(!!userlike); // replace the handlelikepost function with this new version const handlelikepost = async () => { try { // get the post const query = new parse query('post'); const postobject = await query get(id); // check if user has already liked the post const likequery = new parse query('like'); likequery equalto('user', currentuser); likequery equalto('post', postobject); const existinglike = await likequery first(); if (existinglike) { // user already liked the post, so remove the like await existinglike destroy(); // decrement likes count on the post postobject increment('likes', 1); await postobject save(); // update ui sethasliked(false); setpost({ post, likes post likes 1 }); } else { // user hasn't liked the post yet, so add a like const like = parse object extend('like'); const like = new like(); like set('user', currentuser); like set('post', postobject); await like save(); // increment likes count on the post postobject increment('likes'); await postobject save(); // update ui sethasliked(true); setpost({ post, likes post likes + 1 }); } } catch (error) { console error('error toggling like ', error); toaster create({ title 'error', description 'could not process your like please try again ', type 'error', }); } }; ensuite, mettez à jour le bouton j'aime pour refléter l'état actuel \<button variant="ghost" onclick={handlelikepost} color={hasliked ? "red 500" "inherit"} \> {hasliked ? "❤️" "🤍"} {post likes} \</button> cette implémentation suit les j'aime individuels en utilisant un j'aime classe permet aux utilisateurs de basculer les j'aime (aimer et ne pas aimer) indique visuellement si l'utilisateur actuel a aimé le post maintient le compte des j'aime sur le post pour une requête efficace afficher qui a aimé un post vous pouvez également ajouter une fonctionnalité pour montrer qui a aimé un post // add this state const \[likeusers, setlikeusers] = usestate(\[]); const \[showlikes, setshowlikes] = usestate(false); // add this function const fetchlikes = async () => { try { const postquery = new parse query('post'); const postobject = await postquery get(id); const likequery = new parse query('like'); likequery equalto('post', postobject); likequery include('user'); const likes = await likequery find(); const users = likes map(like => { const user = like get('user'); return { id user id, username user get('username'), avatar user get('avatar') ? user get('avatar') url() null }; }); setlikeusers(users); setshowlikes(true); } catch (error) { console error('error fetching likes ', error); } }; ajoutez ensuite un élément d'interface utilisateur pour afficher les utilisateurs qui ont aimé le post \<text color="blue 400" cursor="pointer" onclick={fetchlikes} fontsize="sm" \> see who liked this post \</text> {showlikes && ( \<box mt={2} p={2} bg="gray 700" borderradius="md"> \<heading size="xs" mb={2}>liked by \</heading> {likeusers length > 0 ? ( \<vstack align="stretch"> {likeusers map(user => ( \<hstack key={user id}> \<avatar size="xs" src={user avatar} name={user username} /> \<text fontsize="sm">{user username}\</text> \</hstack> ))} \</vstack> ) ( \<text fontsize="sm">no likes yet\</text> )} \</box> )} projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 6 – mise en œuvre des mises à jour en temps réel avec parse live query l'une des fonctionnalités les plus puissantes de back4app est la capacité de recevoir des mises à jour en temps réel avec live query cela permet à votre application de se mettre à jour automatiquement lorsque de nouveaux posts ou commentaires sont créés sans avoir besoin de rafraîchir manuellement configuration de live query sur back4app tout d'abord, vous devez activer live query pour votre application dans back4app allez sur votre tableau de bord back4app naviguez vers paramètres de l'application > paramètres du serveur faites défiler vers le bas jusqu'à "noms de classes de live query parse" ajoutez "post" et "commentaire" à la liste des classes enregistrez vos modifications implémentation de live query pour des publications en temps réel mettons à jour notre feedpage js pour inclure live query pour des mises à jour de posts en temps réel // add to your import statements import { useeffect, useref } from 'react'; // add to your component const livequerysubscription = useref(null); // add this to your component to set up and clean up the subscription useeffect(() => { const setuplivequery = async () => { if (!currentuser) return; try { console log('setting up live query for posts'); // create a query that will be used for the subscription const query = new parse query('post'); // subscribe to the query livequerysubscription current = await query subscribe(); console log('successfully subscribed to live query'); // handle connection open livequerysubscription current on('open', () => { console log('live query connection opened'); }); // handle new posts livequerysubscription current on('create', (post) => { console log('new post received via live query ', post id); // skip posts from the current user as they're already in the ui if (post get('author') id === currentuser id) { console log('skipping post from current user'); return; } // format the new post const author = post get('author'); const newpost = { id post id, content post get('content'), author { id author id, username author get('username'), avatar author get('avatar') ? author get('avatar') url() null }, image post get('image') ? post get('image') url() null, likes post get('likes') || 0, createdat post get('createdat') }; // add the new post to the posts state setposts(prevposts => \[newpost, prevposts]); }); // handle post updates livequerysubscription current on('update', (post) => { console log('post updated via live query ', post id); // format the updated post const newlikes = post get('likes') || 0; // update the post in the state setposts(prevposts => prevposts map(p => p id === post id ? { p, likes newlikes } p ) ); }); // handle errors livequerysubscription current on('error', (error) => { console error('live query error ', error); }); } catch (error) { console error('error setting up live query ', error); } }; setuplivequery(); // clean up subscription return () => { if (livequerysubscription current) { console log('unsubscribing from live query'); livequerysubscription current unsubscribe(); } }; }, \[currentuser]); implémentation de live query pour des commentaires en temps réel de même, mettons à jour notre postdetailspage js pour inclure live query pour des commentaires en temps réel // add to your import statements import { useeffect, useref } from 'react'; // add to your component const livequerysubscription = useref(null); // add this to your component to set up and clean up the subscription useeffect(() => { const setuplivequery = async () => { if (!currentuser || !id) return; try { console log('setting up live query for comments'); // get the post object to use in the query const postquery = new parse query('post'); const postobject = await postquery get(id); // create a query that will be used for the subscription const query = new parse query('comment'); query equalto('post', postobject); // subscribe to the query livequerysubscription current = await query subscribe(); console log('successfully subscribed to live query for comments'); // handle connection open livequerysubscription current on('open', () => { console log('live query connection opened'); }); // handle new comments livequerysubscription current on('create', (comment) => { console log('new comment received via live query ', comment id); // skip comments from the current user as they're already in the ui if (comment get('author') id === currentuser id) { console log('skipping comment from current user'); return; } // format the new comment const author = comment get('author'); const newcomment = { id comment id, content comment get('content'), author { id author id, username author get('username'), avatar author get('avatar') ? author get('avatar') url() null }, createdat comment get('createdat') }; // add the new comment to the comments state setcomments(prevcomments => \[ prevcomments, newcomment]); }); // handle errors livequerysubscription current on('error', (error) => { console error('live query error ', error); }); } catch (error) { console error('error setting up live query for comments ', error); } }; setuplivequery(); // clean up subscription return () => { if (livequerysubscription current) { console log('unsubscribing from live query'); livequerysubscription current unsubscribe(); } }; }, \[currentuser, id]); avec ces implémentations de live query, votre réseau social se mettra désormais à jour en temps réel de nouveaux posts d'autres utilisateurs apparaîtront automatiquement dans le fil le nombre de likes se mettra à jour en temps réel lorsque d'autres utilisateurs aimeront un post de nouveaux commentaires apparaîtront automatiquement sur la page de détail du post projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app étape 7 – requêtes avancées et optimisation des performances à mesure que votre réseau social se développe, la performance devient de plus en plus importante explorons quelques techniques avancées pour optimiser votre application en utilisant les puissantes capacités de requête de back4app mise en œuvre de la pagination au lieu de charger tous les posts en une seule fois, mettez en œuvre la pagination pour les charger par lots // add these state variables const \[page, setpage] = usestate(0); const \[hasmoreposts, sethasmoreposts] = usestate(true); const postsperpage = 10; // update your fetchposts function const fetchposts = async (loadmore = false) => { if (!currentuser) return; if (!loadmore) { setisloading(true); } try { const query = new parse query('post'); query include('author'); query descending('createdat'); query limit(postsperpage); query skip(page postsperpage); const results = await query find(); // process and format posts as before const fetchedposts = \[ ]; // update the hasmoreposts state sethasmoreposts(results length === postsperpage); // update the posts state if (loadmore) { setposts(prevposts => \[ prevposts, fetchedposts]); } else { setposts(fetchedposts); } // update the page if this was a "load more" request if (loadmore) { setpage(prevpage => prevpage + 1); } } catch (error) { // handle errors } finally { setisloading(false); } }; // add a load more button at the bottom of your posts list {hasmoreposts && ( \<button onclick={() => fetchposts(true)} variant="outline" isloading={isloading} mt={4} \> load more posts \</button> )} mise en œuvre d'un fil spécifique à l'utilisateur pour une expérience plus personnalisée, vous voudrez peut être afficher des publications uniquement des utilisateurs que l'utilisateur actuel suit // assuming you have a "follow" class with "follower" and "following" pointers const fetchfollowingposts = async () => { try { // first, find all users that the current user follows const followquery = new parse query('follow'); followquery equalto('follower', currentuser); const followings = await followquery find(); // extract the users being followed const followedusers = followings map(follow => follow\ get('following')); // add the current user to show their posts too followedusers push(currentuser); // create a query for posts from these users const query = new parse query('post'); query containedin('author', followedusers); query include('author'); query descending('createdat'); query limit(20); const results = await query find(); // process results } catch (error) { // handle errors } }; utiliser les fonctions cloud pour des opérations complexes pour des opérations complexes nécessitant plusieurs requêtes, envisagez d'utiliser les fonctions cloud de back4app allez sur votre tableau de bord back4app naviguez vers code cloud > fonctions cloud créez une nouvelle fonction // example function to get a feed with posts, likes, and comment counts parse cloud define("getfeed", async (request) => { try { const user = request user; const page = request params page || 0; const limit = request params limit || 10; if (!user) { throw new error("user must be logged in"); } // query for posts const query = new parse query("post"); query include("author"); query descending("createdat"); query limit(limit); query skip(page limit); const posts = await query find({ usemasterkey true }); // format posts and get additional info const formattedposts = await promise all(posts map(async (post) => { // check if user has liked this post const likequery = new parse query("like"); likequery equalto("post", post); likequery equalto("user", user); const hasliked = await likequery count({ usemasterkey true }) > 0; // get comment count const commentquery = new parse query("comment"); commentquery equalto("post", post); const commentcount = await commentquery count({ usemasterkey true }); // format post for response return { id post id, content post get("content"), image post get("image") ? post get("image") url() null, author { id post get("author") id, username post get("author") get("username"), avatar post get("author") get("avatar") ? post get("author") get("avatar") url() null }, likes post get("likes") || 0, hasliked hasliked, commentcount commentcount, createdat post get("createdat") }; })); return { posts formattedposts }; } catch (error) { throw new error(`error fetching feed ${error message}`); } }); appelez ensuite cette fonction cloud depuis votre application const fetchfeed = async () => { try { setisloading(true); const result = await parse cloud run('getfeed', { page 0, limit 10 }); setposts(result posts); } catch (error) { console error('error fetching feed ', error); toaster create({ title 'error loading feed', description error message, type 'error', }); } finally { setisloading(false); } }; cette approche peut réduire considérablement le nombre de requêtes à la base de données et améliorer les performances projet back4gram trouvez ici le code complet pour un exemple de projet de réseau social construit avec back4app conclusion dans ce tutoriel, vous avez construit un fil de réseau social complet avec des fonctionnalités interactives en utilisant back4app récapitulons ce que vous avez accompli conception du modèle de données créé un modèle de données structuré pour les publications, les commentaires et les likes implémentation du fil créé une page de fil qui affiche les publications de tous les utilisateurs création de publications implémenté la fonctionnalité permettant aux utilisateurs de créer des publications avec du texte et des images système de commentaires créé un système de commentaires qui permet aux utilisateurs d'interagir avec les publications fonctionnalité de like implémenté des systèmes de like simples et avancés mises à jour en temps réel ajouté une requête en direct pour des mises à jour de publications et de commentaires en temps réel optimisation des performances exploré des techniques pour améliorer les performances à mesure que votre application se développe back4app a fourni une base de données sécurisée et évolutive pour stocker le contenu généré par les utilisateurs stockage de fichiers pour les images de publication authentification utilisateur intégrée pour la sécurité requête en direct pour des mises à jour en temps réel fonctions cloud pour des opérations backend complexes avec ces éléments de base en place, votre réseau social a une fondation solide qui peut évoluer à mesure que votre base d'utilisateurs grandit prochaines étapes pour améliorer davantage votre réseau social, envisagez d'implémenter ces fonctionnalités notifications alertez les utilisateurs lorsque leurs publications reçoivent des likes ou des commentaires système de suivi des utilisateurs permettez aux utilisateurs de se suivre mutuellement et de personnaliser leurs fils d'actualité hashtags et recherche implémentez un système de hashtags et une fonctionnalité de recherche améliorée publications riches en médias supportez les vidéos, plusieurs images et d'autres types de médias analytique suivez l'engagement et le comportement des utilisateurs pour améliorer votre application pour le code complet de l'application de réseau social back4gram, vous pouvez consulter le dépôt github https //github com/templates back4app/back4gram en tirant parti des puissants services backend de back4app, vous pouvez vous concentrer sur la création d'une excellente expérience utilisateur pendant que la plateforme gère l'infrastructure complexe nécessaire pour une application de réseau social