Scoping comments to views and routes
Show only the comments relevant to the current view in a single-page app
By default, Pindrop shows all comments on the page at once. In a single-page app where multiple views share the same URL, comments left on one view would show up on another. Scopes fix this.
A scope is a piece of metadata attached to a comment when it's created. When the scope is no longer active, the comment is hidden. When it becomes active again, the comment reappears.
How it works
Two options work together:
getScope(element)— called when a user clicks to drop a pin. Return an identifier for the current view so the comment knows where it belongs.isScopeActive(scope)— called for every comment on every render. Returntrueif the scope is currently visible,falseto hide the comment.
If a comment has no scope (created before you added scopes, or on a page without getScope), it is always shown.
Next.js App Router
Use the current pathname as the scope:
'use client'
import { usePathname } from 'next/navigation'
import { useEffect, useRef } from 'react'
import { Pindrop } from 'pindrop.js'
export function PindropProvider() {
const pathname = usePathname()
const pindrop = useRef<ReturnType<typeof Pindrop.init> | null>(null)
useEffect(() => {
pindrop.current = Pindrop.init({
storageKey: 'my-app',
getScope: () => ({ route: pathname }),
isScopeActive: (scope) => scope.route === pathname,
})
return () => pindrop.current?.destroy()
}, [])
// Tell Pindrop to re-evaluate scopes on route change
useEffect(() => {
pindrop.current?.refresh()
}, [pathname])
return null
}React Router
import { useLocation } from 'react-router-dom'
import { useEffect, useRef } from 'react'
import { Pindrop } from 'pindrop.js'
export function PindropProvider() {
const location = useLocation()
const pindrop = useRef<ReturnType<typeof Pindrop.init> | null>(null)
useEffect(() => {
pindrop.current = Pindrop.init({
storageKey: 'my-app',
getScope: () => ({ route: location.pathname }),
isScopeActive: (scope) => scope.route === location.pathname,
})
return () => pindrop.current?.destroy()
}, [])
useEffect(() => {
pindrop.current?.refresh()
}, [location.pathname])
return null
}Tabs and modals
Scopes aren't limited to routes — use them for any conditional UI state like tabs or modals:
Pindrop.init({
storageKey: 'my-app',
getScope: (element) => {
const tab = element.closest('[data-tab]')?.getAttribute('data-tab')
return tab ? { tab } : null
},
isScopeActive: (scope) => {
return document.querySelector(`[data-tab="${scope.tab}"]`) !== null
},
})Comments dropped inside a tab panel are hidden when that tab is inactive and reappear when it's opened again.
Calling refresh()
Pindrop re-evaluates scope visibility automatically when comments are added or the toolbar is interacted with. For route changes or programmatic UI updates, call refresh() manually to trigger a re-render:
pindrop.refresh()This is a fast in-memory pass — safe to call on every route change.
Last updated March 29, 2026