Skip to content

Commit 4bbb823

Browse files
Refactor PlatformSelector and add browser guide for JS
Co-authored-by: shannon.anahata <[email protected]>
1 parent e241867 commit 4bbb823

File tree

3 files changed

+68
-53
lines changed

3 files changed

+68
-53
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
---
2+
title: Browser JavaScript
3+
description: Learn how to set up Sentry's Browser JavaScript SDK to automatically report errors and exceptions in your application.
4+
sidebar_order: 1
5+
---
6+
7+
<Alert level="info">
8+
If you don't already have an account and Sentry project established, head over to [sentry.io](https://sentry.io/signup/), then return to this page.
9+
</Alert>
10+
11+
Sentry captures data by using an SDK within your application's runtime.
12+
13+
<PlatformContent includePath="getting-started-install" />
14+
15+
## Configure
16+
17+
Configuration should happen as early as possible in your application's lifecycle.
18+
19+
<PlatformContent includePath="getting-started-config" />
20+
21+
## Verify
22+
23+
This snippet includes an intentional error, so you can test that everything is working as soon as you set it up.
24+
25+
<PlatformContent includePath="getting-started-verify" />
26+
27+
## Next Steps
28+
29+
For complete details on DSN configuration, additional options, and type information, see our TypeScript docs.
30+
31+
Alert us to which features you'd like us to expand upon:
32+
33+
<GuideGrid platform="javascript" />

src/components/platformSelector/index.tsx

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
'use client';
2-
import {Fragment, Ref, useCallback, useEffect, useMemo, useRef, useState} from 'react';
2+
import {Fragment, Ref, useEffect, useMemo, useRef, useState} from 'react';
33
import {Combobox, ComboboxItem, ComboboxList, ComboboxProvider} from '@ariakit/react';
44
import {CaretRightIcon, CaretSortIcon, MagnifyingGlassIcon} from '@radix-ui/react-icons';
55
import * as RadixSelect from '@radix-ui/react-select';
@@ -63,47 +63,42 @@ export function PlatformSelector({
6363
const currentPlatformKey = currentPlatform?.key;
6464
const [open, setOpen] = useState(false);
6565
const [searchValue, setSearchValue] = useState('');
66-
const debounceRef = useRef<NodeJS.Timeout>();
67-
68-
// Debounced search handler to prevent rapid re-renders
69-
const debouncedSetSearchValue = useCallback((value: string) => {
70-
if (debounceRef.current) {
71-
clearTimeout(debounceRef.current);
72-
}
73-
debounceRef.current = setTimeout(() => setSearchValue(value), 100);
74-
}, []);
7566

7667
const matches = useMemo(() => {
7768
if (!searchValue) {
7869
return platformsAndGuides;
7970
}
80-
81-
// Find currently selected platform first to ensure it's never filtered out
82-
const selectedPlatform = platformsAndGuides.find(
83-
lang => lang.key === currentPlatformKey
84-
);
85-
86-
// Filter out the selected platform from search, then add it back at the end
87-
const otherPlatforms = platformsAndGuides.filter(
88-
lang => lang.key !== currentPlatformKey
89-
);
90-
9171
// any of these fields can be used to match the search value
9272
const keys = ['title', 'name', 'aliases', 'sdk', 'keywords'];
93-
const matches_ = matchSorter(otherPlatforms, searchValue, {
73+
const matches_ = matchSorter(platformsAndGuides, searchValue, {
9474
keys,
9575
threshold: matchSorter.rankings.ACRONYM,
9676
});
97-
98-
// Always include the selected platform at the beginning
99-
return selectedPlatform ? [selectedPlatform, ...matches_] : matches_;
77+
// Radix Select does not work if we don't render the selected item, so we
78+
// make sure to include it in the list of matches.
79+
const selectedPlatform = platformsAndGuides.find(
80+
lang => lang.key === currentPlatformKey
81+
);
82+
if (selectedPlatform && !matches_.includes(selectedPlatform)) {
83+
matches_.push(selectedPlatform);
84+
}
85+
return matches_;
10086
}, [searchValue, currentPlatformKey, platformsAndGuides]);
10187

10288
const router = useRouter();
10389
const onPlatformChange = (platformKey: string) => {
10490
const cleanKey = platformKey.replace('-redirect', '');
105-
const targetPlatform = platformsAndGuides.find(platform => platform.key === cleanKey);
106-
91+
let targetPlatform = platformsAndGuides.find(platform => platform.key === cleanKey);
92+
93+
// Special handling for JavaScript: when platform "javascript" is selected,
94+
// redirect to the real browser guide "javascript.browser" instead
95+
if (cleanKey === 'javascript' && targetPlatform?.type === 'platform') {
96+
const browserGuide = platformsAndGuides.find(p => p.key === 'javascript.browser');
97+
if (browserGuide) {
98+
targetPlatform = browserGuide;
99+
}
100+
}
101+
107102
if (targetPlatform) {
108103
localStorage.setItem('active-platform', targetPlatform.key);
109104
router.push(targetPlatform.url);
@@ -120,9 +115,17 @@ export function PlatformSelector({
120115
}, [open]);
121116

122117
const [storedPlatformKey, setStoredPlatformKey] = useState<string | null>(null);
123-
const storedPlatform = platformsAndGuides.find(
118+
let storedPlatform = platformsAndGuides.find(
124119
platform => platform.key === storedPlatformKey
125120
);
121+
122+
// Handle stored JavaScript platform: redirect to browser guide
123+
if (storedPlatformKey === 'javascript' && storedPlatform?.type === 'platform') {
124+
const browserGuide = platformsAndGuides.find(p => p.key === 'javascript.browser');
125+
if (browserGuide) {
126+
storedPlatform = browserGuide;
127+
}
128+
}
126129

127130
useEffect(() => {
128131
if (currentPlatformKey) {
@@ -132,15 +135,6 @@ export function PlatformSelector({
132135
}
133136
}, [currentPlatformKey]);
134137

135-
// Cleanup debounce timer on unmount
136-
useEffect(() => {
137-
return () => {
138-
if (debounceRef.current) {
139-
clearTimeout(debounceRef.current);
140-
}
141-
};
142-
}, []);
143-
144138
const path = usePathname();
145139
const isPlatformPage = Boolean(
146140
path?.startsWith('/platforms/') &&
@@ -158,7 +152,7 @@ export function PlatformSelector({
158152
<div>
159153
<RadixSelect.Root
160154
defaultValue={currentPlatformKey}
161-
value={showStoredPlatform ? storedPlatformKey : undefined}
155+
value={showStoredPlatform ? storedPlatform?.key : undefined}
162156
onValueChange={onPlatformChange}
163157
open={open}
164158
onOpenChange={setOpen}
@@ -167,7 +161,7 @@ export function PlatformSelector({
167161
open={open}
168162
setOpen={setOpen}
169163
includesBaseElement={false}
170-
setValue={debouncedSetSearchValue}
164+
setValue={setSearchValue}
171165
>
172166
<RadixSelect.Trigger aria-label="Platform" className={styles.select}>
173167
<RadixSelect.Value placeholder="Choose your SDK" />

src/docTree.ts

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -372,24 +372,12 @@ function extractGuides(platformNode: DocNode): PlatformGuide[] {
372372
return [];
373373
}
374374

375-
// If a `platformTitle` is defined, we add a virtual guide
376-
const defaultGuide = platformNode.frontmatter.platformTitle
377-
? {
378-
...nodeToGuide(platformNode.slug, platformNode),
379-
key: `${platformNode.slug}.browser`,
380-
name: 'browser',
381-
// Enhance searchable properties for virtual guides
382-
title: platformNode.frontmatter.title || platformNode.frontmatter.platformTitle,
383-
aliases: [...(platformNode.frontmatter.aliases || []), platformNode.slug],
384-
keywords: [...(platformNode.frontmatter.keywords || []), 'browser', 'client'],
385-
}
386-
: undefined;
387-
375+
// No virtual guide needed - we now have a real browser guide
388376
const childGuides = guidesNode.children
389377
.filter(({path}) => !isVersioned(path))
390378
.map(n => nodeToGuide(platformNode.slug, n));
391379

392-
return defaultGuide ? [defaultGuide, ...childGuides] : childGuides;
380+
return childGuides;
393381
}
394382

395383
const extractIntegrations = (p: DocNode): PlatformIntegration[] => {

0 commit comments

Comments
 (0)