Skip to content

Commit

Permalink
WIP react router 6
Browse files Browse the repository at this point in the history
  • Loading branch information
evanpurkhiser committed May 23, 2024
1 parent 9738396 commit 1cf3c29
Show file tree
Hide file tree
Showing 16 changed files with 638 additions and 38 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
"react-mentions": "4.4.10",
"react-popper": "^2.3.0",
"react-router": "3.2.6",
"react-router-dom": "^6.23.0",
"react-select": "4.3.1",
"react-sparklines": "1.7.0",
"react-virtualized": "^9.22.5",
Expand Down
13 changes: 13 additions & 0 deletions static/app/components/links/link.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import {forwardRef} from 'react';
import {Link as RouterLink} from 'react-router';
import {Link as Router6Link} from 'react-router-dom';
import styled from '@emotion/styled';
import type {LocationDescriptor} from 'history';

import {USING_REACT_ROUTER_SIX} from 'sentry/constants';
import {locationDescriptorToTo} from 'sentry/utils/reactRouter6Compat';
import {useLocation} from 'sentry/utils/useLocation';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';

Expand Down Expand Up @@ -46,6 +49,16 @@ function BaseLink({disabled, to, forwardedRef, ...props}: LinkProps): React.Reac
to = normalizeUrl(to, location);

if (!disabled && location) {
if (USING_REACT_ROUTER_SIX) {
return (
<Router6Link
to={locationDescriptorToTo(to)}
ref={forwardedRef as any}
{...props}
/>
);
}

return <RouterLink to={to} ref={forwardedRef as any} {...props} />;
}

Expand Down
39 changes: 30 additions & 9 deletions static/app/components/links/listLink.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
import {Link as RouterLink} from 'react-router';
import {NavLink} from 'react-router-dom';
import styled from '@emotion/styled';
import classNames from 'classnames';
import type {LocationDescriptor} from 'history';

import {USING_REACT_ROUTER_SIX} from 'sentry/constants';
import {locationDescriptorToTo} from 'sentry/utils/reactRouter6Compat';
import {useLocation} from 'sentry/utils/useLocation';
import useRouter from 'sentry/utils/useRouter';
import {normalizeUrl} from 'sentry/utils/withDomainRequired';

type LinkProps = Omit<React.ComponentProps<typeof RouterLink>, 'to'>;

type Props = LinkProps & {
interface ListLinkProps
extends Omit<
React.DetailedHTMLProps<React.HTMLAttributes<HTMLAnchorElement>, HTMLAnchorElement>,
'href' | 'target' | 'as' | 'css' | 'ref'
> {
/**
* Link target. We don't want to expose the ToLocationFunction on this component.
*/
Expand All @@ -19,7 +25,7 @@ type Props = LinkProps & {
* Should be should be supplied by the parent component
*/
isActive?: (location: LocationDescriptor, indexOnly?: boolean) => boolean;
};
}

function ListLink({
children,
Expand All @@ -29,18 +35,33 @@ function ListLink({
index = false,
disabled = false,
...props
}: Props) {
}: ListLinkProps) {
const router = useRouter();
const location = useLocation();
const targetLocation = typeof to === 'string' ? {pathname: to} : to;
const target = normalizeUrl(targetLocation);

const active = isActive?.(target, index) ?? router.isActive(target, index);
const active =
isActive?.(target, index) ??
// XXX(epurkhiser): our shim for router.isActive will throw an error in
// react-router 6. Fallback to manually checking if the path is active
(USING_REACT_ROUTER_SIX
? location.pathname === (typeof target === 'string' ? target : target.pathname)
: router.isActive(target, index));

const link = USING_REACT_ROUTER_SIX ? (
<NavLink {...props} to={disabled ? '' : locationDescriptorToTo(target)}>
{children}
</NavLink>
) : (
<RouterLink {...props} onlyActiveOnIndex={index} to={disabled ? '' : target}>
{children}
</RouterLink>
);

return (
<StyledLi className={classNames({active}, className)} disabled={disabled}>
<RouterLink {...props} onlyActiveOnIndex={index} to={disabled ? '' : target}>
{children}
</RouterLink>
{link}
</StyledLi>
);
}
Expand Down
2 changes: 1 addition & 1 deletion static/app/components/sidebar/sidebarItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ function SidebarItem({

const toProps: LocationDescriptor = useMemo(() => {
return {
pathname: to ? to : href ?? '#',
pathname: to ? to : href,
search,
state: {source: SIDEBAR_NAVIGATION_SOURCE},
};
Expand Down
2 changes: 2 additions & 0 deletions static/app/constants/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -410,3 +410,5 @@ export const USE_REACT_QUERY_DEVTOOL = process.env.USE_REACT_QUERY_DEVTOOL;
export const DEFAULT_ERROR_JSON = {
detail: t('Unknown error. Please try again.'),
};

export const USING_REACT_ROUTER_SIX = true;
17 changes: 12 additions & 5 deletions static/app/main.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {Router, RouterContext} from 'react-router';
import {createBrowserRouter, RouterProvider} from 'react-router-dom';
import {ReactQueryDevtools} from '@tanstack/react-query-devtools';

import DemoHeader from 'sentry/components/demo/demoHeader';
import {OnboardingContextProvider} from 'sentry/components/onboarding/onboardingContext';
import {ThemeAndStyleProvider} from 'sentry/components/themeAndStyleProvider';
import {USE_REACT_QUERY_DEVTOOL} from 'sentry/constants';
import {routes} from 'sentry/routes';
import {USE_REACT_QUERY_DEVTOOL, USING_REACT_ROUTER_SIX} from 'sentry/constants';
import {routes, routes6} from 'sentry/routes';
import ConfigStore from 'sentry/stores/configStore';
import {browserHistory} from 'sentry/utils/browserHistory';
import {
Expand All @@ -32,15 +33,21 @@ function renderRouter(props: any) {

const queryClient = new QueryClient(DEFAULT_QUERY_CLIENT_CONFIG);

const router = createBrowserRouter(routes6);

function Main() {
return (
<ThemeAndStyleProvider>
<QueryClientProvider client={queryClient}>
<OnboardingContextProvider>
{ConfigStore.get('demoMode') && <DemoHeader />}
<Router history={browserHistory} render={renderRouter}>
{routes()}
</Router>
{USING_REACT_ROUTER_SIX ? (
<RouterProvider router={router} />
) : (
<Router history={browserHistory} render={renderRouter}>
{routes()}
</Router>
)}
</OnboardingContextProvider>
{USE_REACT_QUERY_DEVTOOL && (
<ReactQueryDevtools initialIsOpen={false} position="bottom-left" />
Expand Down
29 changes: 18 additions & 11 deletions static/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import {IndexRedirect, Redirect} from 'react-router';
import memoize from 'lodash/memoize';

import LazyLoad from 'sentry/components/lazyLoad';
import {EXPERIMENTAL_SPA, USING_CUSTOMER_DOMAIN} from 'sentry/constants';
import {
EXPERIMENTAL_SPA,
USING_CUSTOMER_DOMAIN,
USING_REACT_ROUTER_SIX,
} from 'sentry/constants';
import {t} from 'sentry/locale';
import HookStore from 'sentry/stores/hookStore';
import type {HookName} from 'sentry/types/hooks';
Expand All @@ -27,6 +31,7 @@ import SettingsWrapper from 'sentry/views/settings/components/settingsWrapper';
import {ModuleName} from 'sentry/views/starfish/types';

import {IndexRoute, Route} from './components/route';
import {buildReactRouter6Routes, ProvideGlobalHistory} from './utils/reactRouter6Compat';

const hook = (name: HookName) => HookStore.get(name).map(cb => cb());

Expand Down Expand Up @@ -689,15 +694,13 @@ function buildRoutes() {
)}
/>
)}
{USING_CUSTOMER_DOMAIN && (
<Route
path="/settings/organization/"
name={t('General')}
component={make(
() => import('sentry/views/settings/organizationGeneralSettings')
)}
/>
)}
<Route
path="organization/"
name={t('General')}
component={make(
() => import('sentry/views/settings/organizationGeneralSettings')
)}
/>
<Route
path="projects/"
name={t('Projects')}
Expand Down Expand Up @@ -2226,7 +2229,7 @@ function buildRoutes() {
);

const appRoutes = (
<Route>
<Route component={USING_REACT_ROUTER_SIX ? ProvideGlobalHistory : undefined}>
{experimentalSpaRoutes}
<Route path="/" component={errorHandler(App)}>
{rootRoutes}
Expand All @@ -2244,6 +2247,10 @@ function buildRoutes() {
// when the app renders Main. Memoize to avoid rebuilding the route tree.
export const routes = memoize(buildRoutes);

// XXX(epurkhiser): Transforms the legacy react-router 3 routest tree into a
// react-router 6 style routes tree.
export const routes6 = buildReactRouter6Routes(buildRoutes());

// Exported for use in tests.
export {buildRoutes};

Expand Down
73 changes: 72 additions & 1 deletion static/app/utils/browserHistory.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import {browserHistory as react3BrowserHistory} from 'react-router';
import type {NavigateFunction} from 'react-router-dom';
import type {History} from 'history';

import {locationDescriptorToTo} from './reactRouter6Compat';

/**
* @deprecated Prefer using useNavigate
Expand All @@ -14,4 +18,71 @@ import {browserHistory as react3BrowserHistory} from 'react-router';
* browserHistory.push({...location, query: {someKey: 1}})
* navigate({...location, query: {someKey: 1}})
*/
export const browserHistory = react3BrowserHistory;
export let browserHistory = react3BrowserHistory;

/**
* This shim sets the global `browserHistory` to a shim object that matches
* react-router 3's browserHistory implementation
*/
export function DANGEROUS_SET_REACT_ROUTER_6_HISTORY(navigate: NavigateFunction) {
// XXX(epurkhiser): The history object for react-router 6 is slightly
// different in typing from the react-router 3 history. We need to shim some
// of the functions to keep things working
const compat6BrowserHistory: History = {
push: to => navigate(locationDescriptorToTo(to)),
replace: to => navigate(locationDescriptorToTo(to), {replace: true}),
go: n => navigate(n),
goBack: () => navigate(-1),
goForward: () => navigate(1),

// XXX(epurkhiser): react-router 6's BrowserHistory does not let you create
// multiple lsiteners. This implementation is similar but allows multiple
// listeners.
listen: _listener => {
// eslint-disable-next-line no-console
console.error('browserHistory.listen not implemented on react-router 6 shim');
return () => {};
},

listenBefore: _hook => {
// eslint-disable-next-line no-console
console.error('browserHistory.listenBefore not implemented on react-router 6 shim');
return () => {};
},
transitionTo: _location => {
// eslint-disable-next-line no-console
console.error('browserHistory.transitionTo not implemented on react-router 6 shim');
},
createKey: () => {
// eslint-disable-next-line no-console
console.error('browserHistory.createKey not implemented on react-router 6 shim');
return '';
},
createPath: () => {
// eslint-disable-next-line no-console
console.error('browserHistory.createPath not implemented on react-router 6 shim');
return '';
},
createHref: () => {
// eslint-disable-next-line no-console
console.error('browserHistory.createHref not implemented on react-router 6 shim');
return '';
},
createLocation: () => {
// eslint-disable-next-line no-console
console.error(
'browserHistory.createLocation not implemented on react-router 6 shim'
);
return undefined as any;
},
getCurrentLocation: () => {
// eslint-disable-next-line no-console
console.error(
'browserHistory.getCurrentLocation not implemented on react-router 6 shim'
);
return undefined as any;
},
};

browserHistory = compat6BrowserHistory;
}
Loading

0 comments on commit 1cf3c29

Please sign in to comment.