JavaScript [React] Auth Komponente und API-Requests

O

owned139

Gast
Hallo zusammen,

ich code aktuell an einer React App und hänge an einer Stelle gedanklich etwas fest. Es geht hierbei um einen Userlogin und den Zugriff auf öffentliche und private API Endpunkte.
Hatte mir überlegt eine Auth-Komponente zu erstellen, welche sich um den User bzw. den Sessiontoken beim Login/Logout kümmert eine entspreche Rückmeldung liefert.

Die Komponente sieht wie folgt aus:
Javascript:
import React, { useContext, createContext, useState, useEffect } from 'react';
import axios from 'axios';

const AuthContext = createContext();
const requestLib = axios.create();

function useAuth () {
  return useContext(AuthContext);
}

function ProvideAuth ({ children }) {
  const auth = useProvideAuth();
  return <AuthContext.Provider value={auth}>{children}</AuthContext.Provider>;
}

function withAuth (Component) {
  return function WrapperComponent (props) {
    const auth = useAuth();
    return <Component auth={auth} {...props} />;
  };
}

function useProvideAuth () {
  const [ user, setUser ] = useState(null);
  const [ accessToken, setAccessToken ] = useState(null);

  const login = function (username, password) {
    return new Promise((resolve, reject) => {
      requestLib
        .post(
          '/login',
          { email: username, password: password },
        )
        .then(res => {
          setUser({ username: res.data.username });
          setAccessToken(res.data.accessToken);
          resolve();
        })
        .catch(error => {
          reject(error);
        });
    });
  };

  const logout = function () {
    return new Promise((resolve, reject) => {
      requestLib
        .post('/logout')
        .then(res => {
          setUser(null);
          setAccessToken(null);
          resolve();
        })
        .catch(error => {
          reject(error);
        });
    });
  };

  const isLoggedIn = function () {
    return user !== null && accessToken !== null;
  };

  return {
    user,
    accessToken,
    login,
    logout,
    isLoggedIn,
  };
}

export { useAuth, withAuth, ProvideAuth };

Nun hatte ich vor eine autarke Library zu erstellen, welche generell für API-Request zuständig ist. Als Beispiel habe ich mir das ungefähr so vorgstellt:

Javascript:
import axios from 'axios';

const requestLib = axios.create();

const fetchUserList = () => {
  return requestLib.get('/user-list');
};

export { fetchUserList };

Nun habe ich folgende Probleme:
  • Wenn ein Request gesendet wird, dann benötigt dieser möglicherweise bei privaten Endpunkten den AccessToken, welcher aber nur in der Auth-Komponente vorhanden ist.
  • Die Auth-Komponente bekommt bei einer Response nicht mit, wenn der Token verfällt
  • Die Requests teilen sich auf 2 Libs/Komponenten auf und sind nicht zentral.
Jetzt könnte ich daraus natürlich auch eine React Komponente machen, allerdings wollte ich das vermeiden, weil ich die Requests auch in React unabhängigen Funktionen/Klassen verwenden möchte.

Wie handhabt man sowas?
 
Zuletzt bearbeitet von einem Moderator:
Eine normale Klasse statt React Component machen?

Dann kapselst du API Aufrufe über eine eigene Klasse (statt überall direkt Axios zu nutzen), die den Token aus dem AuthService holt und bei einem HTTP 401 sich neu authentifiziert (bzw das an den AuthService delegiert) und den Request erneut absetzt.
 
Bagbag schrieb:
Dann kapselst du API Aufrufe über eine eigene Klasse (statt überall direkt Axios zu nutzen), die den Token aus dem AuthService holt...
Genau das ist das Problem. Der AuthService ist eine React Komponente mit States, welcher auch nur innerhalb von anderen React-Komponenten funktioniert. Meine RequestKlasse agiert eben nicht innerhalb des React Kontextes. Ich kann also nicht einfach so den AuthService importieren und mir den Token holen oder etwa doch?
 
Benutzt du eine uralt-React-Version? Falls nein: Bau dir eine AuthHook und pack da alles rein. Hooks lassen sich schachteln bzw. in anderen Hooks verwenden. Da ist am Anfang zwar etwas Umdenken angesagt, aber danach wird das Leben mit React viel viel einfacher.

Innerhalb der Hook hast du den State (eingeloggt oder nicht, auth token, und diverse Funktionen zum ein-/ausloggen, Token erneuern, etc.)

Diese Wrapper Components, die irgendwo ungefragt Properties injecten, würde ich komplett sein lassen.

"Echte" Klassen (= class) für Services solltest du in Kombination mit React Function Components tunlichst vermeiden. Sonst sitzt du am Ende in der "this is undefined" Hölle.


owned139 schrieb:
Die Requests teilen sich auf 2 Libs/Komponenten auf und sind nicht zentral.
Dann zieh den gemeinsamen Code raus in eine separate "common" Lib.

Grundsätzlich würde ich axios niemals direkt für meine Requests verwenden, sondern immer noch einen eigenen Layer dazwischen machen. Dieser Layer kann dann auch auf bestimmte Responses reagieren.
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: owned139
benneq schrieb:
Benutzt du eine uralt-React-Version? Falls nein: Bau dir eine AuthHook und pack da alles rein. Hooks lassen sich schachteln bzw. in anderen Hooks verwenden. Da ist am Anfang zwar etwas Umdenken angesagt, aber danach wird das Leben mit React viel viel einfacher.
Benutze die aktuelle, und verwende sowohl Hooks, als auch Classes, je nachdem was gerade Sinn macht. Nur auf Hooks setzen macht, meiner Meinung nach, nicht unbedingt Sinn. Vererbung kann ich dann beispielsweise knicken.

benneq schrieb:
Diese Wrapper Components, die irgendwo ungefragt Properties injecten, würde ich komplett sein lassen.
Wieso? i18next macht genau das selbe und React schlägt es ebenfalls vor: https://reactjs.org/docs/higher-order-components.html

benneq schrieb:
"Echte" Klassen (= class) für Services solltest du in Kombination mit React Function Components tunlichst vermeiden. Sonst sitzt du am Ende in der "this is undefined" Hölle.
Also alles komplett im React Kontext als Komponentenklasse bzw. Hook? Das fühlt sich für mich aber auch nicht richtig an, weil nicht jede Logik automatisch eine React Komponente sein muss.
 
owned139 schrieb:
Wieso? i18next macht genau das selbe und React schlägt es ebenfalls vor: https://reactjs.org/docs/higher-order-components.html
Es kommt halt darauf an, was man damit macht. HOCs sind gut, wenn man einen Kontext für alle child components bereitstellen will. Auf den Kontext greift man danach aber über Hooks zu: https://reactjs.org/docs/context.html

Also natürlich kannst du es auch mit deiner withAuth Component machen, aber ist halt umständlich und hat den Nachteil, dass der component tree unnötig mit wrapper components zugemüllt wird. Dazu kommt noch, dass du das "auth" Property nicht umbenennen kannst: Falls du mal irgendwann Bedarf hast, eine component mit <MyComp auth=... /> einzubinden, geht das nicht, weil das property wieder durch die wrapper component überschrieben wird. Mit Hooks passiert das nicht, weil du nur eine lokale Variable hast, die du beliebig umbenennen kannst.

In deinem Fall würde ich also z.B. in der index.tsx den AuthContext bereitstellen. Und dann in den einzelnen components (oder anderen Hooks) bei Bedarf mit useAuth darauf zugreifen.

Hooks machen auch den Code nicht größer oder komplexer. Ich würde in dem Fall sogar sagen, dass er einfacher wird, weil du direkt in deiner component sehen kannst, woher die auth Variable kommt, und man nicht erst noch zusätzlich nach HOCs (oder schlimmer: HOC in HOC in HOC-ception :D ) Ausschau halten muss.

Ansonsten auch einfach mal bei Google nach "react hoc vs hooks" suchen. Da gibt's reichlich Beispiele, warum Hooks und in welchen Ausnahmefällen HOCs das Mittel der Wahl sind :)

owned139 schrieb:
Also alles komplett im React Kontext als Komponentenklasse bzw. Hook? Das fühlt sich für mich aber auch nicht richtig an, weil nicht jede Logik automatisch eine React Komponente sein muss.
Klassen und React function components spielen halt nicht schön zusammen. Alternativ kann man natürlich auch seinen Konsturktor mit this.myMethod.bind(this); (für jede enthaltene Methode) vollklatschen. Ich verzichte da lieber gleich komplett auf Klassen.

Oder du baust dir klassen-ähnliche Konstrukte mit JS functions:
Code:
function lib() {
  let myLocalVar = 42;

  const myLocalMethod = () => {
    myLocalVar++;
  }

  return {
    myPublicMethod: () => myLocalMethod()
  }
}
Damit gibt's das this-Problem nicht.

Aber wenn man noch mal hinschaut sieht das ziemlich verdächtig nach Hook aus, oder? :D




Um noch mal auf dein eigentliches Problem zurückzukommen:

Schreib deine Request-"Klasse" einfach als Hook. Und abstrahiere axios komplett weg aus deinem restlichen Code, sodass du am Ende axios nur noch ausschließlich in dieser einen "Klasse" benutzt. Und alle anderen Teile deines Codes nur noch diese "Klasse" für ihre Requests nutzen. Dann hast du eine zentrale Stelle wo alles zusammenläuft und zusätzlich noch den Vorteil, dass du axios bei Bedarf auch ganz einfach austauschen kannst, weil es eben nur an einer Stelle verwendet wird. So kannst du auch eine RequestLibOne und RequestLibTwo haben, die beide jeweils die CommonRequestLib (die axios enthält) nutzen.



Also in etwa so (Achtung: potenziell pseudo code):
Code:
const axiosHttp = axios.create();

function commonRequestLib() {
  // hier laufen alle requests zusammen und können gemeinsam behandelt werden
  // z.B. responses auf expired token untersuchen
  return {
    httpGet: (url) => axiosHttp.get(url),
    httpPost: (url, data) => axiosHttp.post(url, data)
  }
}

// dadurch verwenden alle dieselbe Instanz von commonRequestLib
// nur wirklich notwendig, wenn commonRequestLib stateful sein soll (= lokale Variablen enthält)
export const crl = commonRequestLib();

// new file

import { crl } from '...';

function requestLibOne() {
  return {
    login: (email, password) => crl.httpPost('/login', { email, password })
  }
}

// new file

import { crl } from '...';

function requestLibTwo() {
  return {
    userList: () => crl.httpGet('/users')
  }
}
 
Zuletzt bearbeitet:
  • Gefällt mir
Reaktionen: owned139

Ähnliche Themen

Zurück
Oben