Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Remix Data APIs #8937

Merged
merged 121 commits into from
Jun 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
121 commits
Select commit Hold shift + click to select a range
f2c207d
feat: add history package and createMemoryHistory implementation (#8702)
brophdawg11 Mar 10, 2022
55be575
feat: Add remix-router package and loader functionality
brophdawg11 Mar 8, 2022
4e77754
feat: add support for submissions
brophdawg11 Mar 21, 2022
fb1c063
chore: code organization + cleanup
brophdawg11 Mar 22, 2022
6740728
ci: copy over unit test from transition manager
brophdawg11 Mar 23, 2022
76122ac
fix: Fix action arguments bug
brophdawg11 Mar 23, 2022
63ff51a
feat: add createBrowserHistory and createHashHistory
brophdawg11 Mar 23, 2022
024a960
chore: rename package to @remix-run/router
brophdawg11 Mar 24, 2022
d609205
wip: initial router integration with React
ryanflorence Mar 24, 2022
3d03534
wip: add userLoaderData
ryanflorence Mar 25, 2022
80f5722
Remove history package and move history.ts into router
brophdawg11 Mar 25, 2022
d20a658
fix: fix URLSearchParams keys typescript error
brophdawg11 Mar 25, 2022
8fad5ba
feat: add data loading/rendering APIs
brophdawg11 Mar 25, 2022
da7ceba
feat: add browser/hash routers and bring over useSubmit
brophdawg11 Mar 28, 2022
58f3199
feat: bring <Form> to react router-dom
brophdawg11 Mar 29, 2022
e7ecafe
feat: support redirects returned from loaders
brophdawg11 Mar 29, 2022
52c2f9a
feat: Support basename in data routers
brophdawg11 Mar 29, 2022
3062939
chore: remove backup _components file
brophdawg11 Mar 29, 2022
5549c15
chore: extract dom.ts utils file
brophdawg11 Mar 30, 2022
04c0bb1
feat: add <Route shouldReload> and fix bug in shouldReload logic
brophdawg11 Mar 30, 2022
7511ed0
chore: convert to router.subscribe for easier useSyncExternalStore usage
brophdawg11 Mar 30, 2022
32e00a9
chore: switch to babel-jest TS transform instead of ts-jest
brophdawg11 Mar 31, 2022
b577e21
fix: make route id optional for backwards compatibility
brophdawg11 Mar 31, 2022
e0d9881
feat: default fallbackElement/exceptionElement + add useMacthes/useRo…
brophdawg11 Apr 1, 2022
ff79e13
fix: Fix up build issues with v5-compat
brophdawg11 Apr 1, 2022
c60df10
chore: renames and feedback cleanup
brophdawg11 Apr 1, 2022
8fac456
feat: handle render errors through exceptionElement
brophdawg11 Apr 4, 2022
e09edee
feat: move initial data load into router
brophdawg11 Apr 4, 2022
900cc42
chore: do not handle exceptions for non-data-routers
brophdawg11 Apr 4, 2022
6d41463
chore: remove historyv5 dependency
brophdawg11 Apr 4, 2022
39e64d1
feat: add support for router.revalidate()
brophdawg11 Apr 5, 2022
2730ada
feat: add useRevalidator() hook
brophdawg11 Apr 5, 2022
a0bdf73
fix: fix shouldReload support for revalidate()
brophdawg11 Apr 5, 2022
35fe910
fix: handle 405 action responses correctly
brophdawg11 Apr 6, 2022
0709d47
feat: handle X-Remix-Revalidate hesders returned from loader redirects
brophdawg11 Apr 6, 2022
ba0a23b
chore: PR feedback
brophdawg11 Apr 7, 2022
6ba14ac
chore: copy transition fetcher tests verbatim
brophdawg11 Apr 6, 2022
a94f7f0
feat: add support for fetchers
brophdawg11 Apr 6, 2022
f10fe6b
feat: add useFetcher
brophdawg11 Apr 8, 2022
2033900
ci: add tests for useFetcher/useFetchers
brophdawg11 Apr 11, 2022
66ea797
chore: add formAction to submission
brophdawg11 Apr 11, 2022
60d52e9
chore: remove basename from data routers
brophdawg11 Apr 11, 2022
cb90cb2
chore: change shouldReload url type
brophdawg11 Apr 11, 2022
fa2a721
chore: change default fallback element
brophdawg11 Apr 12, 2022
6fbe9cb
chore: add tests for cleanup and make Router type explicit
brophdawg11 Apr 12, 2022
16df86d
feat: support returning responses from loaders/actions
ryanflorence Apr 12, 2022
5adfc5e
chore: cleanup auto-unwrap logic, add action info to request, remove …
brophdawg11 Apr 12, 2022
1f3e0d6
feat: provide full control to users in shouldRevalidate
brophdawg11 Apr 13, 2022
d943d41
chore: remove hashchange and create createUrlBasedHistory abstraction
brophdawg11 Apr 13, 2022
bbc7244
fix: handle syncronous initial load
brophdawg11 Apr 14, 2022
f312cbf
fix request patch
brophdawg11 Apr 15, 2022
108a65f
feat: support fetcher opt-in revalidation
brophdawg11 Apr 15, 2022
58ba17f
ci: clean up revalidating fetcher tests and logic
brophdawg11 Apr 18, 2022
d50cedd
feat: wiure up useFetcher and turn off revalidation on submit
brophdawg11 Apr 18, 2022
3f19ff9
docs: update transition/fetcher diagrams
brophdawg11 Apr 18, 2022
51cc008
ci: bump bundle thresholds for new data routing
brophdawg11 Apr 26, 2022
eb1668f
chore: update to latest @web-std/fetch with proper formData handling
brophdawg11 May 2, 2022
0032aca
feat: move to better cross-browser fallback spinner
brophdawg11 May 2, 2022
63f5441
Revert "feat: move to better cross-browser fallback spinner"
brophdawg11 May 2, 2022
29b20af
chore: Rename "exception" to "error"
brophdawg11 May 6, 2022
dde861b
feat: handle revalidation for interrupted submissions
brophdawg11 Apr 19, 2022
fb4ad3b
feat: opt-in fetcher.load to revalidation by default
brophdawg11 May 6, 2022
746d457
feat: send actionResult to shouldRevalidate
brophdawg11 May 6, 2022
0eea401
feat: add <ScrollRestoration> component and <Link resetScroll=false>
brophdawg11 Apr 19, 2022
0464c6c
chore: rename navigation -> transition
brophdawg11 May 10, 2022
cd6d365
feat: flatten submission onto shouldRevalidate
brophdawg11 May 10, 2022
86cc465
feat: simplify form submit logic via event.submitter
brophdawg11 May 10, 2022
61e90b7
chore: clean up types
brophdawg11 May 10, 2022
b4c4ec2
chore: sync up react-router-native public api
brophdawg11 May 10, 2022
727170d
feat: properly support React 18 + React.StrictMode
brophdawg11 May 11, 2022
9ed3804
chore: fix lint warnings
brophdawg11 May 11, 2022
058e7f6
chore: remove default fallback element
brophdawg11 May 11, 2022
6eaf162
chore: add @remix-run/router to example vite configs for USE_SOURCE
brophdawg11 May 11, 2022
711d4db
chore: add data-router and scroll-restoration examples
brophdawg11 May 11, 2022
a44fb5f
chore: switch from @web-std/fetch to @remix-run/web-fetch for tests
brophdawg11 May 16, 2022
99bf102
feat: add pending logic to NavLink (#8875)
brophdawg11 May 16, 2022
70cab74
chore: inline react use-sync-external-store/shim for UMD builds
brophdawg11 May 16, 2022
9b2f174
chore: clean up some exports and add some jsdocs
brophdawg11 May 17, 2022
9f40c33
feat: add DataStaticRouter for SSR
brophdawg11 May 19, 2022
b13865e
chore: fix link
brophdawg11 May 19, 2022
8c7b2bf
fix: default replace=false only for GET submissions
brophdawg11 May 19, 2022
a895bca
chore: update scroll restoration example to include data loading
brophdawg11 May 19, 2022
e4c9a2a
feat: remove fetcher.type since it can be derived
brophdawg11 May 20, 2022
ae837ee
feat: Remove promise from return signature of router methods
brophdawg11 May 20, 2022
c2d316d
chore: remove encType from Form props
brophdawg11 May 20, 2022
86ca197
WIP docs
ryanflorence May 20, 2022
018e094
feat: auto-unwrap error Responses into ErrorResponse
brophdawg11 May 20, 2022
b919afd
chore: add error boundary example
brophdawg11 May 20, 2022
e2e4341
feat: support routes prop on data routers
brophdawg11 May 20, 2022
ebca0ff
docs: moar docs
ryanflorence May 21, 2022
c78e674
chore: remove navigation.type
brophdawg11 May 21, 2022
a8a3f7c
docs docs docs
ryanflorence May 21, 2022
37ef9ab
dooooooooooooooooooocs
ryanflorence May 21, 2022
2058362
docs: fix contributing link (#8885)
alexlobera May 21, 2022
c9e6681
Docs: Fix typo in getting-started > concepts (#8888)
tanayv May 21, 2022
4be7677
chore: sort contributors list
remix-cla-bot[bot] May 21, 2022
f7cd2a0
docs: think it’s ready for the big time
ryanflorence May 22, 2022
af38401
docs: notes demo
ryanflorence May 22, 2022
110b924
Update overview.md (#8892)
vikingviolinist May 22, 2022
9df8be0
Merge branch 'main' into remixing
ryanflorence May 23, 2022
2b7870d
Version 6.4.0-pre.0
ryanflorence May 23, 2022
863927f
docs: link fixes
ryanflorence May 23, 2022
538a3da
chore: publish @remix-run/router
ryanflorence May 23, 2022
3468a06
Version 6.4.0-pre.1
ryanflorence May 23, 2022
794ed5b
chore: publish @remix-run/router
ryanflorence May 23, 2022
3179f13
Version 6.4.0-pre.2
ryanflorence May 23, 2022
ea41d7c
docs: DataStaticRouter stub
ryanflorence May 23, 2022
96163ca
docs: not sure what this was 😂
ryanflorence May 23, 2022
ce1f97c
docs: link to the demo
ryanflorence May 23, 2022
0e59e7d
fix: properly handle 404s using error boundaries
brophdawg11 May 24, 2022
4c1ab96
docs: getting started installation
ryanflorence Jun 1, 2022
afdc846
Convert formMethod=GET to be a loading navigation instead of submitting
brophdawg11 Jun 1, 2022
62a2b3d
Handle invalid GET formData via 400 Bad Request error
brophdawg11 Jun 1, 2022
f4d7de5
fix: Properly handle descendent routes within a data router
brophdawg11 Jun 1, 2022
d04c612
docs: update useMatches and useNavigation docs
brophdawg11 Jun 1, 2022
cba5820
fix: better solution for 404 error boundaries
brophdawg11 Jun 1, 2022
6b27e2c
chore: refactor resetScroll to avoid history state mutation
brophdawg11 Jun 1, 2022
71f024b
docs: add note on useMatches and descendent routes
brophdawg11 Jun 1, 2022
2b3f3b9
Merge branch 'dev' into remixing
brophdawg11 Jun 6, 2022
476f4d9
chore: move newly added resolveTo test from react-router to router
brophdawg11 Jun 6, 2022
b169421
ci: Remove website workflow before merging remixing work into dev
brophdawg11 Jun 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 0 additions & 31 deletions .github/workflows/website.yml

This file was deleted.

3 changes: 3 additions & 0 deletions contributors.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
- abhi-kr-2100
- Ajayff4
- alexlbr
- avipatel97
- awreese
- bhbs
Expand Down Expand Up @@ -54,10 +55,12 @@
- shihanng
- shivamsinghchahar
- theostavrides
- tanayv
- thisiskartik
- ThornWu
- timdorr
- tkindy
- turansky
- underager
- vijaypushkin
- vikingviolinist
307 changes: 307 additions & 0 deletions docs/components/form.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,307 @@
---
title: Form
new: true
---

# `<Form>`

The Form component is a wrapper around a plain HTML [form][htmlform] that emulates the browser for client side routing and data mutations. It is _not_ a form validation/state management library like you might be used to in the React ecosystem (for that, we recommend the browser's built in [HTML Form Validation][formvalidation] and data validation on your backend server).

```tsx
import { Form } from "react-router-dom";

function NewEvent() {
return (
<Form method="post" action="/events">
<input type="text" name="title" />
<input type="text" name="description" />
<button type="submit">Create</button>
</Form>
);
}
```

<docs-info>Make sure your inputs have names or else the `FormData` will not include that field's value.</docs-info>

All of this will trigger state updates to any rendered [`useNavigation`][usenavigation] hooks so you can build pending indicators and optimistic UI while the async operations are in-flight.

If the form doesn't _feel_ like navigation, you probably want [`useFetcher`][usefetcher].

## `action`

The url to which the form will be submitted, just like [HTML form action][htmlformaction]. The only difference is the default action. With HTML forms, it defaults to the full URL. With `<Form>`, it defaults to the relative URL of the closest route in context.

Consider the following routes and components:

```jsx
function ProjectsLayout() {
return (
<>
<Form method="post" />
<Outlet />
</>
);
}

function ProjectsPage() {
return <Form method="post" />;
}

<DataBrowserRouter>
<Route
path="/projects"
element={<ProjectsLayout />}
action={ProjectsLayout.action}
>
<Route
path=":projectId"
element={<ProjectPage />}
action={ProjectsPage.action}
/>
</Route>
</DataBrowserRouter>;
```

If the the current URL is `"/projects/123"`, the form inside the child
route, `ProjectsPage`, will have a default action as you might expect: `"/projects/123"`. In this case, where the route is the deepest matching route, both `<Form>` and plain HTML forms have the same result.

But the form inside of `ProjectsLayout` will point to `"/projects"`, not the full URL. In other words, it points to the matching segment of the URL for the route in which the form is rendered.

This helps with portability as well as co-location of forms and their action handlers when if you add some convention around your route modules.

If you need to post to a different route, then add an action prop:

```tsx
<Form action="/projects/new" method="post" />
```

**See also:**

- [Index Search Param][indexsearchparam] (index vs parent route disambiguation)

## `method`

This determines the [HTTP verb](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods) to be used. The same as plain HTML [form method][htmlform-method], except it also supports "put", "patch", and "delete" in addition to "get" and "post". The default is "get".

### GET submissions

The default method is "get". Get submissions _will not call an action_. Get submissions are the same as a normal navigation (user clicks a link) except the user gets to supply the search params that go to the URL from the form.

```tsx
<Form method="get" action="/products">
<input
aria-label="search products"
type="text"
name="q"
/>
<button type="submit">Search</button>
</Form>
```

Let's say the user types in "running shoes" and submits the form. React Router emulates the browser and will serialize the form into [URLSearchParams][urlsearchparams] and then navigate the user to `"/products?q=running+shoes"`. It's as if you rendered a `<Link to="/products?q=running+shoes">` as the developer, but instead you let the user supply the query string dynamically.

Your route loader can access these values most conveniently by creating a new [`URL`][url] from the `request.url` and then load the data.

```tsx
<Route
path="/products"
loader={async ({ request }) => {
let url = new URL(request.url);
let searchTerm = url.searchParams.get("q");
return fakeSearchProducts(searchTerm);
}}
/>
```

### Mutation Submissions

All other methods are "mutation submissions", meaning you intend to change something about your data with POST, PUT, PATCH, or DELETE. Note that plain HTML forms only support "post" and "get", we tend to stick to those two as well.

When the user submits the form, React Router will match the `action` to the app's routes and call the `<Route action>` with the serialized [`FormData`][formdata]. When the action completes, all of the loader data on the page will automatically revalidate to keep your UI in sync with your data.

The method will be available on [`request.method`][requestmethod] inside the route action that is called. You can use this to instruct your data abstractions about the intent of the submission.

```tsx
<Route
path="/projects/:id"
element={<Project />}
loader={async ({ params }) => {
return fakeLoadProject(params.id)
}}
action={async ({ request, params }) => {
switch (request.method) {
case "put": {
let formData = await request.formData();
let name = formData.get("projectName");
return fakeUpdateProject(name);
}
case "delete": {
return fakeDeleteProject(params.id);
}
default {
throw new Response("", { status: 405 })
}
}
}}
/>;

function Project() {
let project = useLoaderData();

return (
<>
<Form method="put">
<input
type="text"
name="projectName"
defaultValue={project.name}
/>
<button type="submit">Update Project</button>
</Form>

<Form method="delete">
<button type="submit">Delete Project</button>
</Form>
</>
);
}
```

As you can see, both forms submit to the same route but you can use the `request.method` to branch on what you intend to do. After the actions completes, the `loader` will be revalidated and the UI will automatically synchronize with the new data.

## `replace`

Instructs the form to replace the current entry in the history stack, instead of pushing the new entry.

```tsx
<Form replace />
```

The default behavior is conditional on the form `method`:

- `get` defaults to `false`
- every other method defaults to `true`

We've found with `get` you often want the user to be able to click "back" to see the previous search results/filters, etc. But with the other methods the default is `true` to avoid the "are you sure you want to resubmit the form?" prompt. Note that even if `replace={false}` React Router _will not_ resubmit the form when the back button is clicked and the method is post, put, patch, or delete.

In other words, this is really only useful for GET submissions and you want to avoid the back button showing the previous results.

## `reloadDocument`

Instructs the form to skip React Router and submit the form with the browser's built in behavior.

```tsx
<Form reloadDocument />
```

This is recommended over `<form>` so you can get the benefits of default and relative `action`, but otherwise is the same as a plain HTML form.

Without a framework like [Remix][remix], or your own server handling of posts to routes, this isn't very useful.

See also:

- [`useTransition`][usetransition]
- [`useActionData`][useactiondata]
- [`useSubmit`][usesubmit]

# Examples

TODO: More examples

## Large List Filtering

A common use case for GET submissions is filtering a large list, like ecommerce and travel booking sites.

```tsx
function FilterForm() {
return (
<Form method="get" action="/slc/hotels">
<select name="sort">
<option value="price">Price</option>
<option value="stars">Stars</option>
<option value="distance">Distance</option>
</select>

<fieldset>
<legend>Star Rating</legend>
<label>
<input type="radio" name="stars" value="5" />{" "}
★★★★★
</label>
<label>
<input type="radio" name="stars" value="4" /> ★★★★
</label>
<label>
<input type="radio" name="stars" value="3" /> ★★★
</label>
<label>
<input type="radio" name="stars" value="2" /> ★★
</label>
<label>
<input type="radio" name="stars" value="1" /> ★
</label>
</fieldset>

<fieldset>
<legend>Amenities</legend>
<label>
<input
type="checkbox"
name="amenities"
value="pool"
/>{" "}
Pool
</label>
<label>
<input
type="checkbox"
name="amenities"
value="exercise"
/>{" "}
Exercise Room
</label>
</fieldset>
<button type="submit">Search</button>
</Form>
);
}
```

When the user submits this form, the form will be serialized to the URL with something like this, depending on the user's selections:

```
/slc/hotels?sort=price&stars=4&amenities=pool&amenities=exercise
```

You can access those values from the `request.url`

```tsx
<Route
path="/:city/hotels"
loader={async ({ request }) => {
let url = new URL(request.url);
let sort = url.searchParams.get("sort");
let stars = url.searchParams.get("stars");
let amenities = url.searchParams.getAll("amenities");
return fakeGetHotels({ sort, stars, amenities });
}}
/>
```

**See also:**

- [useSubmit][usesubmit]

[usenavigation]: ../hooks/use-navigation
[formdata]: https://developer.mozilla.org/en-US/docs/Web/API/FormData
[usefetcher]: ../hooks/use-fetcher
[htmlform]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form
[htmlformaction]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-action
[htmlform-method]: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/form#attr-method
[urlsearchparams]: https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams
[url]: https://developer.mozilla.org/en-US/docs/Web/API/URL
[usesubmit]: ../hooks/use-submit
[requestmethod]: https://developer.mozilla.org/en-US/docs/Web/API/Request/method
[remix]: https://remix.run
[formvalidation]: https://developer.mozilla.org/en-US/docs/Learn/Forms/Form_validation
[indexsearchparam]: ../guides/index-search-param
2 changes: 1 addition & 1 deletion docs/components/index.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
---
title: Components
order: 4
order: 3
---
2 changes: 1 addition & 1 deletion docs/components/link-native.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
title: Link (React Native)
title: Link (RN)
---

# `<Link>` (React Native)
Expand Down
Loading