Project Templates
Social Network
Umum dan Interaksi untuk Jaringan Sosial Anda
39 mnt
 pendahuluan dalam tutorial ini, anda akan belajar bagaimana membangun umpan jejaring sosial dengan fitur interaktif seperti pos, komentar, dan suka menggunakan back4app sebagai layanan backend anda fitur fitur ini membentuk inti dari aplikasi jejaring sosial mana pun, memungkinkan pengguna untuk berbagi konten dan berinteraksi dengan pengguna lain back4app adalah platform backend as a service (baas) yang dibangun di atas parse server yang menyediakan pengembang dengan infrastruktur yang kuat untuk membuat aplikasi yang dapat diskalakan tanpa mengelola kode sisi server kemampuan basis data real time dan manajemen pengguna bawaan menjadikannya pilihan yang sangat baik untuk mengembangkan aplikasi jejaring sosial pada akhir tutorial ini, anda akan memiliki umpan sosial yang sepenuhnya fungsional yang memungkinkan pengguna untuk membuat pos dengan teks dan gambar melihat pos dari pengguna lain dalam umpan menyukai pos mengomentari pos melihat detail pos dengan komentar anda akan belajar bagaimana cara menyusun kelas basis data anda dengan efisien, menerapkan pembaruan real time, dan membuat antarmuka pengguna yang responsif yang memberikan pengalaman yang mulus di berbagai perangkat prasyarat untuk menyelesaikan tutorial ini, anda akan membutuhkan akun back4app anda dapat mendaftar untuk akun gratis di back4app com https //www back4app com proyek back4app yang sudah disiapkan anda dapat belajar cara membuat proyek baru dengan mengikuti panduan memulai dengan back4app https //www back4app com/docs/get started/welcome node js terinstal di mesin lokal anda pengetahuan dasar tentang javascript dan react js sistem otentikasi yang berfungsi jika anda belum menyiapkannya, ikuti sistem otentikasi untuk jaringan sosial tutorial kami terlebih dahulu keterampilan dengan konsep pengembangan web modern (komponen, manajemen status, dll ) proyek back4gram temukan di sini kode lengkap untuk sebuah proyek contoh jaringan sosial yang dibangun dengan back4app langkah 1 ā memahami model data sebelum menulis kode, penting untuk memahami model data untuk jejaring sosial kita kita akan membutuhkan beberapa kelas parse untuk membangun fungsionalitas feed kita kelas post kelas post akan menyimpan semua informasi terkait pos penulis sebuah pointer ke pengguna yang membuat pos konten konten teks dari pos gambar sebuah file gambar opsional suka sebuah angka yang mewakili berapa banyak suka yang dimiliki pos komentar sebuah array dari pointer ke objek komentar (opsional, dapat ditanyakan secara terpisah) dibuatpada dikelola secara otomatis oleh back4app diperbaruipada dikelola secara otomatis oleh back4app kelas komentar kelas komentar akan menyimpan komentar pada pos penulis sebuah pointer ke pengguna yang menulis komentar pos sebuah pointer ke pos yang dikomentari konten konten teks dari komentar dibuatpada dikelola secara otomatis oleh back4app diperbaruipada dikelola secara otomatis oleh back4app pelacakan suka kami memiliki dua opsi untuk melacak suka penghitung sederhana simpan jumlah suka pada setiap pos (lebih mudah tetapi kurang detail) rekaman suka buat sebuah kelas suka , yang melacak setiap suka individu (lebih detail) untuk tutorial ini, kami akan menerapkan kedua pendekatan, dimulai dengan penghitung yang lebih sederhana dan kemudian menunjukkan cara menerapkan rekaman suka individu untuk fitur yang lebih canggih mari kita mulai dengan menyiapkan kelas kelas ini di back4app langkah 2 ā menyiapkan model data di back4app dalam langkah ini, anda akan membuat kelas yang diperlukan di database back4app anda membuat kelas post masuk ke dasbor back4app anda dan navigasikan ke proyek anda di sidebar kiri, klik "database" untuk membuka browser database klik tombol "buat kelas" di bagian atas halaman di modal yang muncul, masukkan "post" sebagai nama kelas dan pilih "kustom" sebagai tipe kemudian klik "buat kelas" \[image back4app buat kelas modal dengan "post" dimasukkan sebagai nama kelas] sekarang, tambahkan kolom berikut ke kelas post anda klik "tambahkan kolom" buat kolom berikut penulis (tipe pointer, kelas target user) konten (tipe string) gambar (tipe file) suka (tipe number, nilai default 0) membuat kelas komentar klik "buat kelas" lagi masukkan "komentar" sebagai nama kelas dan pilih "kustom" sebagai tipe tambahkan kolom berikut penulis (tipe pointer, kelas target user) post (tipe pointer, kelas target post) konten (tipe string) mengatur izin tingkat kelas untuk keamanan, mari kita atur izin yang sesuai untuk kelas kita di database browser, pilih kelas post klik tombol "keamanan" konfigurasi izin baca publik ya (siapa saja dapat melihat pos) tulis publik tidak (hanya pengguna yang terautentikasi yang dapat membuat pos) tambahkan bidang publik tidak temukan, dapatkan, buat, perbarui, hapus memerlukan autentikasi pengguna ulangi pengaturan serupa untuk kelas komentar sekarang kita telah mengatur model data kita, mari kita implementasikan komponen front end untuk umpan jejaring sosial kita halaman umpan adalah jantung dari setiap jejaring sosial ini menampilkan pos dari pengguna dan memungkinkan interaksi mari kita buat komponen umpan yang mengambil dan menampilkan pos buat file bernama 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; komponen feed ini mencakup beberapa fitur kunci pemeriksaan autentikasi memastikan hanya pengguna yang masuk yang dapat melihat feed pembuatan postingan memungkinkan pengguna untuk membuat postingan baru dengan teks dan gambar opsional unggah gambar menangani pemilihan gambar, pratinjau, dan unggah menggunakan parse file tampilan feed menampilkan postingan dari semua pengguna dalam urutan kronologis terbalik fungsi suka memungkinkan pengguna untuk menyukai postingan menggunakan pendekatan penghitung sederhana navigasi menyediakan tautan ke bagian lain dari aplikasi mari kita periksa bagaimana back4app membantu kita menerapkan fitur fitur ini pembuatan postingan dengan parse back4app membuat pembuatan postingan dengan gambar menjadi sederhana // 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 secara otomatis menangani unggahan file, penyimpanan, dan pembuatan url, sehingga mudah untuk menambahkan gambar ke postingan mengambil postingan dengan query parse sistem kueri back4app memudahkan untuk mengambil dan menampilkan pos // 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(); metode include('author') sangat kuat, karena secara otomatis menyertakan objek pengguna yang direferensikan dengan setiap pos, mengurangi kebutuhan untuk beberapa kueri proyek back4gram temukan di sini kode lengkap untuk sebuah proyek contoh jaringan sosial yang dibangun dengan back4app langkah 4 ā mengimplementasikan tampilan detail post dengan komentar sekarang, mari kita buat halaman detail post yang menampilkan satu post beserta komentarnya dan memungkinkan pengguna untuk menambahkan komentar baru buat file bernama 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); the include('author') metode secara otomatis mengambil objek user yang terkait bersama dengan pos, menghilangkan kebutuhan untuk kueri terpisah mengambil komentar terkait sistem kueri back4app memungkinkan kita untuk menemukan semua komentar yang terkait dengan pos tertentu // get comments const commentquery = new parse query('comment'); commentquery equalto('post', postobject); commentquery include('author'); commentquery ascending('createdat'); const commentresults = await commentquery find(); the equalto('post', postobject) metode menyaring komentar hanya untuk yang terkait dengan pos saat ini membuat komentar dengan pointers membuat komentar dengan hubungan ke pos sangat sederhana // 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(); the post field is set as a pointer to the post object, establishing the relationship between the comment and the post back4gram project temukan di sini kode lengkap untuk sebuah proyek contoh jaringan sosial yang dibangun dengan back4app langkah 5 ā mengimplementasikan fungsionalitas like lanjutan dengan parse penghitung like sederhana yang telah kita implementasikan sejauh ini bekerja dengan baik untuk fungsionalitas dasar, tetapi memiliki keterbatasan pengguna dapat menyukai sebuah pos beberapa kali seorang pengguna tidak dapat "menghapus suka" dari sebuah pos kita tidak dapat menunjukkan siapa yang menyukai sebuah pos mari kita buat sistem like yang lebih canggih menggunakan like kelas untuk melacak like individu menyiapkan kelas like pertama, buat kelas baru di dasbor back4app anda klik "buat kelas" di database browser masukkan "suka" sebagai nama kelas dan pilih "kustom" sebagai tipe tambahkan kolom berikut pengguna (tipe pointer, kelas target user) pos (tipe pointer, kelas target post) mengimplementasikan manajemen suka dalam kode kita sekarang, mari kita perbarui fungsionalitas menyukai pos kita di postdetailspage js file // 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', }); } }; kemudian, perbarui tombol suka untuk mencerminkan keadaan saat ini \<button variant="ghost" onclick={handlelikepost} color={hasliked ? "red 500" "inherit"} \> {hasliked ? "ā¤ļø" "š¤"} {post likes} \</button> implementasi ini melacak suka individu menggunakan like kelas memungkinkan pengguna untuk mengubah suka (suka dan tidak suka) secara visual menunjukkan apakah pengguna saat ini telah menyukai pos mempertahankan jumlah suka pada pos untuk kueri yang efisien menampilkan siapa yang menyukai sebuah postingan anda juga dapat menambahkan fungsionalitas untuk menunjukkan siapa yang telah menyukai sebuah postingan // 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); } }; kemudian tambahkan elemen ui untuk menampilkan pengguna yang menyukai postingan \<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> )} proyek back4gram temukan di sini kode lengkap untuk sebuah proyek contoh jaringan sosial yang dibangun dengan back4app langkah 6 ā mengimplementasikan pembaruan waktu nyata dengan parse live query salah satu fitur paling kuat dari back4app adalah kemampuan untuk menerima pembaruan waktu nyata dengan live query ini memungkinkan aplikasi anda untuk secara otomatis memperbarui ketika postingan atau komentar baru dibuat tanpa perlu menyegarkan secara manual mengatur live query di back4app pertama, anda perlu mengaktifkan live query untuk aplikasi anda di back4app pergi ke dasbor back4app anda navigasi ke pengaturan aplikasi > pengaturan server gulir ke bawah ke "nama kelas live query parse" tambahkan "post" dan "comment" ke daftar kelas simpan perubahan anda mengimplementasikan live query untuk postingan waktu nyata mari kita perbarui feedpage js untuk menyertakan live query untuk pembaruan pos secara real time // 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]); mengimplementasikan live query untuk komentar real time demikian pula, mari kita perbarui postdetailspage js untuk menyertakan live query untuk komentar real time // 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]); dengan implementasi live query ini, jaringan sosial anda sekarang akan diperbarui secara real time postingan baru dari pengguna lain akan muncul secara otomatis di umpan jumlah suka akan diperbarui secara real time ketika pengguna lain menyukai sebuah postingan komentar baru akan muncul secara otomatis di halaman detail postingan proyek back4gram temukan di sini kode lengkap untuk sebuah proyek contoh jaringan sosial yang dibangun dengan back4app langkah 7 ā kuery lanjutan dan optimisasi kinerja seiring pertumbuhan jejaring sosial anda, kinerja menjadi semakin penting mari kita eksplorasi beberapa teknik lanjutan untuk mengoptimalkan aplikasi anda menggunakan kemampuan kuery yang kuat dari back4app mengimplementasikan paginasi alih alih memuat semua pos sekaligus, terapkan paginasi untuk memuatnya dalam kelompok // 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> )} mengimplementasikan feed khusus pengguna untuk pengalaman yang lebih personal, anda mungkin ingin menampilkan pos hanya dari pengguna yang diikuti oleh pengguna saat ini // 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 } }; menggunakan cloud functions untuk operasi kompleks untuk operasi kompleks yang memerlukan beberapa kueri, pertimbangkan untuk menggunakan back4app cloud functions pergi ke dasbor back4app anda navigasi ke cloud code > cloud functions buat fungsi baru // 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}`); } }); kemudian panggil fungsi cloud ini dari aplikasi anda 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); } }; pendekatan ini dapat secara signifikan mengurangi jumlah kueri ke database dan meningkatkan kinerja proyek back4gram temukan di sini kode lengkap untuk sebuah proyek contoh jaringan sosial yang dibangun dengan back4app kesimpulan dalam tutorial ini, anda telah membangun umpan jejaring sosial yang komprehensif dengan fitur interaktif menggunakan back4app mari kita ringkas apa yang telah anda capai desain model data membuat model data terstruktur untuk pos, komentar, dan suka implementasi umpan membangun halaman umpan yang menampilkan pos dari semua pengguna pembuatan pos mengimplementasikan fungsionalitas bagi pengguna untuk membuat pos dengan teks dan gambar sistem komentar membuat sistem komentar yang memungkinkan pengguna berinteraksi dengan pos fungsionalitas suka mengimplementasikan sistem suka yang sederhana dan lanjutan pembaruan waktu nyata menambahkan live query untuk pembaruan pos dan komentar secara waktu nyata optimisasi kinerja menjelajahi teknik untuk meningkatkan kinerja saat aplikasi anda berkembang back4app telah menyediakan basis data yang aman dan dapat diskalakan untuk menyimpan konten yang dihasilkan pengguna penyimpanan file untuk gambar pos autentikasi pengguna bawaan untuk keamanan kueri langsung untuk pembaruan waktu nyata fungsi cloud untuk operasi backend yang kompleks dengan blok bangunan ini, jaringan sosial anda memiliki fondasi yang solid yang dapat diskalakan seiring pertumbuhan basis pengguna anda langkah selanjutnya untuk lebih meningkatkan jaringan sosial anda, pertimbangkan untuk menerapkan fitur fitur ini notifikasi mengingatkan pengguna ketika pos mereka menerima suka atau komentar sistem mengikuti pengguna memungkinkan pengguna untuk saling mengikuti dan menyesuaikan umpan mereka hashtag dan pencarian menerapkan sistem hashtag dan fungsionalitas pencarian yang ditingkatkan pos kaya media mendukung video, beberapa gambar, dan jenis media lainnya analitik melacak keterlibatan dan perilaku pengguna untuk meningkatkan aplikasi anda untuk kode lengkap dari aplikasi jaringan sosial back4gram, anda dapat memeriksa repositori github https //github com/templates back4app/back4gram dengan memanfaatkan layanan backend yang kuat dari back4app, anda dapat fokus pada menciptakan pengalaman pengguna yang hebat sementara platform menangani infrastruktur kompleks yang diperlukan untuk aplikasi jaringan sosial