# React, Next.js & Vue

> Copy-paste contact forms for React, Next.js and Vue.

Source: https://docs.formclick.io/html-forms/frameworks

Formclick has no SDK to install. In a framework you either render a plain HTML form, or post JSON with `fetch` and keep the state in your component.

**React**

```jsx title="ContactForm.jsx"
import { useState } from 'react';

export default function ContactForm() {
  const [status, setStatus] = useState('idle'); // idle | sending | sent | error
  const [error, setError] = useState('');

  async function onSubmit(e) {
    e.preventDefault();
    setStatus('sending');
    const data = Object.fromEntries(new FormData(e.currentTarget));

    const res = await fetch('https://api.formclick.io/submit', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
      body: JSON.stringify({ access_key: process.env.NEXT_PUBLIC_FORMCLICK_KEY, ...data }),
    });
    const result = await res.json();

    if (result.success) {
      setStatus('sent');
      e.target.reset();
    } else {
      setStatus('error');
      setError(result.message);
    }
  }

  if (status === 'sent') return <p>Thanks! We'll be in touch soon.</p>;

  return (
    <form onSubmit={onSubmit}>
      <input name="name" placeholder="Name" required />
      <input name="email" type="email" placeholder="Email" required />
      <textarea name="message" placeholder="Message" required />
      <input type="checkbox" name="botcheck" style={{ display: 'none' }} tabIndex={-1} />
      <button disabled={status === 'sending'}>
        {status === 'sending' ? 'Sending…' : 'Send'}
      </button>
      {status === 'error' && <p role="alert">{error}</p>}
    </form>
  );
}
```

**Next.js**

```jsx title="app/contact/page.tsx"
// app/contact/page.tsx — a plain HTML form works in a Server Component
export default function ContactPage() {
  return (
    <form action="https://api.formclick.io/submit" method="POST">
      <input type="hidden" name="access_key" value={process.env.NEXT_PUBLIC_FORMCLICK_KEY} />
      <input type="hidden" name="redirect" value="https://yourwebsite.com/thanks" />
      <input name="name" required />
      <input name="email" type="email" required />
      <textarea name="message" required />
      <button type="submit">Send</button>
      {/* formclick.min.js is loaded once in app/layout.tsx (see below) */}
    </form>
  );
}
```

**Vue**

```vue title="ContactForm.vue"
<script setup>
import { ref } from 'vue';

const form = ref({ name: '', email: '', message: '' });
const status = ref('idle');

async function submit() {
  status.value = 'sending';
  const res = await fetch('https://api.formclick.io/submit', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
    body: JSON.stringify({ access_key: 'YOUR_ACCESS_KEY', ...form.value }),
  });
  const result = await res.json();
  status.value = result.success ? 'sent' : 'error';
}
</script>

<template>
  <p v-if="status === 'sent'">Thanks! Your message has been sent.</p>
  <form v-else @submit.prevent="submit">
    <input v-model="form.name" placeholder="Name" required />
    <input v-model="form.email" type="email" placeholder="Email" required />
    <textarea v-model="form.message" placeholder="Message" required />
    <button :disabled="status === 'sending'">Send</button>
  </form>
</template>
```

## Keeping the key in an environment variable

The access key is public, so an env variable is about convenience rather than secrecy: one key per environment, nothing hard-coded. Next.js exposes variables prefixed with `NEXT_PUBLIC_`; Vite uses `VITE_`.

```bash title=".env.local"
NEXT_PUBLIC_FORMCLICK_KEY=YOUR_ACCESS_KEY
```

## Using formclick.min.js in a framework

Plain HTML forms in a framework (like the Next.js example above) should load the script too. Load it once in your root layout and it enhances forms as they are rendered, including on client-side navigation.

```jsx title="app/layout.tsx"
import Script from 'next/script';

export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>
        {children}
        {/* Handles submit, file upload, loading state and success/error messages. */}
        <Script src="https://cdn.formclick.io/formclick.min.js" strategy="afterInteractive" />
      </body>
    </html>
  );
}
```
