diff --git a/apps/react-router/package.json b/apps/react-router/package.json
index 9af6936..50b3b1e 100644
--- a/apps/react-router/package.json
+++ b/apps/react-router/package.json
@@ -15,7 +15,7 @@
"domain-functions": "^2.6.0",
"react": "18.3.1",
"react-dom": "18.3.1",
- "react-hook-form": "^7.51.3",
+ "react-hook-form": "^7.51.4",
"react-router-dom": "^6.23.0",
"remix-forms": "*",
"zod": "^3.23.6"
diff --git a/apps/remix-1-7/.eslintrc.cjs b/apps/remix-1-7/.eslintrc.cjs
new file mode 100644
index 0000000..ba825df
--- /dev/null
+++ b/apps/remix-1-7/.eslintrc.cjs
@@ -0,0 +1,6 @@
+const path = require('path')
+
+module.exports = {
+ extends: path.resolve(__dirname, '../../.eslintrc.base.js'),
+ rules: {},
+}
diff --git a/apps/remix-1-7/.gitignore b/apps/remix-1-7/.gitignore
new file mode 100644
index 0000000..3f7bf98
--- /dev/null
+++ b/apps/remix-1-7/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+
+/.cache
+/build
+/public/build
+.env
diff --git a/apps/remix-1-7/.prettierignore b/apps/remix-1-7/.prettierignore
new file mode 100644
index 0000000..11e1dbb
--- /dev/null
+++ b/apps/remix-1-7/.prettierignore
@@ -0,0 +1,7 @@
+node_modules
+build
+public/build
+dist
+tsc
+*.css
+*.html
diff --git a/apps/remix-1-7/README.md b/apps/remix-1-7/README.md
new file mode 100644
index 0000000..9659e78
--- /dev/null
+++ b/apps/remix-1-7/README.md
@@ -0,0 +1,53 @@
+# Welcome to Remix!
+
+- [Remix Docs](https://remix.run/docs)
+
+## Development
+
+From your terminal:
+
+```sh
+npm run dev
+```
+
+This starts your app in development mode, rebuilding assets on file changes.
+
+## Deployment
+
+First, build your app for production:
+
+```sh
+npm run build
+```
+
+Then run the app in production mode:
+
+```sh
+npm start
+```
+
+Now you'll need to pick a host to deploy it to.
+
+### DIY
+
+If you're familiar with deploying node applications, the built-in Remix app server is production-ready.
+
+Make sure to deploy the output of `remix build`
+
+- `build/`
+- `public/build/`
+
+### Using a Template
+
+When you ran `npx create-remix@latest` there were a few choices for hosting. You can run that again to create a new project, then copy over your `app/` folder to the new project that's pre-configured for your target server.
+
+```sh
+cd ..
+# create a new project, and pick a pre-configured host
+npx create-remix@latest
+cd my-new-remix-app
+# remove the new project's app (not the old one!)
+rm -rf app
+# copy your app over
+cp -R ../my-old-remix-app/app app
+```
diff --git a/apps/web/app/entry.client.tsx b/apps/remix-1-7/app/entry.client.tsx
similarity index 100%
rename from apps/web/app/entry.client.tsx
rename to apps/remix-1-7/app/entry.client.tsx
diff --git a/apps/web/app/entry.server.tsx b/apps/remix-1-7/app/entry.server.tsx
similarity index 100%
rename from apps/web/app/entry.server.tsx
rename to apps/remix-1-7/app/entry.server.tsx
diff --git a/apps/remix-1-7/app/formAction.ts b/apps/remix-1-7/app/formAction.ts
new file mode 100644
index 0000000..f918c19
--- /dev/null
+++ b/apps/remix-1-7/app/formAction.ts
@@ -0,0 +1,6 @@
+import { json, redirect } from '@remix-run/node'
+import { createFormAction } from 'remix-forms'
+
+const formAction = createFormAction({ redirect, json })
+
+export { formAction }
diff --git a/apps/remix-1-7/app/root.tsx b/apps/remix-1-7/app/root.tsx
new file mode 100644
index 0000000..dc4fa02
--- /dev/null
+++ b/apps/remix-1-7/app/root.tsx
@@ -0,0 +1,27 @@
+import {
+ Links,
+ LiveReload,
+ Meta,
+ Outlet,
+ Scripts,
+ ScrollRestoration,
+} from '@remix-run/react'
+
+export default function App() {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/apps/remix-1-7/app/routes/index.tsx b/apps/remix-1-7/app/routes/index.tsx
new file mode 100644
index 0000000..9e30024
--- /dev/null
+++ b/apps/remix-1-7/app/routes/index.tsx
@@ -0,0 +1,23 @@
+import { makeDomainFunction } from 'domain-functions'
+import type { ActionArgs } from '@remix-run/node'
+import { z } from 'zod'
+import { formAction } from '../formAction'
+import { Form } from '../ui/form'
+
+const schema = z.object({
+ firstName: z.string().min(1),
+ email: z.string().min(1).email(),
+})
+
+const mutation = makeDomainFunction(schema)(async (values) => values)
+
+export function action({ request }: ActionArgs) {
+ return formAction({
+ request,
+ schema,
+ mutation,
+ successPath: '/success',
+ })
+}
+
+export default () =>
diff --git a/apps/remix-1-7/app/routes/success.tsx b/apps/remix-1-7/app/routes/success.tsx
new file mode 100644
index 0000000..d310774
--- /dev/null
+++ b/apps/remix-1-7/app/routes/success.tsx
@@ -0,0 +1 @@
+export default () => Success!
diff --git a/apps/remix-1-7/app/ui/form.tsx b/apps/remix-1-7/app/ui/form.tsx
new file mode 100644
index 0000000..b7e8030
--- /dev/null
+++ b/apps/remix-1-7/app/ui/form.tsx
@@ -0,0 +1,16 @@
+import { createForm } from 'remix-forms'
+import {
+ Form as RemixForm,
+ useActionData,
+ useSubmit,
+ useTransition as useNavigation,
+} from '@remix-run/react'
+
+const Form = createForm({
+ component: RemixForm,
+ useNavigation,
+ useSubmit,
+ useActionData,
+})
+
+export { Form }
diff --git a/apps/remix-1-7/package.json b/apps/remix-1-7/package.json
new file mode 100644
index 0000000..29d925f
--- /dev/null
+++ b/apps/remix-1-7/package.json
@@ -0,0 +1,34 @@
+{
+ "name": "remix-forms-remix-1-7",
+ "private": true,
+ "sideEffects": false,
+ "scripts": {
+ "build": "remix build",
+ "dev": "PORT=3002 remix dev",
+ "start": "remix-serve build",
+ "lint": "eslint . --max-warnings=0",
+ "prelint": "prettier --check .",
+ "tsc": "tsc"
+ },
+ "dependencies": {
+ "@remix-run/node": "1.7.4",
+ "@remix-run/react": "1.7.4",
+ "@remix-run/serve": "1.7.4",
+ "domain-functions": "^2.6.0",
+ "isbot": "^5.1.6",
+ "react": "^18.3.1",
+ "react-dom": "^18.3.1",
+ "remix-forms": "*",
+ "zod": "^3.23.6"
+ },
+ "devDependencies": {
+ "@remix-run/dev": "1.7.4",
+ "@remix-run/eslint-config": "2.9.1",
+ "@types/react": "^18.3.1",
+ "@types/react-dom": "^18.3.0",
+ "typescript": "~5.4.5"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+}
diff --git a/apps/remix-1-7/public/favicon.ico b/apps/remix-1-7/public/favicon.ico
new file mode 100644
index 0000000..8830cf6
Binary files /dev/null and b/apps/remix-1-7/public/favicon.ico differ
diff --git a/apps/remix-1-7/remix.config.js b/apps/remix-1-7/remix.config.js
new file mode 100644
index 0000000..b26d07b
--- /dev/null
+++ b/apps/remix-1-7/remix.config.js
@@ -0,0 +1,15 @@
+/** @type {import('@remix-run/dev').AppConfig} */
+module.exports = {
+ ignoredRouteFiles: ['**/.*'],
+ // appDirectory: "app",
+ // assetsBuildDirectory: "public/build",
+ // serverBuildPath: "build/index.js",
+ // publicPath: "/build/",
+ future: {
+ v2_errorBoundary: true,
+ v2_meta: true,
+ v2_normalizeFormMethod: true,
+ v2_routeConvention: true,
+ },
+ devServerPort: 8004,
+}
diff --git a/apps/remix-1-7/remix.env.d.ts b/apps/remix-1-7/remix.env.d.ts
new file mode 100644
index 0000000..dcf8c45
--- /dev/null
+++ b/apps/remix-1-7/remix.env.d.ts
@@ -0,0 +1,2 @@
+///
+///
diff --git a/apps/remix-1-7/tsconfig.json b/apps/remix-1-7/tsconfig.json
new file mode 100644
index 0000000..9a995ac
--- /dev/null
+++ b/apps/remix-1-7/tsconfig.json
@@ -0,0 +1,22 @@
+{
+ "include": ["remix.env.d.ts", "**/*.ts", "**/*.tsx"],
+ "compilerOptions": {
+ "lib": ["DOM", "DOM.Iterable", "ES2019"],
+ "isolatedModules": true,
+ "esModuleInterop": true,
+ "jsx": "react-jsx",
+ "moduleResolution": "node",
+ "resolveJsonModule": true,
+ "target": "ES2019",
+ "strict": true,
+ "allowJs": true,
+ "forceConsistentCasingInFileNames": true,
+ "baseUrl": ".",
+ "paths": {
+ "~/*": ["./app/*"]
+ },
+ "skipLibCheck": true,
+ // Remix takes care of building everything in `remix build`.
+ "noEmit": true
+ }
+}
diff --git a/apps/web/app/helpers.ts b/apps/web/app/helpers.ts
index 23bd54a..cd912b2 100644
--- a/apps/web/app/helpers.ts
+++ b/apps/web/app/helpers.ts
@@ -1,30 +1,41 @@
-import { compose, join, reject, isBoolean, isNil, flatten } from 'lodash/fp'
-import type { HtmlMetaDescriptor } from '@remix-run/node'
+import social from './social.png'
+import type { MetaDescriptor } from '@remix-run/node'
-const cx = (...args: unknown[]) =>
- compose(join(' '), reject(isBoolean), reject(isNil), flatten)(args)
+function cx(...args: unknown[]): string {
+ return args
+ .flat()
+ .filter((x) => typeof x === 'string')
+ .join(' ')
+}
function pageTitle(title: string) {
return `${title} ยท Remix Forms`
}
+const baseMeta = [
+ { property: 'author', content: 'Seasoned' },
+ { property: 'og:type', content: 'website' },
+ { property: 'og:image', content: social },
+ { property: 'og:site_name', content: 'Remix Forms' },
+]
+
function metaTags({
title: rawTitle,
description,
- ...otherTags
-}: Record) {
+}: {
+ title: string
+ description?: string
+}): MetaDescriptor[] {
const title = rawTitle ? pageTitle(rawTitle) : null
- const titleTags = title ? { title, 'og:title': title } : {}
+ const titleTags: MetaDescriptor[] = title
+ ? [{ title }, { property: 'og:title', content: title }]
+ : []
- const descriptionTags = description
- ? { description, 'og:description': description }
- : {}
+ const descriptionTags: MetaDescriptor[] = description
+ ? [{ description }, { property: 'og:description', content: description }]
+ : []
- return {
- ...titleTags,
- ...descriptionTags,
- ...otherTags,
- } as HtmlMetaDescriptor
+ return [...titleTags, ...descriptionTags, ...baseMeta]
}
-export { cx, pageTitle, metaTags }
+export { cx, pageTitle, metaTags, baseMeta }
diff --git a/apps/web/app/root.tsx b/apps/web/app/root.tsx
index 629b433..49c8400 100644
--- a/apps/web/app/root.tsx
+++ b/apps/web/app/root.tsx
@@ -1,8 +1,7 @@
-import type { LinksFunction, MetaFunction } from '@remix-run/node'
+import type { LinksFunction } from '@remix-run/node'
import {
Link,
Links,
- LiveReload,
Meta,
Outlet,
Scripts,
@@ -10,23 +9,16 @@ import {
useMatches,
} from '@remix-run/react'
import colors from 'tailwindcss/colors'
-import styles from './styles/app.css'
-import highlightStyles from 'highlight.js/styles/a11y-dark.css'
+import styles from './styles/app.css?url'
+import highlightStyles from 'highlight.js/styles/a11y-dark.css?url'
import favicon from './favicon.png'
-import social from './social.png'
import ExternalLink from './ui/external-link'
import TopBar from './ui/top-bar'
import ConfTopBar from './ui/conf/top-bar'
import { GlobalLoading } from './ui/global-loading'
+import { baseMeta } from './helpers'
-export const meta: MetaFunction = () => {
- return {
- author: 'Seasoned',
- 'og:type': 'website',
- 'og:image': social,
- 'og:site_name': 'Remix Forms',
- }
-}
+export const meta = () => baseMeta
export const links: LinksFunction = () => {
return [
@@ -40,10 +32,7 @@ export const links: LinksFunction = () => {
]
}
-export default function App() {
- const matches = useMatches()
- const conf = matches.find((match) => match.pathname === '/conf')
-
+export function Layout({ children }: { children: React.ReactNode }) {
return (
@@ -55,26 +44,36 @@ export default function App() {
-
- {!conf && (
-
-
- Check out our talk
- {' '}
- at Remix Conf!
-
- )}
-
-
-
-
+ {children}
-
)
}
+
+export default function App() {
+ const matches = useMatches()
+ const conf = matches.find((match) => match.pathname === '/conf')
+
+ return (
+ <>
+
+ {!conf && (
+
+
+ Check out our talk
+ {' '}
+ at Remix Conf!
+
+ )}
+
+
+
+
+ >
+ )
+}
diff --git a/apps/web/app/routes/index.tsx b/apps/web/app/routes/_index.tsx
similarity index 96%
rename from apps/web/app/routes/index.tsx
rename to apps/web/app/routes/_index.tsx
index 1d82079..c6a9e03 100644
--- a/apps/web/app/routes/index.tsx
+++ b/apps/web/app/routes/_index.tsx
@@ -1,9 +1,5 @@
import hljs from 'highlight.js/lib/common'
-import type {
- ActionFunction,
- LoaderFunction,
- MetaFunction,
-} from '@remix-run/node'
+import type { ActionFunction, LoaderFunction } from '@remix-run/node'
import { useLoaderData } from '@remix-run/react'
import { z } from 'zod'
import Form from '~/ui/form'
@@ -27,7 +23,7 @@ const title = 'The full-stack form library for Remix and React Router'
const description =
'E2E type-safe, with client + server validations, a11y, pending UI, and focus management'
-export const meta: MetaFunction = () => metaTags({ title, description })
+export const meta = () => metaTags({ title, description })
const code = `import { z } from 'zod'
import { makeDomainFunction } from 'domain-functions'
diff --git a/apps/web/app/routes/conf/01.tsx b/apps/web/app/routes/conf.01.tsx
similarity index 96%
rename from apps/web/app/routes/conf/01.tsx
rename to apps/web/app/routes/conf.01.tsx
index b6e7ce5..41c4b43 100644
--- a/apps/web/app/routes/conf/01.tsx
+++ b/apps/web/app/routes/conf.01.tsx
@@ -1,9 +1,5 @@
import hljs from 'highlight.js/lib/common'
-import type {
- ActionFunction,
- LoaderFunction,
- MetaFunction,
-} from '@remix-run/node'
+import type { ActionFunction, LoaderFunction } from '@remix-run/node'
import { redirect } from '@remix-run/node'
import { metaTags } from '~/helpers'
import Example from '~/ui/example'
@@ -18,7 +14,7 @@ const title = 'Quick and dirty'
const description =
"First, we'll create a barebones prototype without any validations."
-export const meta: MetaFunction = () => metaTags({ title, description })
+export const meta = () => metaTags({ title, description })
const code = `import { Form } from '@remix-run/react'
import { ActionFunction, redirect } from '@remix-run/node'
diff --git a/apps/web/app/routes/conf/02.tsx b/apps/web/app/routes/conf.02.tsx
similarity index 97%
rename from apps/web/app/routes/conf/02.tsx
rename to apps/web/app/routes/conf.02.tsx
index 8d7649c..9f19fce 100644
--- a/apps/web/app/routes/conf/02.tsx
+++ b/apps/web/app/routes/conf.02.tsx
@@ -1,9 +1,5 @@
import hljs from 'highlight.js/lib/common'
-import type {
- ActionFunction,
- LoaderFunction,
- MetaFunction,
-} from '@remix-run/node'
+import type { ActionFunction, LoaderFunction } from '@remix-run/node'
import { json, redirect } from '@remix-run/node'
import { metaTags } from '~/helpers'
import Example from '~/ui/example'
@@ -19,7 +15,7 @@ const title = 'Server validations'
const description =
"Now let's add server-side validations. To make our lives easier, we'll use zod for that. (It won't work yet ๐คซ)"
-export const meta: MetaFunction = () => metaTags({ title, description })
+export const meta = () => metaTags({ title, description })
const code = `import { Form } from '@remix-run/react'
import { ActionFunction, redirect, json } from '@remix-run/node'
diff --git a/apps/web/app/routes/conf/03.tsx b/apps/web/app/routes/conf.03.tsx
similarity index 97%
rename from apps/web/app/routes/conf/03.tsx
rename to apps/web/app/routes/conf.03.tsx
index 6a2ca0e..9121b48 100644
--- a/apps/web/app/routes/conf/03.tsx
+++ b/apps/web/app/routes/conf.03.tsx
@@ -1,9 +1,5 @@
import hljs from 'highlight.js/lib/common'
-import type {
- ActionFunction,
- LoaderFunction,
- MetaFunction,
-} from '@remix-run/node'
+import type { ActionFunction, LoaderFunction } from '@remix-run/node'
import { json, redirect } from '@remix-run/node'
import { metaTags } from '~/helpers'
import Example from '~/ui/example'
@@ -19,7 +15,7 @@ const title = 'Type coercions'
const description =
"But to make our server validations work, we'll need to coerce our FormData into the correct types. Let's use z.preprocess for that."
-export const meta: MetaFunction = () => metaTags({ title, description })
+export const meta = () => metaTags({ title, description })
const code = `import { Form } from '@remix-run/react'
import { ActionFunction, redirect, json } from '@remix-run/node'
diff --git a/apps/web/app/routes/conf/04.tsx b/apps/web/app/routes/conf.04.tsx
similarity index 98%
rename from apps/web/app/routes/conf/04.tsx
rename to apps/web/app/routes/conf.04.tsx
index 036beaa..117787a 100644
--- a/apps/web/app/routes/conf/04.tsx
+++ b/apps/web/app/routes/conf.04.tsx
@@ -1,9 +1,5 @@
import hljs from 'highlight.js/lib/common'
-import type {
- ActionFunction,
- LoaderFunction,
- MetaFunction,
-} from '@remix-run/node'
+import type { ActionFunction, LoaderFunction } from '@remix-run/node'
import { json, redirect } from '@remix-run/node'
import { metaTags } from '~/helpers'
import Example from '~/ui/example'
@@ -21,7 +17,7 @@ const title = 'Client validations'
const description =
"Now let's add client-side validations. We'll use the amazing react-hook-form for that."
-export const meta: MetaFunction = () => metaTags({ title, description })
+export const meta = () => metaTags({ title, description })
const code = `import { Form } from '@remix-run/react'
import { ActionFunction, redirect, json } from '@remix-run/node'
diff --git a/apps/web/app/routes/conf/05.tsx b/apps/web/app/routes/conf.05.tsx
similarity index 95%
rename from apps/web/app/routes/conf/05.tsx
rename to apps/web/app/routes/conf.05.tsx
index 42550ce..685f6a1 100644
--- a/apps/web/app/routes/conf/05.tsx
+++ b/apps/web/app/routes/conf.05.tsx
@@ -1,9 +1,5 @@
import hljs from 'highlight.js/lib/common'
-import type {
- ActionFunction,
- LoaderFunction,
- MetaFunction,
-} from '@remix-run/node'
+import type { ActionFunction, LoaderFunction } from '@remix-run/node'
import { json, redirect } from '@remix-run/node'
import { metaTags } from '~/helpers'
import Example from '~/ui/example'
@@ -12,7 +8,7 @@ import Label from '~/ui/conf/label'
import Button from '~/ui/submit-button'
import Select from '~/ui/select'
import TextArea from '~/ui/text-area'
-import { Form, useActionData, useSubmit, useTransition } from '@remix-run/react'
+import { Form, useActionData, useSubmit, useNavigation } from '@remix-run/react'
import { z } from 'zod'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
@@ -21,7 +17,7 @@ const title = 'Pending UI'
const description =
"Now let's change the text of the submit button and disable it while submitting."
-export const meta: MetaFunction = () => metaTags({ title, description })
+export const meta = () => metaTags({ title, description })
const code = `import { Form } from '@remix-run/react'
import { ActionFunction, redirect, json } from '@remix-run/node'
@@ -97,8 +93,8 @@ export default function Component() {
const { register, handleSubmit, formState } = useForm({ resolver })
const { errors } = formState
const submit = useSubmit()
- const transition = useTransition()
- const submitting = Boolean(transition.submission)
+ const navigation = useNavigation()
+ const submitting = navigation.state === 'submitting'
return (