āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā ā š shadcn/directory/clerk/clerk-docs/guides/development/custom-flows/organizations/manage-membership-requests ā āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
This guide will demonstrate how to use the Clerk API to build a custom flow for managing organization membership requests.
<Tabs items={["Next.js", "JavaScript"]}> <Tab> The following example:
1. Uses the [`useOrganization()`](/docs/reference/hooks/use-organization) hook to get `membershipRequests`, which is a list of the [active organization's](!active-organization) membership requests.
- `membershipRequests` is an object with `data` that contains an array of [`OrganizationMembershipRequest`](/docs/reference/javascript/types/organization-membership-request) objects.
- Each `OrganizationMembershipRequest` object has an [`accept()`](/docs/reference/javascript/types/organization-membership-request#accept) and [`reject()`](/docs/reference/javascript/types/organization-membership-request#reject) method to accept or reject the membership request, respectively.
1. Maps over the `data` array to display the membership requests in a table, providing an "Accept" and "Reject" button for each request that calls the `accept()` and `reject()` methods, respectively.
This example is written for Next.js App Router but can be adapted for any React-based framework.
```jsx {{ filename: 'app/components/MembershipRequests.tsx', collapsible: true }}
'use client'
import { useOrganization } from '@clerk/nextjs'
export const MembershipRequestsParams = {
membershipRequests: {
pageSize: 5,
keepPreviousData: true,
},
}
// List of organization membership requests.
export const MembershipRequests = () => {
const { isLoaded, membershipRequests } = useOrganization(MembershipRequestsParams)
if (!isLoaded) {
return <>Loading</>
}
return (
<>
<h1>Membership requests</h1>
<table>
<thead>
<tr>
<th>User</th>
<th>Date requested</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{membershipRequests?.data?.map((mem) => (
<tr key={mem.id}>
<td>{mem.publicUserData.identifier}</td>
<td>{mem.createdAt.toLocaleDateString()}</td>
<td>
<button
onClick={async () => {
await mem.accept()
}}
>
Accept
</button>
<button
onClick={async () => {
await mem.reject()
}}
>
Reject
</button>
</td>
</tr>
))}
</tbody>
</table>
<div>
<button
disabled={!membershipRequests?.hasPreviousPage || membershipRequests?.isFetching}
onClick={() => membershipRequests?.fetchPrevious?.()}
>
Previous
</button>
<button
disabled={!membershipRequests?.hasNextPage || membershipRequests?.isFetching}
onClick={() => membershipRequests?.fetchNext?.()}
>
Next
</button>
</div>
</>
)
}
```
</Tab>
<Tab>
The following example:
1. Calls the [`getMembershipRequests()`](/docs/reference/javascript/organization#get-membership-requests) method to retrieve the list of membership requests for the active organization. This method returns `data`, which is an array of [`OrganizationMembershipRequest`](/docs/reference/javascript/types/organization-membership-request) objects.
1. Maps over the `data` array to display the membership requests in a table.
1. Provides an "Accept" and "Reject" button for each request that calls the [`accept()`](/docs/reference/javascript/types/organization-membership-request#accept) and [`reject()`](/docs/reference/javascript/types/organization-membership-request#reject) methods, respectively.
Use the tabs to view the code necessary for the `index.html` and `main.js` files.
<CodeBlockTabs options={["index.html", "main.js"]}>
```html {{ filename: 'index.html', collapsible: true }}
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clerk + JavaScript App</title>
</head>
<body>
<div id="app"></div>
<h1>Membership Requests</h1>
<table>
<thead>
<tr>
<th>User</th>
<th>Date requested</th>
<th>Accept</th>
<th>Reject</th>
</tr>
</thead>
<tbody id="requests-table-body"></tbody>
</table>
<script type="module" src="/src/main.js" async crossorigin="anonymous"></script>
</body>
</html>
```
```js {{ filename: 'main.js', collapsible: true }}
import { Clerk } from '@clerk/clerk-js'
const pubKey = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY
if (!pubKey) {
throw new Error('Add your VITE_CLERK_PUBLISHABLE_KEY to .env file')
}
const clerk = new Clerk('{{pub_key}}')
await clerk.load()
if (clerk.isSignedIn) {
// Check for an active organization
if (clerk.organization) {
const requestsTable = document.getElementById('requests-table-body')
const { data } = await clerk.organization
.getMembershipRequests()
.then((res) => console.log(`Membership requests:`, data).catch((err) => console.error(err)))
const requests = data
requests.map((request) => {
const row = requestsTable.insertRow()
row.insertCell().textContent = request.publicUserData.identifier
row.insertCell().textContent = request.createdAt.toLocaleDateString()
// Accept request
const acceptBtn = document.createElement('button')
acceptBtn.textContent = 'Accept'
acceptBtn.addEventListener('click', async function (e) {
e.preventDefault()
await request.accept()
})
row.insertCell().appendChild(acceptBtn)
// Reject request
const rejectBtn = document.createElement('button')
rejectBtn.textContent = 'Reject'
rejectBtn.addEventListener('click', async function (e) {
e.preventDefault()
await request.reject()
})
row.insertCell().appendChild(rejectBtn)
})
} else {
// If there is no active organization,
// mount Clerk's <OrganizationSwitcher />
// to allow the user to set an organization as active
document.getElementById('app').innerHTML = `
<h2>Select an organization to set it as active</h2>
<div id="org-switcher"></div>
`
const orgSwitcherDiv = document.getElementById('org-switcher')
clerk.mountOrganizationSwitcher(orgSwitcherDiv)
}
} else {
// If there is no active user, mount Clerk's <SignIn />
document.getElementById('app').innerHTML = `
<div id="sign-in"></div>
`
const signInDiv = document.getElementById('sign-in')
clerk.mountSignIn(signInDiv)
}
```
</CodeBlockTabs>
</Tab>
</Tabs>ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā ā
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā