diff --git a/examples/next-forms/.eslintrc.json b/examples/next-forms/.eslintrc.json new file mode 100644 index 0000000000000..bffb357a71225 --- /dev/null +++ b/examples/next-forms/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "next/core-web-vitals" +} diff --git a/examples/next-forms/.gitignore b/examples/next-forms/.gitignore new file mode 100644 index 0000000000000..1437c53f70bc2 --- /dev/null +++ b/examples/next-forms/.gitignore @@ -0,0 +1,34 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# vercel +.vercel diff --git a/examples/next-forms/README.md b/examples/next-forms/README.md new file mode 100644 index 0000000000000..2d04b96993d80 --- /dev/null +++ b/examples/next-forms/README.md @@ -0,0 +1,27 @@ +# Building Web Forms with Next.js Example + +This example shows how you can build forms with Next.js. + +## Preview + +Preview the example live on [StackBlitz](http://stackblitz.com/): + +[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/vercel/next.js/tree/canary/examples/next-forms) + +## Deploy your own + +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https://github.com/vercel/next.js/tree/canary/examples/next-forms&project-name=next-forms&repository-name=next-forms) + +## How to use + +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +npx create-next-app --example next-forms next-forms-app +# or +yarn create next-app --example next-forms next-forms-app +``` + +Deploy it to the cloud with [Vercel](https://vercel.com/new?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)). diff --git a/examples/next-forms/next.config.js b/examples/next-forms/next.config.js new file mode 100644 index 0000000000000..0d6071006ab35 --- /dev/null +++ b/examples/next-forms/next.config.js @@ -0,0 +1,3 @@ +module.exports = { + reactStrictMode: true, +} diff --git a/examples/next-forms/package.json b/examples/next-forms/package.json new file mode 100644 index 0000000000000..9c52e1770b0ff --- /dev/null +++ b/examples/next-forms/package.json @@ -0,0 +1,18 @@ +{ + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "latest", + "react": "17.0.2", + "react-dom": "17.0.2" + }, + "devDependencies": { + "eslint": "8.4.1", + "eslint-config-next": "latest" + } +} diff --git a/examples/next-forms/pages/_app.js b/examples/next-forms/pages/_app.js new file mode 100644 index 0000000000000..1e1cec92425c8 --- /dev/null +++ b/examples/next-forms/pages/_app.js @@ -0,0 +1,7 @@ +import '../styles/globals.css' + +function MyApp({ Component, pageProps }) { + return +} + +export default MyApp diff --git a/examples/next-forms/pages/api/form.js b/examples/next-forms/pages/api/form.js new file mode 100644 index 0000000000000..d3afc36812996 --- /dev/null +++ b/examples/next-forms/pages/api/form.js @@ -0,0 +1,12 @@ +export default function handler(req, res) { + const body = req.body + console.log('body: ', body) + + // Both of these are required. + if (!body.first || !body.last) { + return res.json({ data: 'First or last name not found' }) + } + + // Found the name. + res.json({ data: `${body.first} ${body.last}` }) +} diff --git a/examples/next-forms/pages/index.js b/examples/next-forms/pages/index.js new file mode 100644 index 0000000000000..485abd8cd3647 --- /dev/null +++ b/examples/next-forms/pages/index.js @@ -0,0 +1,53 @@ +import Head from 'next/head' +import Image from 'next/image' +import Link from 'next/link' +import styles from '../styles/Home.module.css' + +export default function Home() { + return ( +
+ + Next.js forms + + + + +
+

+ Forms with Next.js! +

+ +

+ Get started by looking at{' '} + pages/js-form.js and{' '} + pages/no-js-form.js +

+ +
+ + +

Form with JavaScript →

+

Learn to handle forms with JavaScript in Next.js.

+
+ + + + +

Form without JavaScript →

+

Learn to handle forms without JavaScript in Next.js.

+
+ +
+
+ + +
+ ) +} diff --git a/examples/next-forms/pages/js-form.js b/examples/next-forms/pages/js-form.js new file mode 100644 index 0000000000000..3dd90a9a59a08 --- /dev/null +++ b/examples/next-forms/pages/js-form.js @@ -0,0 +1,61 @@ +import Link from 'next/link' +import styles from '../styles/Home.module.css' + +export default function PageWithJSbasedForm() { + // Handle the submit event on form submit. + const handleSubmit = async (event) => { + // Stop the form from submitting and refreshing the page. + event.preventDefault() + + // Get data from the form. + const data = { + first: event.target.first.value, + last: event.target.last.value, + } + + const JSONdata = JSON.stringify(data) + + // Send the form data to our API and get a response. + const response = await fetch('/api/form', { + // Body of the request is the JSON data we created above. + body: JSONdata, + + // Tell the server we're sending JSON. + headers: { + 'Content-Type': 'application/json', + }, + // The method is POST because we are sending data. + method: 'POST', + }) + + // Get the response data from server as JSON. + // If server returns the name submitted, that means the form works. + const result = await response.json() + alert(`Is this your full name: ${result.data}`) + } + return ( +
+

+ Form{' '} + + with + {' '} + JavaScript. +

+ +

+ Get started by looking at{' '} + pages/js-from.js +

+ +
+ + + + + + +
+
+ ) +} diff --git a/examples/next-forms/pages/no-js-form.js b/examples/next-forms/pages/no-js-form.js new file mode 100644 index 0000000000000..8a5a8f6fb62d4 --- /dev/null +++ b/examples/next-forms/pages/no-js-form.js @@ -0,0 +1,33 @@ +import Link from 'next/link' +import styles from '../styles/Home.module.css' + +export default function Form() { + return ( +
+

+ Form{' '} + + without + {' '} + JavaScript. +

+

+ Get started by looking at{' '} + pages/no-js-from.js +

+ + {/*action: The action attribute defines where the data gets sent. Its value must be a valid relative or absolute URL. If this attribute isn't provided, the data will be sent to the URL of the page containing the form — the current page. + method: The HTTP method to submit the form with. (case insensitive) s*/} + +
+ + + + + + + +
+
+ ) +} diff --git a/examples/next-forms/public/favicon.ico b/examples/next-forms/public/favicon.ico new file mode 100644 index 0000000000000..718d6fea4835e Binary files /dev/null and b/examples/next-forms/public/favicon.ico differ diff --git a/examples/next-forms/public/vercel.svg b/examples/next-forms/public/vercel.svg new file mode 100644 index 0000000000000..fbf0e25a651c2 --- /dev/null +++ b/examples/next-forms/public/vercel.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/examples/next-forms/styles/Home.module.css b/examples/next-forms/styles/Home.module.css new file mode 100644 index 0000000000000..35454bb748190 --- /dev/null +++ b/examples/next-forms/styles/Home.module.css @@ -0,0 +1,121 @@ +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100vh; +} + +.main { + padding: 5rem 0; + flex: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} + +.footer { + width: 100%; + height: 100px; + border-top: 1px solid #eaeaea; + display: flex; + justify-content: center; + align-items: center; +} + +.footer a { + display: flex; + justify-content: center; + align-items: center; + flex-grow: 1; +} + +.title a { + color: #0070f3; + text-decoration: none; +} + +.title a:hover, +.title a:focus, +.title a:active { + text-decoration: underline; +} + +.title { + margin: 0; + line-height: 1.15; + font-size: 4rem; +} + +.title, +.description { + text-align: center; +} + +.description { + line-height: 1.5; + font-size: 1.5rem; +} + +.code { + background: #fafafa; + border-radius: 5px; + padding: 0.75rem; + font-size: 1.1rem; + font-family: Menlo, Monaco, Lucida Console, Liberation Mono, DejaVu Sans Mono, + Bitstream Vera Sans Mono, Courier New, monospace; +} + +.grid { + display: flex; + align-items: center; + justify-content: center; + flex-wrap: wrap; + max-width: 800px; + margin-top: 3rem; +} + +.card { + margin: 1rem; + padding: 1.5rem; + text-align: left; + color: inherit; + text-decoration: none; + border: 1px solid #eaeaea; + border-radius: 10px; + transition: color 0.15s ease, border-color 0.15s ease; + width: 45%; +} + +.card:hover, +.card:focus, +.card:active { + color: #0070f3; + border-color: #0070f3; +} + +.card h2 { + margin: 0 0 1rem 0; + font-size: 1.5rem; +} + +.card p { + margin: 0; + font-size: 1.25rem; + line-height: 1.5; +} + +.logo { + height: 1em; + margin-left: 0.5rem; +} + +@media (max-width: 600px) { + .grid { + width: 100%; + flex-direction: column; + } +} diff --git a/examples/next-forms/styles/globals.css b/examples/next-forms/styles/globals.css new file mode 100644 index 0000000000000..1114e3bad5dc9 --- /dev/null +++ b/examples/next-forms/styles/globals.css @@ -0,0 +1,72 @@ +html, +body { + padding: 0; + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, + Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif; +} + +a { + color: inherit; + text-decoration: none; +} + +* { + box-sizing: border-box; +} + +form { + display: table; + min-width: 350px; + margin: 0 auto; + padding: 1rem; +} + +label { + display: table; + width: 100%; + margin-bottom: 0.5rem; + color: #3b3b3b; +} + +input { + display: table; + width: 100%; + padding: 0.5rem; + border: 1px solid #ccc; + border-radius: 0.25rem; + box-sizing: border-box; + margin-bottom: 2.5rem; +} + +input:focus { + outline: none; + border-color: #0070f3; +} + +button { + display: table; + width: 100%; + padding: 0.5rem; + border: none; + border-radius: 0.25rem; + box-sizing: border-box; + margin-bottom: 2.5rem; + background-color: #0070f3; + color: #fff; + cursor: pointer; +} + +button:hover { + background-color: #0060e9; +} + +.container { + min-height: 100vh; + padding: 0 0.5rem; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: 100vh; +}