Install @teamhanko/hanko-elements

Once you’ve initialized your React app, installing hanko-elements provides you with access to the prebuilt components: hanko-auth and hanko-profile.

Add the Hanko API URL

Retrieve the API URL from the Hanko console and place it in your .env file.

.env
REACT_APP_HANKO_API_URL=https://f4****-4802-49ad-8e0b-3d3****ab32.hanko.io

If you are self-hosting you need to provide the URL of your running Hanko backend.

Add <hanko-auth> component

The <hanko-auth> web component adds a login interface to your app. Begin by importing the register function from @teamhanko/hanko-elements into your React component. Call it with the Hanko API URL as an argument to register <hanko-auth> with the browser’s CustomElementRegistry. Once done, include it in your JSX.

components/HankoAuth.tsx
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";

const hankoApi = process.env.REACT_APP_HANKO_API_URL;

export default function HankoAuth() {
  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-auth />;
}

Define event callbacks

The Hanko client from @teamhanko/hanko-elements lets you “listen” for specific events. It simplifies the process of subscribing to events, such as user logins.

components/HankoAuth.tsx
import { useEffect, useCallback, useMemo } from "react";
import { useNavigate } from "react-router-dom";
import { register, Hanko } from "@teamhanko/hanko-elements";

const hankoApi = process.env.REACT_APP_HANKO_API_URL;

export default function HankoAuth() {
  const navigate = useNavigate();
  const hanko = useMemo(() => new Hanko(hankoApi), []);

  const redirectAfterLogin = useCallback(() => {
    navigate("/dashboard");
  }, [navigate]);

  useEffect(
    () =>
      hanko.onSessionCreated(() => {
        redirectAfterLogin();
      }),
    [hanko, redirectAfterLogin]
  );

  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-auth />;
}

By now, your sign-up and sign-in features should be working. You should see an interface similar to this 👇

Add <hanko-profile> component

The <hanko-profile> component provides an interface, where users can manage their email addresses and passkeys.

components/HankoProfile.tsx
import { useEffect } from "react";
import { register } from "@teamhanko/hanko-elements";

const hankoApi = process.env.REACT_APP_HANKO_API_URL;

export default function HankoProfile() {
  useEffect(() => {
    register(hankoApi).catch((error) => {
      // handle error
    });
  }, []);

  return <hanko-profile />;
}

It should look like this 👇

Implement logout functionality

You can use @teamhanko/hanko-elements to easily manage user logouts. Below, we make a logout button component that you can use anywhere.

components/LogoutButton.tsx
import React, { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Hanko } from "@teamhanko/hanko-elements";

const hankoApi = process.env.REACT_APP_HANKO_API_URL;

function LogoutBtn() {
  const navigate = useNavigate();
  const [hanko, setHanko] = useState<Hanko>();

  useEffect(() => {
    import("@teamhanko/hanko-elements").then(({ Hanko }) =>
      setHanko(new Hanko(hankoApi ?? ""))
    );
  }, []);

  const logout = async () => {
    try {
      await hanko?.user.logout();
      navigate("/login");
    } catch (error) {
      console.error("Error during logout:", error);
    }
  };

  return <button onClick={logout}>Logout</button>;
}

export default LogoutBtn;

Customize component styles

You can customize the appearance of hanko-auth and hanko-profile components using CSS variables and parts. Refer to our customization guide.

Getting User and Session Data

Client Side

We will create two custom hooks to fetch the current user’s data and session data using @teamhanko/hanko-elements. The useUserData hook leverages the hanko.user.getCurrent() method to retrieve the currently logged-in user’s data.

On the other hand, the useSessionData hook uses the hanko.session.isValid() and hanko.session.get() methods to validate and fetch the current session data.

hooks/useUserData.ts
import { useState, useEffect, useMemo } from "react";
import { Hanko } from "@teamhanko/hanko-elements";

const hankoApi = process.env.REACT_APP_HANKO_API_URL;
// const hankoApi = import.meta.env.VITE_HANKO_API_URL; // for Vite

interface HankoUser {
  id: string;
  email: string;
  loading: boolean;
  error: string | null;
}

export function useUserData(): HankoUser {
  const hanko = useMemo(() => new Hanko(hankoApi), []);
  const [userState, setUserState] = useState<HankoUser>({
    id: "",
    email: "",
    loading: true,
    error: null,
  });

  useEffect(() => {
    hanko?.user
      .getCurrent()
      .then(({ id, email }) => {
        setUserState({ id, email, loading: false, error: null });
      })
      .catch((error) => {
        setUserState((prevState) => ({ ...prevState, loading: false, error }));
      });
  }, [hanko]);

  return userState;
}

This is how you can use them according to your needs.

components/userData.tsx
import { useUserData } from "@/hooks/useUserData";
import { useSessionData } from "@/hooks/useSessionData";

const UserData = () => {
  const { id, email, loading: userDataLoading, error: userDataError } = useUserData();
  const { userID, jwt, isValid, loading: sessionDataLoading, error: sessionDataError } = useSessionData();

  if (userDataLoading) {
    return <div>Loading...</div>;
  }

  return (
    <div>
      <div>User id: {id}</div>
      <div>User email: {email}</div>
    </div>
  );
};

export default UserData;

You might get "r" is read-only error after you deploy a CRA (Create React App). To fix it you’ll need to add @babel/core as a dependency in your project. You can do this by running the following command:

Authenticate backend requests

To authenticate requests on your backend with Hanko, refer to our backend guide.

Try it yourself

React example (Vite)

It uses Express.js for the backend, full source code available on our GitHub.