diff --git a/.github/workflows/no-response.yml b/.github/workflows/no-response.yml new file mode 100644 index 0000000000..d40c0eba03 --- /dev/null +++ b/.github/workflows/no-response.yml @@ -0,0 +1,26 @@ +name: 🥺 No Response + +on: + issue_comment: + types: [created] + schedule: + # Schedule for five minutes after the hour, every hour + - cron: "5 * * * *" + +jobs: + noResponse: + if: github.repository == 'remix-run/react-router' + runs-on: ubuntu-latest + + steps: + - name: 🥺 Handle Ghosting + uses: lee-dohm/no-response@v0.5.0 + with: + closeComment: > + This issue has been automatically closed because we haven't received a + response from the original author 🙈. This automation helps keep the issue + tracker clean from issues that are unactionable. Please reach out if you + have more information for us! 🙂 + daysUntilClose: 10 + responseRequiredLabel: needs-response + token: ${{ github.token }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e8b807e74c..a6e637d603 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1 +1 @@ -Please see [our guide to contributing](docs/contributing.md). +Please see [our guide to contributing](docs/guides/contributing.md). diff --git a/contributors.yml b/contributors.yml index 6f4e395143..e74157d22e 100644 --- a/contributors.yml +++ b/contributors.yml @@ -1,5 +1,6 @@ - abhi-kr-2100 - Ajayff4 +- alexlbr - avipatel97 - awreese - bhbs @@ -36,10 +37,13 @@ - liuhanqu - lqze - lukerSpringTree +- marc2332 - markivancho - mcansh - mfijas +- michal-antczak - morleytatro +- ms10596 - noisypigeon - paulsmithkc - petersendidit @@ -51,6 +55,7 @@ - shamsup - shihanng - shivamsinghchahar +- tanayv - thisiskartik - ThornWu - timdorr @@ -58,3 +63,5 @@ - turansky - underager - vijaypushkin +- vikingviolinist +- xcsnowcity diff --git a/docs/components/route.md b/docs/components/route.md index f083dbe900..0228fea232 100644 --- a/docs/components/route.md +++ b/docs/components/route.md @@ -64,6 +64,6 @@ For example, in the following config the parent route renders an `` by d ``` -[location]: ../hooks/location +[location]: ../utils/location [outlet]: ./outlet [use-route]: ../hooks/use-routes diff --git a/docs/components/routes.md b/docs/components/routes.md index 7f7df768c0..70cae2d61b 100644 --- a/docs/components/routes.md +++ b/docs/components/routes.md @@ -64,6 +64,6 @@ For example, in the following config the parent route renders an `` by d ``` -[location]: ../hook/location +[location]: ../utils/location [outlet]: ./outlet -[use-route]: ../hooks/use-routes +[use-routes]: ../hooks/use-routes diff --git a/docs/getting-started/concepts.md b/docs/getting-started/concepts.md index 770e12ca6d..450b537753 100644 --- a/docs/getting-started/concepts.md +++ b/docs/getting-started/concepts.md @@ -171,7 +171,7 @@ The last two, `{ state, key }`, are React Router specific. **Location Pathname** -This is the part of [URL](#url) after the origin, so for `https://example.com/teams/hotspurs` the pathname is `/teams/hostspurs`. This is the only part of the location that routes match against. +This is the part of [URL](#url) after the origin, so for `https://example.com/teams/hotspurs` the pathname is `/teams/hotspurs`. This is the only part of the location that routes match against. **Location Search** diff --git a/docs/getting-started/overview.md b/docs/getting-started/overview.md index 1fd9cad255..1a6e6363ca 100644 --- a/docs/getting-started/overview.md +++ b/docs/getting-started/overview.md @@ -172,7 +172,7 @@ When the URL is `"/invoices/sent"` the component tree will be: ``` -When the URL is `"/invoices/123"`, the component tree will: +When the URL is `"/invoices/123"`, the component tree will be: ```tsx diff --git a/docs/upgrading/v5.md b/docs/upgrading/v5.md index 42dff00bbe..b139fddc6a 100644 --- a/docs/upgrading/v5.md +++ b/docs/upgrading/v5.md @@ -891,6 +891,44 @@ To see the exact API of the new `useMatch` hook and its type declaration, check +## Change the order of arguments passed to `matchPath`. Change pathPattern options. + +Since version 6 the order of arguments passed to `matchPath` function has changed. Also pattern options has changed. + +- first argument is pathPattern object, then comes pathname +- pathPattern doesn't include `exact` and `strict` options any more. New `caseSensitive` and `end` options has been added. + +Please refactor it as follows: + +Before: + +```js +// This is a React Router v5 app +import { matchPath } from "react-router-dom"; + +const match = matchPath("/users/123", { + path: "/users/:id", + exact: true, // Optional, defaults to false + strict: false, // Optional, defaults to false +}); +``` + +After: + +```js +// This is a React Router v6 app +import { matchPath } from "react-router-dom"; + +const match = matchPath( + { + path: "/users/:id", + caseSensitive: false, // Optional. Should be `true` if the static portions of the `path` should be matched in the same case. + end: true, // Optional. Should be `true` if this pattern should match the entire URL pathname + }, + "/users/123" +); +``` + ## `` is not currently supported `` from v5 (along with `usePrompt` and `useBlocker` from the v6 betas) are not included in the current released version of v6. We decided we'd rather ship with what we have than take even more time to nail down a feature that isn't fully baked. We will absolutely be working on adding this back in to v6 at some point in the near future, but not for our first stable release of 6.x. diff --git a/packages/react-router-dom-v5-compat/index.ts b/packages/react-router-dom-v5-compat/index.ts index 673db4f153..c2b848af55 100644 --- a/packages/react-router-dom-v5-compat/index.ts +++ b/packages/react-router-dom-v5-compat/index.ts @@ -69,7 +69,7 @@ export type { Pathname, Search, RoutesProps, -} from "./react-router-dom"; +} from "../react-router-dom"; export { BrowserRouter, HashRouter, @@ -108,7 +108,7 @@ export { useResolvedPath, useRoutes, useSearchParams, -} from "./react-router-dom"; +} from "../react-router-dom"; export type { StaticRouterProps } from "./lib/components"; export { CompatRouter, CompatRoute, StaticRouter } from "./lib/components"; diff --git a/packages/react-router-dom-v5-compat/lib/components.tsx b/packages/react-router-dom-v5-compat/lib/components.tsx index fd5eefb65d..f0e0a7c204 100644 --- a/packages/react-router-dom-v5-compat/lib/components.tsx +++ b/packages/react-router-dom-v5-compat/lib/components.tsx @@ -9,7 +9,7 @@ import { useHistory, Route as RouteV5 } from "react-router-dom"; // We are a wrapper around react-router-dom v6, so bring it in // and bundle it because an app can't have two versions of // react-router-dom in its package.json. -import { Router, Routes, Route } from "../react-router-dom"; +import { Router, Routes, Route } from "../../react-router-dom"; // v5 isn't in TypeScript, they'll also lose the @types/react-router with this // but not worried about that for now. diff --git a/packages/react-router-dom/index.tsx b/packages/react-router-dom/index.tsx index 83ecd0e83b..a8df0aa649 100644 --- a/packages/react-router-dom/index.tsx +++ b/packages/react-router-dom/index.tsx @@ -142,6 +142,8 @@ export interface BrowserRouterProps { /** * A `` for use in web browsers. Provides the cleanest URLs. + * + * @see https://reactrouter.com/docs/en/v6/routers/browser-router */ export function BrowserRouter({ basename, @@ -181,6 +183,8 @@ export interface HashRouterProps { /** * A `` for use in web browsers. Stores the location in the hash * portion of the URL so it is not sent to the server. + * + * @see https://reactrouter.com/docs/en/v6/routers/hash-router */ export function HashRouter({ basename, children, window }: HashRouterProps) { let historyRef = React.useRef(); @@ -218,6 +222,8 @@ export interface HistoryRouterProps { * to note that using your own history object is highly discouraged and may add * two versions of the history library to your bundles unless you use the same * version of the history library that React Router uses internally. + * + * @see https://reactrouter.com/docs/en/v6/routers/history-router */ function HistoryRouter({ basename, children, history }: HistoryRouterProps) { const [state, setState] = React.useState({ @@ -258,6 +264,8 @@ export interface LinkProps /** * The public API for rendering a history-aware . + * + * @see https://reactrouter.com/docs/en/v6/components/link */ export const Link = React.forwardRef( function LinkWithRef( @@ -307,6 +315,8 @@ export interface NavLinkProps /** * A wrapper that knows if it's "active" or not. + * + * @see https://reactrouter.com/docs/en/v6/components/nav-link */ export const NavLink = React.forwardRef( function NavLinkWithRef( @@ -384,6 +394,8 @@ if (__DEV__) { * Handles the click behavior for router `` components. This is useful if * you need to create custom `` components with the same click behavior we * use in our exported ``. + * + * @see https://reactrouter.com/docs/en/v6/hooks/use-link-click-handler */ export function useLinkClickHandler( to: To, @@ -425,6 +437,8 @@ export function useLinkClickHandler( /** * A convenient wrapper for reading and writing search parameters via the * URLSearchParams interface. + * + * @see https://reactrouter.com/docs/en/v6/hooks/use-search-params */ export function useSearchParams(defaultInit?: URLSearchParamsInit) { warning( @@ -498,6 +512,8 @@ export type URLSearchParamsInit = * let searchParams = createSearchParams({ * sort: ['name', 'price'] * }); + * + * @see https://reactrouter.com/docs/en/v6/utils/create-search-params */ export function createSearchParams( init: URLSearchParamsInit = "" diff --git a/packages/react-router/__tests__/useNavigate-test.tsx b/packages/react-router/__tests__/useNavigate-test.tsx index 8502fea9b8..ffce47a70b 100644 --- a/packages/react-router/__tests__/useNavigate-test.tsx +++ b/packages/react-router/__tests__/useNavigate-test.tsx @@ -50,6 +50,77 @@ describe("useNavigate", () => { `); }); + it("transitions to the new location when called immediately", () => { + const Home = React.forwardRef(function Home(_props, ref) { + let navigate = useNavigate(); + + React.useImperativeHandle(ref, () => ({ + navigate: () => navigate("/about") + })) + + return null + }) + + let homeRef; + + let renderer: TestRenderer.ReactTestRenderer; + renderer = TestRenderer.create( + + + homeRef = ref} />} /> + About} /> + + + ); + + TestRenderer.act(() => { + homeRef.navigate(); + }) + + expect(renderer.toJSON()).toMatchInlineSnapshot(` +

+ About +

+ `); + }); + + it("allows navigation in child useEffects", () => { + function Child({ onChildRendered }) { + + React.useEffect(() => { + onChildRendered(); + }); + + return null; + } + + function Parent() { + let navigate = useNavigate(); + + let onChildRendered = React.useCallback(() => navigate("/about"), []); + + return ; + } + + let renderer: TestRenderer.ReactTestRenderer; + TestRenderer.act(() => { + renderer = TestRenderer.create( + + + } /> + About} /> + + + ); + }); + + expect(renderer.toJSON()).toMatchInlineSnapshot(` +

+ About +

+ `); + }); + describe("with state", () => { it("adds the state to location.state", () => { function Home() { @@ -97,4 +168,6 @@ describe("useNavigate", () => { `); }); }); + + }); diff --git a/packages/react-router/lib/components.tsx b/packages/react-router/lib/components.tsx index c5646ff58b..a569f0febe 100644 --- a/packages/react-router/lib/components.tsx +++ b/packages/react-router/lib/components.tsx @@ -27,7 +27,7 @@ export interface MemoryRouterProps { /** * A that stores all entries in memory. * - * @see https://reactrouter.com/docs/en/v6/api#memoryrouter + * @see https://reactrouter.com/docs/en/v6/routers/memory-router */ export function MemoryRouter({ basename, @@ -72,7 +72,7 @@ export interface NavigateProps { * able to use hooks. In functional components, we recommend you use the * `useNavigate` hook instead. * - * @see https://reactrouter.com/docs/en/v6/api#navigate + * @see https://reactrouter.com/docs/en/v6/components/navigate */ export function Navigate({ to, replace, state }: NavigateProps): null { invariant( @@ -104,7 +104,7 @@ export interface OutletProps { /** * Renders the child route's element, if there is one. * - * @see https://reactrouter.com/docs/en/v6/api#outlet + * @see https://reactrouter.com/docs/en/v6/components/outlet */ export function Outlet(props: OutletProps): React.ReactElement | null { return useOutlet(props.context); @@ -139,7 +139,7 @@ export interface IndexRouteProps { /** * Declares an element that should be rendered at a certain URL path. * - * @see https://reactrouter.com/docs/en/v6/api#route + * @see https://reactrouter.com/docs/en/v6/components/route */ export function Route( _props: PathRouteProps | LayoutRouteProps | IndexRouteProps @@ -167,7 +167,7 @@ export interface RouterProps { * router that is more specific to your environment such as a * in web browsers or a for server rendering. * - * @see https://reactrouter.com/docs/en/v6/api#router + * @see https://reactrouter.com/docs/en/v6/routers/router */ export function Router({ basename: basenameProp = "/", @@ -247,7 +247,7 @@ export interface RoutesProps { * A container for a nested tree of elements that renders the branch * that best matches the current location. * - * @see https://reactrouter.com/docs/en/v6/api#routes + * @see https://reactrouter.com/docs/en/v6/components/routes */ export function Routes({ children, @@ -265,7 +265,7 @@ export function Routes({ * either a `` element or an array of them. Used internally by * `` to create a route config from its children. * - * @see https://reactrouter.com/docs/en/v6/api#createroutesfromchildren + * @see https://reactrouter.com/docs/en/v6/utils/create-routes-from-children */ export function createRoutesFromChildren( children: React.ReactNode diff --git a/packages/react-router/lib/hooks.tsx b/packages/react-router/lib/hooks.tsx index e81c674760..5a85f16dff 100644 --- a/packages/react-router/lib/hooks.tsx +++ b/packages/react-router/lib/hooks.tsx @@ -26,7 +26,7 @@ import { * Returns the full href for the given "to" value. This is useful for building * custom links that are also accessible and preserve right-click behavior. * - * @see https://reactrouter.com/docs/en/v6/api#usehref + * @see https://reactrouter.com/docs/en/v6/hooks/use-href */ export function useHref(to: To): string { invariant( @@ -55,7 +55,7 @@ export function useHref(to: To): string { /** * Returns true if this component is a descendant of a . * - * @see https://reactrouter.com/docs/en/v6/api#useinroutercontext + * @see https://reactrouter.com/docs/en/v6/hooks/use-in-router-context */ export function useInRouterContext(): boolean { return React.useContext(LocationContext) != null; @@ -69,7 +69,7 @@ export function useInRouterContext(): boolean { * "routing" in your app, and we'd like to know what your use case is. We may * be able to provide something higher-level to better suit your needs. * - * @see https://reactrouter.com/docs/en/v6/api#uselocation + * @see https://reactrouter.com/docs/en/v6/hooks/use-location */ export function useLocation(): Location { invariant( @@ -86,7 +86,7 @@ export function useLocation(): Location { * Returns the current navigation action which describes how the router came to * the current location, either by a pop, push, or replace on the history stack. * - * @see https://reactrouter.com/docs/en/v6/api#usenavigationtype + * @see https://reactrouter.com/docs/en/v6/hooks/use-navigation-type */ export function useNavigationType(): NavigationType { return React.useContext(LocationContext).navigationType; @@ -97,7 +97,7 @@ export function useNavigationType(): NavigationType { * This is useful for components that need to know "active" state, e.g. * . * - * @see https://reactrouter.com/docs/en/v6/api#usematch + * @see https://reactrouter.com/docs/en/v6/hooks/use-match */ export function useMatch< ParamKey extends ParamParseKey, @@ -134,7 +134,7 @@ export interface NavigateOptions { * Returns an imperative method for changing the location. Used by s, but * may also be used by other elements to change the location. * - * @see https://reactrouter.com/docs/en/v6/api#usenavigate + * @see https://reactrouter.com/docs/en/v6/hooks/use-navigate */ export function useNavigate(): NavigateFunction { invariant( @@ -153,7 +153,7 @@ export function useNavigate(): NavigateFunction { ); let activeRef = React.useRef(false); - React.useEffect(() => { + React.useLayoutEffect(() => { activeRef.current = true; }); @@ -165,8 +165,6 @@ export function useNavigate(): NavigateFunction { `your component is first rendered.` ); - if (!activeRef.current) return; - if (typeof to === "number") { navigator.go(to); return; @@ -198,7 +196,7 @@ const OutletContext = React.createContext(null); /** * Returns the context (if provided) for the child route at this level of the route * hierarchy. - * @see https://reactrouter.com/docs/en/v6/api#useoutletcontext + * @see https://reactrouter.com/docs/en/v6/hooks/use-outlet-context */ export function useOutletContext(): Context { return React.useContext(OutletContext) as Context; @@ -208,7 +206,7 @@ export function useOutletContext(): Context { * Returns the element for the child route at this level of the route * hierarchy. Used internally by to render child routes. * - * @see https://reactrouter.com/docs/en/v6/api#useoutlet + * @see https://reactrouter.com/docs/en/v6/hooks/use-outlet */ export function useOutlet(context?: unknown): React.ReactElement | null { let outlet = React.useContext(RouteContext).outlet; @@ -224,7 +222,7 @@ export function useOutlet(context?: unknown): React.ReactElement | null { * Returns an object of key/value pairs of the dynamic params from the current * URL that were matched by the route path. * - * @see https://reactrouter.com/docs/en/v6/api#useparams + * @see https://reactrouter.com/docs/en/v6/hooks/use-params */ export function useParams< ParamsOrKey extends string | Record = string @@ -261,7 +259,7 @@ export function useResolvedPath(to: To): Path { * elements in the tree must render an to render their child route's * element. * - * @see https://reactrouter.com/docs/en/v6/api#useroutes + * @see https://reactrouter.com/docs/en/v6/hooks/use-routes */ export function useRoutes( routes: RouteObject[], diff --git a/packages/react-router/lib/router.ts b/packages/react-router/lib/router.ts index a5165980d5..a0c316ba67 100644 --- a/packages/react-router/lib/router.ts +++ b/packages/react-router/lib/router.ts @@ -98,7 +98,7 @@ export interface RouteObject { /** * Returns a path with params interpolated. * - * @see https://reactrouter.com/docs/en/v6/api#generatepath + * @see https://reactrouter.com/docs/en/v6/utils/generate-path */ export function generatePath(path: string, params: Params = {}): string { return path @@ -136,7 +136,7 @@ export interface RouteMatch { /** * Matches the given routes to a location and returns the match data. * - * @see https://reactrouter.com/docs/en/v6/api#matchroutes + * @see https://reactrouter.com/docs/en/v6/utils/match-routes */ export function matchRoutes( routes: RouteObject[], @@ -383,7 +383,7 @@ type Mutable = { * Performs pattern matching on a URL pathname and returns information about * the match. * - * @see https://reactrouter.com/docs/en/v6/api#matchpath + * @see https://reactrouter.com/docs/en/v6/utils/match-path */ export function matchPath< ParamKey extends ParamParseKey, @@ -502,7 +502,7 @@ function safelyDecodeURIComponent(value: string, paramName: string) { /** * Returns a resolved path object relative to the given pathname. * - * @see https://reactrouter.com/docs/en/v6/api#resolvepath + * @see https://reactrouter.com/docs/en/v6/utils/resolve-path */ export function resolvePath(to: To, fromPathname = "/"): Path { let {