Erstellen eines Instagram-Klons in React with GraphQL und Hasura - Teil II

Dieses Tutorial wurde von Abhijeet Singh geschrieben und im Rahmen des Hasura Technical Writer Program veröffentlicht - einer Initiative, die Autoren unterstützt, die Anleitungen und Tutorials für die Open Source Hasura GraphQL Engine schreiben.

In Teil 1 dieser Serie richten wir unser Backend und Auth0 ein. In diesem Teil richten wir unsere React-App ein und verbinden sie mit unserem Backend.

App-Setup reagieren

Wir werden zunächst die Benutzerauthentifizierung implementieren. Wir werden JWT (JSON Web Token) zur Authentifizierung verwenden. Lassen Sie uns zunächst einige grundlegende Header in unserer Reaktions-App erstellen, um die Anmeldeschaltfläche anzuzeigen.

Ersetzen Sie den Inhalt der Datei styles / App.css durch diese Datei. Diese Stile werden in unserer gesamten App verwendet, sodass Sie sich nicht um das Styling kümmern müssen. Laden Sie diese Datei auch herunter und legen Sie sie in Ihrem Format / Verzeichnis ab. Wir werden dies verwenden, um verschiedene Schaltflächen in unserer App anzuzeigen.

Richten Sie den Apollo GraphQL Client ein

Ersetzen Sie den Inhalt von App.js, um den Apollo GraphQL-Client wie unten gezeigt zu verwenden. (Weitere Hilfe finden Sie im Apollo Github-Repository.)

Ändern Sie in Zeile 15 die URL in Ihren GraphQL-Endpunkt auf hasura, den Sie auf der hasura-Konsole finden (denken Sie daran, wo Sie Tabellen erstellt haben). Hier haben wir die Header-Komponente importiert, die wir jetzt implementieren werden.

Erstellen Sie eine Header-Komponente und verwenden Sie Reaktionswege:

Wir werden den React-Router verwenden, um das Anwendungsverhalten einer einzelnen Seite zu implementieren. Installieren Sie den React-Router mit:

$ npm installiere react-router-dom
Mit React Router und dynamischem clientseitigem Routing können wir eine einseitige Webanwendung mit Navigation erstellen, ohne dass die Seite beim Navigieren des Benutzers aktualisiert wird. React Router verwendet die Komponentenstruktur, um Komponenten aufzurufen, die die entsprechenden Informationen anzeigen.
Durch Verhindern einer Seitenaktualisierung und Verwenden von Router oder Link wird das Blinken eines weißen Bildschirms oder einer leeren Seite verhindert. Dies ist eine zunehmend verbreitete Methode für eine nahtlosere Benutzererfahrung. (Quelle)

Um React-Router in unserer App verwenden zu können, müssen wir die gesamte App in BrowserRouter einbinden. Dies ist ein Kontextanbieter für das Routing, der mehrere für das Routing erforderliche Requisiten bereitstellt (wie Übereinstimmung, Standort, Verlauf). Sehen Sie dies, wenn Sie mit dem Kontext nicht vertraut sind. Ersetzen Sie den Inhalt von index.js:

Als Nächstes erstellen wir eine Header-Komponente für die Navigation innerhalb der App. Erstellen Sie eine Header.js-Datei im Komponentenverzeichnis. Der Inhalt von Header.js sollte sein:

Hier erstellen wir eine Navigationsleiste ähnlich der Instagram-Navigationsleiste. Später werden wir einige Routen zur Navigation hinzufügen. Das ist es! Wir haben erfolgreich eine Header-Navigationsleiste erstellt und Reaktionsrouten in unserer App verwendet.

Im Moment sollte Ihre App so aussehen

Auth0 JWT-Integration mit React App

Befolgen Sie die Anleitung zum Auth0-React-Quickstart als Referenz, um Auth0 in die React-App aufzunehmen. Konfigurieren Sie den Auth0-Client, indem Sie zulässige Rückruf-URLs, zulässige Webursprünge und zulässige Abmelde-URLs auf http: // localhost: 3000 setzen und die benutzerdefinierte API hinzufügen, falls Sie dies noch nicht getan haben. Installieren Sie nun auth0-spa-js:

$ npm install @ auth0 / auth0-spa-js

Jetzt werden wir den React-Auth0-Wrapper in unsere App aufnehmen. Hierbei handelt es sich um eine Reihe von benutzerdefinierten React-Hooks, mit denen Sie mit dem Auth0-SDK arbeiten können. Erstellen Sie ein neues Verzeichnis src / auth und fügen Sie die Datei react-auth0-wrapper.js hinzu. Füllen Sie es von hier aus mit Code.

Fügen Sie nun eine weitere Datei als auth / auth_config.json in src / auth hinzu. Füllen Sie auth_config.json mit folgendem Code (ändern Sie die Werte entsprechend):

Jetzt können wir die Anmeldefunktion in unsere Reaktions-App aufnehmen. Grundsätzlich werden wir einen Login-Button in die Kopfzeile aufnehmen. Diese Schaltfläche führt zur Anmeldung über Auth0 mit Weiterleitung zu unserem lokalen Host, sobald die Anmeldung / Anmeldung abgeschlossen ist. Gleichzeitig werden die Anmelde- / Anmeldedaten in unserer Benutzertabelle im Hasura-Backend aufgrund der zuvor hinzugefügten Auth0-Regeln aktualisiert. Sobald die Anmeldung abgeschlossen ist, erhalten wir das accessToken im JWT-Format mithilfe der Funktionen, die vom Auth0 SDK in App.js bereitgestellt werden. Dieses accessToken wird dann als Autorisierungsheader in Apollo Client-Abfragen zum Backend verwendet, sodass jede Abfrage, die zum Backend geht, einen Autorisierungsheader hat.

Ändern Sie zunächst den Inhalt von index.js wie folgt:

Hier verwenden wir den Auth0Provider, einen Kontextanbieter für den Auth0-Client. Alle untergeordneten Komponenten haben jetzt Zugriff auf den Auth0-Client.

Nachdem wir den Auth0-Client für unsere App bereitgestellt haben, ersetzen wir nun den Inhalt der Datei components / Header.js durch Folgendes:

Wir verwenden den useAuth0-Hook (Zeile 7), um verschiedene von Auth0 bereitgestellte Funktionen zu nutzen. Mit isAuthenticated wird überprüft, ob der Benutzer angemeldet ist oder nicht. loginWithRedirect wird verwendet, um sich nach der Anmeldung bei der angegebenen Weiterleitungs-URL anzumelden und umzuleiten. Das Benutzerobjekt enthält Informationen zum aktuell angemeldeten Benutzer.

Wenn der Benutzer angemeldet ist, führen wir den Benutzer zum Benutzerprofil, das wir später implementieren werden. Wenn der Benutzer abgemeldet ist, wird nur die Anmeldeschaltfläche angezeigt.

Jetzt werden wir Änderungen in unserer App.js vornehmen, um die Auth0-Funktionalität einzuschließen. Ändern Sie den Inhalt von App.js wie folgt:

Wir verwenden den useState-Hook (Zeile 22), um den anfänglichen accessToken-Wert auf eine leere Zeichenfolge zu setzen. Wenn der Benutzer angemeldet ist, wird das Token mit getTokenSilently () (Zeile 33) vom Auth0 SDK-Client abgerufen. Beachten Sie, dass diese Funktion ein Versprechen zurückgibt und asynchron ist. Diese Funktion versucht, das aktuelle Zugriffstoken zurückzugeben. Wenn das Token ungültig ist, wird das Token stillschweigend aktualisiert, bevor es von der Funktion zurückgegeben wird. Wenn der try-Block erfolgreich ausgeführt wird, wird der Wert accessToken von Auth0 auf das JWT-Zugriffstoken gesetzt (Zeile 34).

Die Komponente wird erneut gerendert, wenn der Wert accessToken angezeigt wird. Nachdem die asynchrone Funktion ausgeführt wurde, speichern wir den Wert von accessToken im Status. Die Komponente wird neu gerendert und Apollo-Client erhält den Token-Wert, wodurch der gesamte ApolloProvider (Kontextanbieter) mit dem neuen Token-Wert und dem Authentifizierungsheader neu gerendert wird.

Sobald wir accessToken haben, werden wir dies verwenden, um mit dem Apollo-Client Anfragen an unser Backend zu richten. Informationen zur Apollo-Authentifizierung mithilfe von Headern finden Sie in den Apollo-Dokumenten. Grundsätzlich übergeben wir in unseren Apollo-Abfragen das accessToken als Autorisierungsheader (Zeile 52). Dieser Client wird dann im ApolloProvider (Kontextanbieter) verwendet, um den untergeordneten Elementen Zugriff auf den hier erstellten Apollo-Client zu gewähren.

Jetzt sollten Sie sich in unserer App anmelden können. Cache leeren und anmelden. Sie müssen von unserem hasura-Backend aufgefordert werden, Ihrem auth0-Mandanten Zugriff zu gewähren. Geben Sie den Zugang und Sie können loslegen.

Hinweis: Wenn Sie auf Fehler stoßen, denken Sie daran, keine Abhängigkeit von React-Apollo zu behalten. Stattdessen müssen @ apollo / react-hooks verwendet werden.
Jetzt sieht unsere App so aus

Implementieren von Feeds und Likes (Echtzeit-Updates von Likes)

Wir werden eine Liste von Posts (Feeds) und einen Like-Button implementieren. Erstellen Sie eine neue Komponente Komponenten / Feed.js als:

Die Abfrage POSTS_LIST (Zeile 8) wird verwendet, um Details aus der Post-Tabelle in unserer Datenbank abzurufen. Wir fragen die ID des Beitrags ab. useQuery (Zeile 18) ist ein benutzerdefinierter Apollo-Client-Reaktions-Hook. Wir erhalten die Abfragedaten im Datenobjekt (Zeile 18), das dann als Requisite an die Post-Komponente übergeben wird, die wir jetzt implementieren werden.

Erstellen Sie eine neue Komponente Komponenten / Post.js als:

Hier erhalten wir die Requisiten, die von der Feed.js-Komponente übergeben werden, und mithilfe der ID-Requisite erhalten wir die vollständigen Post-Daten mithilfe der POST_INFO-Abfrage. Wir rendern dann die Daten mit dem Styling in der return-Anweisung. Wir verwenden die Funktion timeDifferenceForDate (Zeile 68), um post.created_at in die Zeit im Instagram-Stil zu konvertieren. Jetzt müssen wir diese Funktion implementieren. Wir importieren auch Like-Komponenten, die sich um Like-Funktionen kümmern, die wir später implementieren werden.

Erstellen Sie ein neues Verzeichnis src / utils und erstellen Sie eine neue Datei TimeDifference.js als:

Es ist nur eine Dienstprogrammfunktion, um die Datums- und Uhrzeitdaten in unser erforderliches Format zu konvertieren.

Jetzt werden wir die Like-Komponente implementieren. Erstellen Sie eine neue Datei Komponenten / Like.js als:

Wie Komponenten bekommt die post_id durch Requisiten. Hier schreiben wir zwei Mutationen und eine Abfrage. FETCH_LIKES wird verwendet, um die Anzahl der Likes aus der Post-Tabelle abzurufen. Außerdem holen wir ab, ob der aktuell angemeldete Benutzer den Beitrag bereits gemocht hat (Zeile 15). LIKE_POST und DELETE_LIKE werden verwendet, um ein Like in die Like-Tabelle einzufügen bzw. aus der Like-Tabelle zu löschen.

Wir speichern countLikes (Anzahl der Likes) und Likes (wenn der Benutzer den Beitrag mag) in Statusvariablen. Wenn sich der Status ändert, wird die Like-Komponente neu gerendert, wodurch wir eine aktualisierte Ansicht erhalten, wenn dem Benutzer der Beitrag gefällt. Wenn dem Benutzer der Beitrag gefällt, wird ein rotes Herz angezeigt, ansonsten ein weißes Herz in der Benutzeroberfläche. Um dies zu implementieren, überprüfen wir den Wert von "Gefällt mir" (Zeile 104) und rendern die Schaltflächen entsprechend. Wenn der Benutzer den Beitrag mag, ändert sich der Status (Zeile 109), die Komponenten werden neu gerendert und es kommt zu einer ähnlichen Mutation (Zeile 108), die das Gleiche in der Datenbank aufzeichnet, und die Anzahl der Likes wird erhöht (Zeile 110).

Wir haben zwei Mutationen, die das Gleiche einreichen (Zeile 58) und das Gleiche löschen (Zeile 69). Beide Mutationen verwenden das Argument refetchQueries (Zeile 60), mit dem die Abfrage FETCH_LIKES erneut abgerufen wird, wodurch der Apollo-Cache mit neuen Werten aktualisiert wird. Dies implementiert Echtzeit-Likes.

Wir haben jetzt alle Komponenten installiert, um den Post-Feed zu implementieren. Wir müssen App.js ändern, um Feed.js einzuschließen. Nehmen Sie folgende Änderungen in Ihrer App.js vor:

Der Switch ist ein Teil des React-Routers, mit dem Komponenten mit ihren Pfaden abgeglichen werden. Fügen Sie einige zufällige Daten (Beiträge) aus der Hasura-Konsole in die Post-Tabelle ein und probieren Sie die App aus.

Jetzt sieht unsere App so aus

Versuchen Sie, Beiträge zu mögen, und sehen Sie dank refetchQueries die Echtzeit-Updates in Likes. Wir haben das Benutzerprofil noch nicht implementiert, daher funktionieren die Benutzerprofil-Links nicht. Als nächstes werden wir das gleiche implementieren.

Benutzerprofil implementieren

Unser Benutzerprofil verfügt über eine Benutzeroberfläche im Instagram-Stil mit Benutzerinformationen oben und einem Raster von Posts, die vom Benutzer unten hochgeladen wurden. Wir werden das Profil in zwei Komponenten implementieren, eine kümmert sich um das Rendern der Hauptbenutzeroberfläche und die andere übernimmt die Follow-Funktionalität.

Erstellen Sie eine neue Komponente Komponenten / Profile.js als:

Wir haben drei verschiedene Abfragen, die alle Basisdaten des anzuzeigenden Benutzers abrufen. Beachten Sie, dass wir alle Abfragen auf einmal hätten aufrufen können, aber beim erneuten Abrufen der Abfragen im Falle einer Follow-Mutation müssen wir alle Daten erneut abrufen, um den Cache zu aktualisieren, aber nur die Follow-Daten hätten sich geändert. Daher haben wir zwei separate Abfragen für NUMBER_OF_FOLLOWERS (Zeile 41) und NUMBER_OF_FOLLOWING (Zeile 31) durchgeführt. Wir haben diese Abfragen exportiert, sodass wir bei der Implementierung der Follow-Komponente die Abfragen importieren und erneut abrufen können. Dies wird klarer, sobald wir mit der Implementierung der Follow-Funktionalität beginnen.

Wir erhalten user_id als Requisiten, die verwendet werden, um unsere Backend-Datenbank nach Benutzerinformationen für die angegebene user_id abzufragen. Die Daten werden dann im Gegenzug () gerendert. Die Requisiten (user_id) hier werden in Form einer URL übergeben, und wir verwenden props.match.params.id, um diese Requisite zu erhalten. Diese Requisiten werden vom Kontextanbieter BrowserRouter des React-Routers bereitgestellt, der in unserer Datei index.js enthalten ist.

Die Abfrage USER_INFO wird verwendet, um Daten aus der Tabelle User and Post abzurufen. In Zeile 103 prüfen wir, ob das aktuell angezeigte Profil mit dem aktuell angemeldeten Benutzer übereinstimmt. In diesem Fall wird eine Schaltfläche zum Abmelden angezeigt. Wenn das Profil von anderen Benutzern stammt, wird stattdessen die Schaltfläche "Folgen" angezeigt. Die Funktion isLoggedUser wird verwendet, um diesen Zustand zu überprüfen. Die Schaltfläche "Folgen" ist in der Komponente "Folgen" implementiert, die wir als Nächstes implementieren werden.

Außerdem verwenden wir React-Bootstrap-Zeilen, um das Beitragsraster am unteren Rand des Benutzerprofils mit drei Elementen pro Zeile (Zeile 147) zu implementieren. Jedes Beitragselement im Raster ist ein anklickbarer Link, der zum jeweiligen Beitrag führt. Hier übergeben wir die ID als Requisiten über die URL (an = {"/ post /" + post.id}) in Zeile 148, auf die über props.match.params.id in der empfangenden Komponente zugegriffen wird. Dies ist eine React-Router-Methode zum Übergeben von Requisiten. In diesem Beispiel finden Sie weitere Details.

Jetzt implementieren wir die Follow-Komponente. Erstellen Sie eine neue Dateikomponente / Follow.js als:

Dies ist identisch mit der Like-Komponente. Mit der Abfrage FETCH_FOLLWERS wird abgerufen, ob der aktuell angemeldete Benutzer dem aktuell gerenderten Profil folgt. Wenn die von FETCH_FOLLWERS zurückgegebenen Daten nicht leer sind, setzen wir den Status "folge "zunächst auf" true "(Zeile 115). Hier verwenden wir einen Status (Zeile 49), um zu überprüfen, ob der aktuelle Benutzer dem angezeigten Profil folgt, und eine Referenzvariable firstRun (Zeile 52), die überprüft, ob die Komponente zum ersten Mal gerendert wird, was nützlich ist, wie wir möchten Führen Sie bestimmte Operationen (Zeile 112) nur beim ersten Rendern der Komponente aus, z. B. das Setzen des Status auf true oder false, abhängig von den von der Abfrage FETCH_FOLLWERS zurückgegebenen Daten.

Außerdem verwenden wir zwei Mutationen FOLLOW_USER und UNFOLLOW_USER, die Daten aus der Follow-Tabelle in unserem Backend einfügen und löschen. Beachten Sie, dass beide Mutationen drei Abfragen (Zeile 66) erneut abrufen, um den Apollo-Cache nach der Mutation mit den richtigen Daten zu aktualisieren. Dadurch werden automatisch Echtzeit-Datenaktualisierungen implementiert, bei denen sobald die Mutation durchgeführt wird, die Anzahl der Follower des angezeigten Profils und die Anzahl der folgenden der angemeldeten Benutzeraktualisierungen.

Jetzt werden wir die erforderlichen Änderungen in App.js vornehmen. Erstellen Sie zunächst eine neue Datei als Komponenten / SecuredRoute.js als:

Auf diese Weise können wir einige gesicherte Routen erstellen, auf die nur zugegriffen werden kann, wenn der Benutzer angemeldet ist. Wir werden während des Routings gesicherte Routen verwenden. Wenn jemand über die gesicherte Route versucht, auf die URLs zuzugreifen, ohne sich anzumelden, wird der Benutzer zur automatischen Anmeldung weitergeleitet.

Nehmen Sie nun die folgenden Änderungen in App.js vor:

Jetzt sollten Sie Benutzerprofile besuchen können. Fügen Sie einige Beispieldaten von der Hasura-Konsole ein, sehen Sie sich die Benutzerprofile an und folgen Sie den Funktionen. Das Echtzeit-Update finden Sie in der folgenden Funktion.

Das Benutzerprofil sieht so aus

Implementieren der Funktion zum Senden von Posts

Erstellen Sie eine neue Datei components / Upload.js wie folgt:

Die SUBMIT_POST-Mutation wird verwendet, um einen Eintrag in unsere Datenbanktabelle Post vorzunehmen. Wir verwenden das React-Bootstrap-Modal, um ein Popup-Feld zur Eingabe von URL- und Beschriftungswerten anzuzeigen. Derzeit wird das Hochladen von Bildern nicht unterstützt, da wir keinen Speicherdienst zum Speichern von Bildern implementieren.

Wir haben ein Formular (Zeile 48) mit zwei Eingabefeldern für Beschriftung und URL. Wir verwenden den Reaktionsstatus, um Werte für Beschriftung, URL und Fehler zu speichern (wenn die Mutation nicht erfolgreich ist). Wenn das Formular gesendet wird, wird die submitPost-Mutation aufgerufen, die die Daten ändert und refetchQueries aktualisiert die Daten im Apollo-Cache für die Abfragen POST_LIST und USER_INFO, wodurch der Feed bzw. das Benutzerprofil aktualisiert werden.

Jetzt werden wir die erforderlichen Änderungen in App.js vornehmen:

Wenn der Benutzer authentifiziert ist, wird eine Upload-Schaltfläche angezeigt, die beim Klicken das folgende Popup öffnet:

Endlich haben wir unsere App mit Upload-Post-Funktionalität fertig. Sie können zu Benutzerprofilen navigieren, neue Beiträge erstellen und Echtzeit-Updates von neuen Beiträgen, Likes und Followern anzeigen.

Sie sollten jetzt einen funktionierenden Instagram-Klon haben. Falls Sie darauf verweisen möchten, wird der endgültige Code für diese App hier gehostet. Sehen Sie hier die Live-Demo der App.

Danksagung:

TimeDifference-Funktion: https://github.com/howtographql/react-apollo

Einige Stile stammen von: https://pusher.com/tutorials/instagram-clone-part-1

Über den Autor

Abhijeet Singh ist UG-Student im letzten Jahr in Informatik und Ingenieurwesen am IIIT Kalyani. Er hat in den Bereichen Full Stack-Entwicklung, Android, Deep Learning, Maschinelles Lernen und NLP gearbeitet. Er nimmt aktiv an wettbewerbsorientierten Programmierwettbewerben teil und hat Interesse an der Lösung algorithmischer Probleme. Er ist ein Startup-Enthusiast und spielt in seiner Freizeit Tischtennis und Gitarre.

Ursprünglich veröffentlicht am 6. September 2019 unter https://blog.hasura.io.