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 10, 2024
1 parent 2da6297 commit 927c27e
Show file tree
Hide file tree
Showing 17 changed files with 652 additions and 48 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 @@ -170,7 +170,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 @@ -382,3 +382,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;
35 changes: 29 additions & 6 deletions static/app/main.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,19 @@
import {Router, RouterContext} from 'react-router';
import {UNSAFE_mapRouteProperties as mapRouteProperties} from 'react-router/lib/index';
import {RouterProvider} from 'react-router-dom';
import {createBrowserHistory, createRouter} from '@remix-run/router';
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 {
browserHistory,
DANGEROUS_SET_REACT_ROUTER_6_HISTORY,
} from 'sentry/utils/browserHistory';
import {
DEFAULT_QUERY_CLIENT_CONFIG,
QueryClient,
Expand All @@ -32,15 +38,32 @@ function renderRouter(props: any) {

const queryClient = new QueryClient(DEFAULT_QUERY_CLIENT_CONFIG);

const reactRouterSixBrowserHistory = createBrowserHistory();

if (USING_REACT_ROUTER_SIX) {
DANGEROUS_SET_REACT_ROUTER_6_HISTORY(reactRouterSixBrowserHistory);
}

const router = createRouter({
future: {v7_prependBasename: true},
history: reactRouterSixBrowserHistory,
routes: routes6,
mapRouteProperties,
}).initialize();

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
21 changes: 12 additions & 9 deletions static/app/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import RouteNotFound from 'sentry/views/routeNotFound';
import SettingsWrapper from 'sentry/views/settings/components/settingsWrapper';

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

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

Expand Down Expand Up @@ -697,15 +698,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 @@ -2254,6 +2253,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
77 changes: 76 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 {BrowserHistory} from '@remix-run/router/dist/history';
import type {History} from 'history';

import {location6ToLocation3, locationDescriptorToTo} from './reactRouter6Compat';

/**
* @deprecated Prefer using useNavigate
Expand All @@ -9,4 +13,75 @@ import {browserHistory as react3BrowserHistory} from 'react-router';
* browserHistory.push('/next') -> navigate('/next')
* browserHistory.replace('/next') -> navigate('/next', {replace: true})
*/
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(history: BrowserHistory) {
// 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 => history.push(locationDescriptorToTo(to)),
replace: to => history.replace(locationDescriptorToTo(to)),
go: history.go,
goBack: () => history.go(-1),
goForward: () => history.go(-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 => {
const shimListener = () => {
// XXX(epurkhiser): History object must be translated
listener(location6ToLocation3(history.location));
};

window.addEventListener('popstate', shimListener);
return () => window.removeEventListener('popstate', shimListener);
},

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 927c27e

Please sign in to comment.