-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): create a
<Link />
component to navigate to a given reso…
…urce. (#6330)
- Loading branch information
1 parent
6e5d97c
commit 5a81b35
Showing
11 changed files
with
380 additions
and
76 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
--- | ||
"@refinedev/core": minor | ||
--- | ||
|
||
feat: add [`<Link />`](https://refine.dev/docs/routing/components/link/) component to navigate to a resource with a specific action. Under the hood, It uses [`useGo`](https://refine.dev/docs/routing/hooks/use-go/) to generate the URL. | ||
|
||
## Usage | ||
|
||
```tsx | ||
import { Link } from "@refinedev/core"; | ||
|
||
const MyComponent = () => { | ||
return ( | ||
<> | ||
{/* simple usage, navigates to `/posts` */} | ||
<Link to="/posts">Posts</Link> | ||
{/* complex usage with more control, navigates to `/posts` with query filters */} | ||
<Link | ||
go={{ | ||
query: { | ||
// `useTable` or `useDataGrid` automatically use this filters to fetch data if `syncWithLocation` is true. | ||
filters: [ | ||
{ | ||
operator: "eq", | ||
value: "published", | ||
field: "status", | ||
}, | ||
], | ||
}, | ||
to: { | ||
resource: "posts", | ||
action: "list", | ||
}, | ||
}} | ||
> | ||
Posts | ||
</Link> | ||
</> | ||
); | ||
}; | ||
``` | ||
|
||
[Fixes #6329](https://github.com/refinedev/refine/issues/6329) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
--- | ||
"@refinedev/core": minor | ||
--- | ||
|
||
chore: From now on, [`useLink`](https://refine.dev/docs/routing/hooks/use-link/) returns [`<Link />`](https://refine.dev/docs/routing/components/link/) component instead of returning [`routerProvider.Link`](https://refine.dev/docs/routing/router-provider/#link). | ||
|
||
Since the `<Link />` component uses `routerProvider.Link` under the hood with leveraging `useGo` hook to generate the URL there is no breaking change. It's recommended to use the `<Link />` component from the `@refinedev/core` package instead of `useLink` hook. This hook is used mostly for internal purposes and is only exposed for customization needs. | ||
|
||
[Fixes #6329](https://github.com/refinedev/refine/issues/6329) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
--- | ||
title: <Link /> | ||
--- | ||
|
||
`<Link />` is a component that is used to navigate to different pages in your application. | ||
|
||
It uses [`routerProvider.Link`](/docs/routing/router-provider/#link) under the hood, if [`routerProvider`](/docs/routing/router-provider) is not provided, it will be use [`<a>`](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/a) HTML element. | ||
|
||
## Usage | ||
|
||
```tsx | ||
import { Link } from "@refinedev/core"; | ||
|
||
const MyComponent = () => { | ||
return ( | ||
<> | ||
{/* simple usage, navigates to `/posts` */} | ||
<Link to="/posts">Posts</Link> | ||
{/* complex usage with more control, navigates to `/posts` with query filters */} | ||
<Link | ||
go={{ | ||
query: { | ||
// `useTable` or `useDataGrid` automatically uses these filters to fetch data if `syncWithLocation` is true. | ||
filters: [ | ||
{ | ||
operator: "eq", | ||
value: "published", | ||
field: "status", | ||
}, | ||
], | ||
}, | ||
to: { | ||
resource: "posts", | ||
action: "list", | ||
}, | ||
}} | ||
> | ||
Posts | ||
</Link> | ||
</> | ||
); | ||
}; | ||
``` | ||
|
||
## Props | ||
|
||
The `<Link />` component takes all the props from the [`routerProvider.Link`](/docs/routing/router-provider/#link) and the props that an `<a>` HTML element uses. In addition to these props, it also accepts the `go` | ||
and `to` props to navigate to a specific `resource` defined in the `<Refine />` component. | ||
|
||
### go | ||
|
||
When `go` prop is provided, this component will use [`useGo`](/docs/routing/hooks/use-go/) to create the URL to navigate to. It's accepts all the props that `useGo.go` accepts. | ||
|
||
It's useful to use this prop when you want to navigate to a resource with a specific action. | ||
|
||
:::caution | ||
|
||
- `routerProvider` is required to use this prop. | ||
- When `to` prop is provided, `go` will be ignored. | ||
|
||
::: | ||
|
||
### to | ||
|
||
The URL to navigate to. | ||
|
||
## Type support with generics | ||
|
||
`<Link />` works with any routing library because it uses [`routerProvider.Link`](/docs/routing/router-provider/#link) internally. However, when importing it from `@refinedev/core`, it doesn't provide type support for your specific routing library. To enable full type support, you can use generics. | ||
|
||
```tsx | ||
import type { LinkProps } from "react-router-dom"; | ||
import { Link } from "@refinedev/core"; | ||
|
||
const MyComponent = () => { | ||
return ( | ||
// Omit 'to' prop from LinkProps (required by react-router-dom) since we use the 'go' prop | ||
<Link<Omit<LinkProps, "to">> | ||
// Props from "react-router-dom" | ||
// highlight-start | ||
replace={true} | ||
unstable_viewTransition={true} | ||
preventScrollReset={true} | ||
// highlight-end | ||
// Props from "@refinedev/core" | ||
go={{ | ||
to: { | ||
resource: "posts", | ||
action: "list", | ||
}, | ||
}} | ||
> | ||
Posts | ||
</Link> | ||
); | ||
}; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import React from "react"; | ||
import { TestWrapper, render } from "@test/index"; | ||
import { Link } from "./index"; | ||
|
||
describe("Link", () => { | ||
describe("with `to`", () => { | ||
it("should render a tag without router provider", () => { | ||
const { getByText } = render(<Link to="/test">Test</Link>); | ||
|
||
const link = getByText("Test"); | ||
expect(link.tagName).toBe("A"); | ||
expect(link.getAttribute("href")).toBe("/test"); | ||
}); | ||
|
||
it("should render a tag with router provider", () => { | ||
const { getByTestId } = render( | ||
<Link<{ foo: "bar" }> foo="bar" to="/test" aria-label="test-label"> | ||
Test | ||
</Link>, | ||
{ | ||
wrapper: TestWrapper({ | ||
routerProvider: { | ||
Link: ({ to, children, ...props }) => ( | ||
<a href={to} data-testid="test-link" {...props}> | ||
{children} | ||
</a> | ||
), | ||
}, | ||
}), | ||
}, | ||
); | ||
|
||
const link = getByTestId("test-link"); | ||
expect(link.tagName).toBe("A"); | ||
expect(link.getAttribute("href")).toBe("/test"); | ||
expect(link.getAttribute("aria-label")).toBe("test-label"); | ||
expect(link.getAttribute("foo")).toBe("bar"); | ||
}); | ||
}); | ||
|
||
describe("with `go`", () => { | ||
it("should render a tag go.to as object", () => { | ||
const { getByTestId } = render( | ||
<Link | ||
go={{ | ||
to: { | ||
resource: "test", | ||
action: "show", | ||
id: 1, | ||
}, | ||
options: { keepQuery: true }, | ||
}} | ||
aria-label="test-label" | ||
> | ||
Test | ||
</Link>, | ||
{ | ||
wrapper: TestWrapper({ | ||
resources: [{ name: "test", show: "/test/:id" }], | ||
routerProvider: { | ||
go: () => () => { | ||
return "/test/1"; | ||
}, | ||
Link: ({ to, children, ...props }) => ( | ||
<a href={to} data-testid="test-link" {...props}> | ||
{children} | ||
</a> | ||
), | ||
}, | ||
}), | ||
}, | ||
); | ||
|
||
const link = getByTestId("test-link"); | ||
expect(link.tagName).toBe("A"); | ||
expect(link.getAttribute("href")).toBe("/test/1"); | ||
expect(link.getAttribute("aria-label")).toBe("test-label"); | ||
}); | ||
|
||
it("should render a tag go.to as string", () => { | ||
const { getByTestId } = render( | ||
<Link | ||
go={{ | ||
to: "/test/1", | ||
}} | ||
aria-label="test-label" | ||
> | ||
Test | ||
</Link>, | ||
{ | ||
wrapper: TestWrapper({ | ||
routerProvider: { | ||
go: () => () => { | ||
return "/test/1"; | ||
}, | ||
Link: ({ to, children, ...props }) => ( | ||
<a href={to} data-testid="test-link" {...props}> | ||
{children} | ||
</a> | ||
), | ||
}, | ||
}), | ||
}, | ||
); | ||
|
||
const link = getByTestId("test-link"); | ||
expect(link.tagName).toBe("A"); | ||
expect(link.getAttribute("href")).toBe("/test/1"); | ||
expect(link.getAttribute("aria-label")).toBe("test-label"); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.