Build a custom flow for managing member roles in an organization
Organization members with appropriate permissions can manage a member's role and remove members within an organization.
This guide will demonstrate how to use the Clerk API to build a custom flow for managing member roles in an organization.
The following example uses the useOrganization()
hook to get memberships
, which is a list of the active organization's memberships.
memberships
is an object with data
that contains an array of OrganizationMembership
objects.
Each OrganizationMembership
object has an update()
and destroy()
method to update the member's role and remove the member from the organization, respectively.
This example is written for Next.js App Router but can be adapted for any React meta framework, such as Remix.
'use client'
import { useState, useEffect, ChangeEventHandler, useRef } from 'react'
import { useOrganization, useUser } from '@clerk/nextjs'
import type { OrganizationCustomRoleKey } from '@clerk/types'
export const OrgMembersParams = {
memberships: {
pageSize: 5,
keepPreviousData: true,
},
}
// List of organization memberships. Administrators can
// change member roles or remove members from the organization.
export const ManageRoles = () => {
const { user } = useUser()
const { isLoaded, memberships } = useOrganization(OrgMembersParams)
if (!isLoaded) {
return <>Loading</>
}
return (
<>
<h1>Memberships List</h1>
<table>
<thead>
<tr>
<th>User</th>
<th>Joined</th>
<th>Role</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{memberships?.data?.map((mem) => (
<tr key={mem.id}>
<td>
{mem.publicUserData.identifier} {mem.publicUserData.userId === user?.id && '(You)'}
</td>
<td>{mem.createdAt.toLocaleDateString()}</td>
<td>
<SelectRole
defaultRole={mem.role}
onChange={async (e) => {
await mem.update({
role: e.target.value as OrganizationCustomRoleKey,
})
await memberships?.revalidate()
}}
/>
</td>
<td>
<button
onClick={async () => {
await mem.destroy()
await memberships?.revalidate()
}}
>
Remove
</button>
</td>
</tr>
))}
</tbody>
</table>
<div>
<button
disabled={!memberships?.hasPreviousPage || memberships?.isFetching}
onClick={() => memberships?.fetchPrevious?.()}
>
Previous
</button>
<button
disabled={!memberships?.hasNextPage || memberships?.isFetching}
onClick={() => memberships?.fetchNext?.()}
>
Next
</button>
</div>
</>
)
}
type SelectRoleProps = {
fieldName?: string
isDisabled?: boolean
onChange?: ChangeEventHandler<HTMLSelectElement>
defaultRole?: string
}
const SelectRole = (props: SelectRoleProps) => {
const { fieldName, isDisabled = false, onChange, defaultRole } = props
const { organization } = useOrganization()
const [fetchedRoles, setRoles] = useState<OrganizationCustomRoleKey[]>([])
const isPopulated = useRef(false)
useEffect(() => {
if (isPopulated.current) return
organization
?.getRoles({
pageSize: 20,
initialPage: 1,
})
.then((res) => {
isPopulated.current = true
setRoles(res.data.map((roles) => roles.key as OrganizationCustomRoleKey))
})
}, [organization?.id])
if (fetchedRoles.length === 0) return null
return (
<select
name={fieldName}
disabled={isDisabled}
aria-disabled={isDisabled}
onChange={onChange}
defaultValue={defaultRole}
>
{fetchedRoles?.map((roleKey) => (
<option key={roleKey} value={roleKey}>
{roleKey}
</option>
))}
</select>
)
}
Feedback
Last updated on