Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/auth/delete-user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Delete User

You can delete a currently signed-in user using one of the following methods:

- [`useDeleteUser` hook](../hooks/useDeleteUser.md)
2 changes: 1 addition & 1 deletion docs/auth/get-user.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

You can use one of the methods below to get the current user:

- [`useUser` hook](../hooks/useUser-hook.md)
- [`useUser` hook](../hooks/useUser.md)
2 changes: 1 addition & 1 deletion docs/auth/sign-out.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

You can use one of the ways below to sign out currently signed-in user:

- [`useSignOut` hook](../hooks/useSignOut-hook.md) to handle sign-out logic yourself
- [`useSignOut` hook](../hooks/useSignOut.md) to handle sign-out logic yourself
- [`SignOut` component](../components/SignOut.md) to let `firereact` to handle the logic
12 changes: 12 additions & 0 deletions docs/auth/sign-up.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!--
Copyright (c) 2024 Eray Erdin

This software is released under the MIT License.
https://opensource.org/licenses/MIT
-->

# Sign Up

You can use one of the following ways to sign up a user.

- [`useSignUp` hook](../hooks/useSignUp.md)
2 changes: 1 addition & 1 deletion docs/components/SignOut.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,4 @@ Input parameters for `FirestoreDocument` component is as follows:
| `onAnonymous` | `() => ReactNode` | The component to render the user is anonymous. | ❌ | An empty component. |

[AuthRefDoc]: https://firebase.google.com/docs/reference/node/firebase.auth.Auth
[OnAnonymity]: ../hooks/useSignOut-hook.md#on-anonymity
[OnAnonymity]: ../hooks/useSignOut.md#on-anonymity
4 changes: 2 additions & 2 deletions docs/firestore/creating-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

You can use one of the ways below to create documents in Firebase:

- [`useAddDocument` hook](../hooks/useAddDocument-hook.md) to add a document into a collection with a random-generated ID
- [`useSetDocument` hook](../hooks/useSetDocument-hook.md) to add a document by defining its path (thus, its ID)
- [`useAddDocument` hook](../hooks/useAddDocument.md) to add a document into a collection with a random-generated ID
- [`useSetDocument` hook](../hooks/useSetDocument.md) to add a document by defining its path (thus, its ID)
2 changes: 1 addition & 1 deletion docs/firestore/deleting-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

You can use one of the ways below to delete documents in Firebase:

- [`useDeleteDocument` hook](../hooks/useDeleteDocument-hook.md)
- [`useDeleteDocument` hook](../hooks/useDeleteDocument.md)
2 changes: 1 addition & 1 deletion docs/firestore/listing-a-collection.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

You can use one of the ways below to list a collection in Firebase:

- [`useCollection` hook](../hooks/useCollection-hook.md)
- [`useCollection` hook](../hooks/useCollection.md)
2 changes: 1 addition & 1 deletion docs/firestore/reading-a-single-document.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

You can use one of the ways below to read a single Firestore document:

- [`useDocument` hook](../hooks/useDocument-hook.md)
- [`useDocument` hook](../hooks/useDocument.md)
- [`FirestoreDocument` component](../components/FirestoreDocument.md)
2 changes: 1 addition & 1 deletion docs/firestore/updating-documents.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@

You can use one of the ways below to update documents in Firebase:

- [`useSetDocument` hook](../hooks/useSetDocument-hook.md) overwrite an existing document or merge new data into it
- [`useSetDocument` hook](../hooks/useSetDocument.md) overwrite an existing document or merge new data into it
File renamed without changes.
File renamed without changes.
File renamed without changes.
59 changes: 59 additions & 0 deletions docs/hooks/useDeleteUser.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---
tags:
- hook
---

# `useDeleteUser` hook

`useDeleteUser` hook is used to delete the currently signed-in user. A very simple example would be:

```typescript
const { dispatch } = useDeleteUser({ auth });
await dispatch();
```

!!! warning
`useDeleteUser` is lazy by default and will not do anything until you use `dispatch` function.

You can also get the state[^unauthorized] of deletion process.

```typescript
const { state, dispatch } = useDeleteUser({ auth });
await dispatch();
// `state` is "ready" | "loading" | "anonymous"
```

!!! warning
`useDeleteUser` automatically listens to authentication state and will be `"anonymous"` if the user is anonymous. In `"anonymous"` state, `dispatch` will simply do nothing even if it is invoked.

By default, `"anonymous"` state includes both real anonymous and Firebase-handled anonymous users[^anonymity]. If you'd like to enable deleting Firebase-handled anonymous users as well, you can use `includeFirebaseAnon` as such:

```typescript
// assuming user is Firebase-handled anon
const { dispatch } = useDeleteUser({ auth, includeFirebaseAnon: true });
await dispatch(); // this will delete anonymous user
```

## Input Parameters

Input parameters for `useDeleteUser` hook is as follows:

| Name | Type | Description | Required | Default Value |
|---|---|---|---|---|
| `auth` | [`firebase/auth/Auth`][AuthRefDoc] | Reference to the Firebase Auth service instance. | ✅ | - |
| `includeFirebaseAnon` | `boolean` | Enable deleting Firebase-handled anonymous users. | ❌ | `false` |

## Return Type

`useDeleteUser` hook returns an object with properties as below:

| Name | Type | Description |
|---|---|---|
| `state` | `"ready" | "loading" | "anonymous"`[^unauthorized] | The state of sign-up process. |
| `dispatch` | `() => Promise<void>` | A callback to start deletion process. |

[^unauthorized]: You can consider `"anonymous"` state as logically *unauthorized*. Your website visitors are not authorized to delete users if they are anonymous (signed-in).

[^anonymity]: See ["On Anonimity" section on `useSignOut` hook](useSignOut.md#on-anonymity) to learn more about how Firebase handles anonymity.

[AuthRefDoc]: https://firebase.google.com/docs/reference/node/firebase.auth.Auth
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion docs/hooks/useSignOut-hook.md → docs/hooks/useSignOut.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ await dispatch();

## On Anonymity

In Firebase, there are two types of anonymity: Firebase-handle anonymous users (which are stored in Firebase and seen as real users) and real anonymous users (which are essentially `null` users).
In Firebase, there are two types of anonymity: Firebase-handled anonymous users (which are stored in Firebase and seen as real users) and real anonymous users (which are essentially `null` users).

`useSignOut` considers both cases as anonymous and behaves accordingly. So, in a case where user is *Firebase-handled* or *really* anonymous, `useSignOut` will have `"anonymous"` state. If, for a reason, this behavior is not desirable for you, you can use `onlyRealAnon` parameter on `useSignOut` hook. To see both cases, check this code:

Expand Down
51 changes: 51 additions & 0 deletions docs/hooks/useSignUp.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
---
tags:
- hook
---

# `useSignUp` hook

`useSignUp` hook is used to create a user with email and password. A very simple example would be:

```typescript
const { dispatch } = useSignUp({ auth });
await dispatch(email, password);
```

!!! warning
`useSignUp` is lazy by default and will not do anything until you use `dispatch` function.

You can also get the state[^unauthorized] of sign-out process.

```typescript
const { state, dispatch } = useSignUp({ auth });
await dispatch();
// `state` is "ready" | "loading" | "authenticated"
```

!!! warning
`useSignUp` automatically listens to authentication state and will be `"authenticated"` if the user is authenticated. In `"authenticated"` state, `dispatch` will simply do nothing even if it is invoked.

`dispatch` method will return an instance of [`UserCredential`][UserCredentialDocRef] if successful or `undefined` if user is already authenticated (thus, unauthorized to create a new user).

## Input Parameters

Input parameters for `useSignUp` hook is as follows:

| Name | Type | Description | Required | Default Value |
|---|---|---|---|---|
| `auth` | [`firebase/auth/Auth`][AuthRefDoc] | Reference to the Firebase Auth service instance. | ✅ | - |

## Return Type

`useSignUp` hook returns an object with properties as below:

| Name | Type | Description |
|---|---|---|
| `state` | `"ready" | "loading" | "authenticated"`[^unauthorized] | The state of sign-up process. |
| `dispatch` | `(email: string, password: string) => Promise<UserCredential>` | A callback to start sign-up process. |

[^unauthorized]: You can consider `"authenticated"` state as logically *unauthorized*. Your website visitors are not authorized to create a new users if they are authenticated (signed-in).

[AuthRefDoc]: https://firebase.google.com/docs/reference/node/firebase.auth.Auth
[UserCredentialDocRef]: https://firebase.google.com/docs/reference/js/auth.usercredential
File renamed without changes.
17 changes: 10 additions & 7 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,17 +49,20 @@ nav:
- firestore/deleting-documents.md
- Auth:
- auth/get-user.md
- auth/sign-up.md
- auth/sign-out.md
- auth/delete-user.md
- Hooks:
- Firestore:
- hooks/useDocument-hook.md
- hooks/useCollection-hook.md
- hooks/useAddDocument-hook.md
- hooks/useSetDocument-hook.md
- hooks/useDeleteDocument-hook.md
- hooks/useDocument.md
- hooks/useCollection.md
- hooks/useAddDocument.md
- hooks/useSetDocument.md
- hooks/useDeleteDocument.md
- Auth:
- hooks/useUser-hook.md
- hooks/useSignOut-hook.md
- hooks/useUser.md
- hooks/useSignOut.md
- hooks/useDeleteUser.md
- Components:
- Firestore:
- components/FirestoreDocument.md
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "firereact",
"version": "0.4.2",
"version": "0.4.3",
"description": "React hooks, components and utils for Firebase",
"type": "module",
"main": "dist/index.cjs.js",
Expand Down
4 changes: 4 additions & 0 deletions src/auth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,7 @@ export * from "./useUser";
export * from "./useSignOut";

export * from "./SignOut";

export * from "./useSignUp";

export * from "./useDeleteUser";
113 changes: 113 additions & 0 deletions src/auth/useDeleteUser.hook.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// Copyright (c) 2024 Eray Erdin
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT

import { renderHook } from "@testing-library/react";
import { FirebaseError } from "firebase/app";
import {
UserCredential,
createUserWithEmailAndPassword,
deleteUser,
signInAnonymously,
signInWithEmailAndPassword,
signOut,
} from "firebase/auth";
import sleep from "sleep-sleep";
import { useDeleteUser } from ".";
import { auth } from "../firebase";

const generateEmail = (id: string) => `usedeleteuser_${id}@hook.com`;
const password = "111111" as const;

describe("when real anon, useDeleteUser hook", () => {
beforeEach(async () => {
await signOut(auth);
});

it("should have anonymous state", async () => {
const { result } = renderHook(() => useDeleteUser({ auth }));
const { state } = result.current;
expect(state).toBe("anonymous");
});
});

describe("when anon, useDeleteUser hook", () => {
let credential: UserCredential;

beforeEach(async () => {
credential = await signInAnonymously(auth);
});

afterEach(async () => {
await signOut(auth);
await deleteUser(credential.user);
});

it("should have anonymous state", async () => {
const { result } = renderHook(() => useDeleteUser({ auth }));
const { state } = result.current;
expect(state).toBe("anonymous");
});

it("should have ready state if includeFirebaseAnon", async () => {
const { result } = renderHook(() =>
useDeleteUser({ auth, includeFirebaseAnon: true }),
);
const { state } = result.current;
expect(state).toBe("ready");
});
});

describe("when authed, useDeleteUser hook", () => {
let credential: UserCredential;
let emailIndex: number = 0;

beforeEach(async () => {
const email = generateEmail(emailIndex.toString());

await createUserWithEmailAndPassword(auth, email, password);
credential = await signInWithEmailAndPassword(
auth,
generateEmail(emailIndex.toString()),
password,
);
emailIndex++;
});

afterEach(async () => {
await signOut(auth);
try {
await deleteUser(credential.user);
} catch (e) {
if (e instanceof FirebaseError && e.code == "auth/user-token-expired") {
return;
}
throw e;
}
});

it("should have ready state", async () => {
const { result } = renderHook(() => useDeleteUser({ auth }));
const { state } = result.current;
expect(state).toBe("ready");
});

it("should delete user", async () => {
const { result } = renderHook(() => useDeleteUser({ auth }));
const { dispatch } = result.current;
await dispatch();
await sleep(100);
const { state } = result.current;
expect(state).toBe("anonymous");
});

it("should have loading state while dispatching", async () => {
const { result } = renderHook(() => useDeleteUser({ auth }));
const { dispatch } = result.current;
dispatch();
await sleep(1);
const { state } = result.current;
expect(state).toBe("loading");
});
});
Loading