Skip to content

Commit

Permalink
Working example for building forms with Next.js (vercel#32669)
Browse files Browse the repository at this point in the history
* feat: Forms example

* docs: deploy button

* Update examples/next-forms/package.json

Co-authored-by: Lee Robinson <me@leerob.io>

* Update examples/next-forms/package.json

Co-authored-by: Lee Robinson <me@leerob.io>

* Update examples/next-forms/package.json

Co-authored-by: Lee Robinson <me@leerob.io>

* Update examples/next-forms/pages/js-form.js

Co-authored-by: Lee Robinson <me@leerob.io>

* Update examples/next-forms/pages/js-form.js

Co-authored-by: Lee Robinson <me@leerob.io>

* Update examples/next-forms/pages/index.js

Co-authored-by: Lee Robinson <me@leerob.io>

* Update examples/next-forms/README.md

Co-authored-by: Lee Robinson <me@leerob.io>

* Update comments formatting

* Improve docs for correct examples format.

* Lint tests

Co-authored-by: Lee Robinson <me@leerob.io>
  • Loading branch information
2 people authored and natew committed Feb 16, 2022
1 parent a7f0e78 commit 49a439a
Show file tree
Hide file tree
Showing 14 changed files with 448 additions and 0 deletions.
3 changes: 3 additions & 0 deletions examples/next-forms/.eslintrc.json
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
34 changes: 34 additions & 0 deletions 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
27 changes: 27 additions & 0 deletions 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)).
3 changes: 3 additions & 0 deletions examples/next-forms/next.config.js
@@ -0,0 +1,3 @@
module.exports = {
reactStrictMode: true,
}
18 changes: 18 additions & 0 deletions 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"
}
}
7 changes: 7 additions & 0 deletions examples/next-forms/pages/_app.js
@@ -0,0 +1,7 @@
import '../styles/globals.css'

function MyApp({ Component, pageProps }) {
return <Component {...pageProps} />
}

export default MyApp
12 changes: 12 additions & 0 deletions 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}` })
}
53 changes: 53 additions & 0 deletions 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 (
<div className={styles.container}>
<Head>
<title>Next.js forms</title>
<meta name="description" content="Learn forms with Next.js" />
<link rel="icon" href="/favicon.ico" />
</Head>

<main className={styles.main}>
<h1 className={styles.title}>
Forms with <a href="https://nextjs.org">Next.js!</a>
</h1>

<p className={styles.description}>
Get started by looking at{' '}
<code className={styles.code}>pages/js-form.js</code> and{' '}
<code className={styles.code}>pages/no-js-form.js</code>
</p>

<div className={styles.grid}>
<Link href="/js-form">
<a className={styles.card}>
<h2>Form with JavaScript &rarr;</h2>
<p>Learn to handle forms with JavaScript in Next.js.</p>
</a>
</Link>

<Link href="/no-js-form">
<a className={styles.card}>
<h2>Form without JavaScript &rarr;</h2>
<p>Learn to handle forms without JavaScript in Next.js.</p>
</a>
</Link>
</div>
</main>

<footer className={styles.footer}>
<a href="https://nextjs.org" target="_blank" rel="noopener noreferrer">
Built with Next.js | Powered by{' '}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
)
}
61 changes: 61 additions & 0 deletions 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 (
<div className="container">
<h1 className={styles.title}>
Form{' '}
<Link href="/">
<a>with</a>
</Link>{' '}
JavaScript.
</h1>

<p className={styles.description}>
Get started by looking at{' '}
<code className={styles.code}>pages/js-from.js</code>
</p>

<form onSubmit={handleSubmit}>
<label htmlFor="first">First Name</label>
<input type="text" id="first" name="first" required />
<label htmlFor="last">Last Name</label>
<input type="text" id="last" name="last" required />

<button type="submit">Submit</button>
</form>
</div>
)
}
33 changes: 33 additions & 0 deletions 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 (
<div className="container">
<h1 className={styles.title}>
Form{' '}
<Link href="/">
<a>without</a>
</Link>{' '}
JavaScript.
</h1>
<p className={styles.description}>
Get started by looking at{' '}
<code className={styles.code}>pages/no-js-from.js</code>
</p>

{/*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*/}

<form action="/api/form" method="post">
<label htmlFor="first">First Name</label>
<input type="text" id="first" name="first" required />

<label htmlFor="last">Last Name</label>
<input type="text" id="last" name="last" required />

<button type="submit">Submit</button>
</form>
</div>
)
}
Binary file added examples/next-forms/public/favicon.ico
Binary file not shown.
4 changes: 4 additions & 0 deletions examples/next-forms/public/vercel.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
121 changes: 121 additions & 0 deletions 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;
}
}

0 comments on commit 49a439a

Please sign in to comment.