Summoning builders…Compiling dreams…Polishing pixels…Aligning the stars to your roadmap…Brewing the backlog…Forging gold from ideas…Charting the constellations…Waking the render spirits…

    Insights/Commerce & CMS/React Forms Post To Firebase Firestore Collections

    React Forms Post To Firebase Firestore Collections

    admin
    November 05, 2019
    8 views
    Commerce & CMS
    firebase
    firestore
    forms
    nosql
    react
    tutorials
    React Forms Post To Firebase Firestore Collections

    A React form is only half finished when its inputs look right. The other half is what happens after someone clicks Submit: validating the values, writing a document, showing a useful result, and keeping the data protected from unauthorized access.

    Cloud Firestore makes that workflow possible without building a separate API server for a straightforward form. Its browser SDK can write directly to a collection, while Firebase Authentication and Firestore Security Rules control which writes are allowed.

    This tutorial uses the Firebase JavaScript SDK and React patterns available in 2019; tools and interfaces may have changed since then.

    We will build a small contact-details form that stores a name and email address in a users collection. It will use controlled React inputs, wait for Firestore to confirm the write, preserve values when a request fails, and enforce a basic document structure with security rules.

    How a React Form Writes to Firestore

    Firestore organizes data into collections and documents. A collection contains documents, and each document contains named fields. Unlike a relational table, a collection does not require a predefined schema, although your application should still enforce one.

    Our document will look like this:

    {
      name: 'Alex',
      email: 'alex@example.com',
      ownerId: 'firebase-auth-user-id',
      createdAt: /* Firestore timestamp */
    }
    

    Calling db.collection('users').add(data) creates a document with an automatically generated ID. You do not need to create the collection manually; it appears when its first document is written.

    Despite the word “post” in the title, we will not use fetch() or write an HTTP POST endpoint. The Firebase SDK handles communication with Firestore and returns a promise representing the write.

    1. Create the React App and Firebase Project

    For a new application, Create React App provides a convenient starting point:

    npx create-react-app firestore-form
    cd firestore-form
    npm install firebase@6
    npm start
    

    This example deliberately uses Firebase version 6 and its namespaced API, which was available in 2019. Do not mix these examples with instructions for later modular SDK releases.

    In the Firebase console:

    1. Create a Firebase project.
    2. Register a web application and copy its Firebase configuration.
    3. Open the database area and create a Cloud Firestore database.
    4. Choose a suitable database location and start with locked-down rules.
    5. In Authentication, enable the Anonymous sign-in provider.

    Anonymous authentication gives a visitor a Firebase user ID without requiring an email-and-password screen. That lets us associate each submission with an authenticated identity.

    It does not prove that the visitor owns the submitted email address. It also does not make an unrestricted public form resistant to spam. For account profiles, use your application's actual sign-in flow instead.

    2. Initialize Firebase Once

    Create src/firebase.js:

    import firebase from 'firebase/app';
    import 'firebase/auth';
    import 'firebase/firestore';
    
    const firebaseConfig = {
      apiKey: 'YOUR_API_KEY',
      authDomain: 'YOUR_PROJECT.firebaseapp.com',
      projectId: 'YOUR_PROJECT_ID',
      appId: 'YOUR_APP_ID'
    };
    
    if (!firebase.apps.length) {
      firebase.initializeApp(firebaseConfig);
    }
    
    const db = firebase.firestore();
    const auth = firebase.auth();
    
    // Wait for Firebase to restore any existing session before signing in.
    const authReady = new Promise((resolve, reject) => {
      const unsubscribe = auth.onAuthStateChanged(user => {
        unsubscribe();
    
        if (user) {
          resolve(user);
        } else {
          auth.signInAnonymously().then(result => {
            resolve(result.user);
          }).catch(reject);
        }
      }, reject);
    });
    
    // Mark initialization failures as handled; submit still awaits the promise.
    authReady.catch(() => {});
    
    export { db, authReady };
    export default firebase;
    

    Keeping initialization in one module prevents components from each setting up their own Firebase app. Waiting for the initial authentication state also avoids unnecessarily replacing an existing restored session with an anonymous one.

    Firebase's web configuration is shipped to the browser; it is not an administrative secret. Security comes from authentication and rules, not from hiding this configuration. Never place service-account credentials in a React application.

    You may see older examples calling db.settings({ timestampsInSnapshots: true }). That migration setting is unnecessary with Firebase version 6 and should be omitted here.

    3. Add Rules Before Connecting the Form

    Client-side validation improves usability, but someone can bypass your React code and call Firebase directly. Firestore Security Rules must therefore validate incoming writes independently.

    Publish these rules for this tutorial's users collection:

    rules_version = '2';
    service cloud.firestore {
      match /databases/{database}/documents {
        match /users/{documentId} {
          allow create: if request.auth != null
            && request.resource.data.keys().hasAll([
              'name', 'email', 'ownerId', 'createdAt'
            ])
            && request.resource.data.keys().hasOnly([
              'name', 'email', 'ownerId', 'createdAt'
            ])
            && request.resource.data.name is string
            && request.resource.data.name.size() > 0
            && request.resource.data.name.size() <= 100
            && request.resource.data.email is string
            && request.resource.data.email.size() >= 3
            && request.resource.data.email.size() <= 254
            && request.resource.data.ownerId == request.auth.uid
            && request.resource.data.createdAt == request.time;
    
          allow read, update, delete: if false;
        }
      }
    }
    

    These rules allow authenticated visitors to create documents only with the expected fields, their own user ID, and a server-generated timestamp. Reads, edits, and deletions from client applications remain blocked.

    This is intentionally a submission-only example. A write can succeed even when the client cannot read the document afterward. You can inspect submissions through the Firebase console with appropriate project permissions.

    The email checks above constrain type and length, not ownership or full address validity. If verified addresses matter, implement a verification workflow rather than relying on a regular expression.

    4. Build the Controlled React Form

    Create src/MyForm.js. Each input gets its value from component state and updates that state through onChange.

    import React from 'react';
    import firebase, { db, authReady } from './firebase';
    
    class MyForm extends React.Component {
      state = {
        name: '',
        email: '',
        isSubmitting: false,
        error: '',
        success: ''
      };
    
      submitting = false;
    
      handleChange = event => {
        const { name, value } = event.target;
        this.setState({ [name]: value });
      };
    
      handleSubmit = async event => {
        event.preventDefault();
        if (this.submitting) return;
    
        const name = this.state.name.trim();
        const email = this.state.email.trim();
    
        if (!name || !email || name.length > 100 || email.length > 254) {
          this.setState({
            error: 'Enter a name and email within the allowed lengths.',
            success: ''
          });
          return;
        }
    
        this.submitting = true;
        this.setState({ isSubmitting: true, error: '', success: '' });
    
        try {
          const user = await authReady;
    
          const document = await db.collection('users').add({
            name,
            email,
            ownerId: user.uid,
            createdAt: firebase.firestore.FieldValue.serverTimestamp()
          });
    
          this.setState({
            name: '',
            email: '',
            success: `Details saved. Reference: ${document.id}`
          });
        } catch (error) {
          console.error('Submission failed:', error.code);
          this.setState({
            error: 'We could not save your details. Please try again.'
          });
        } finally {
          this.submitting = false;
          this.setState({ isSubmitting: false });
        }
      };
    
      render() {
        const { name, email, isSubmitting, error, success } = this.state;
    
        return (
          <form onSubmit={this.handleSubmit} aria-busy={isSubmitting}>
            <fieldset disabled={isSubmitting}>
              <legend>Your contact details</legend>
    
              <div>
                <label htmlFor="contact-name">Name</label>
                <input
                  id="contact-name"
                  name="name"
                  type="text"
                  autoComplete="name"
                  value={name}
                  onChange={this.handleChange}
                  maxLength={100}
                  required
                />
              </div>
    
              <div>
                <label htmlFor="contact-email">Email</label>
                <input
                  id="contact-email"
                  name="email"
                  type="email"
                  autoComplete="email"
                  value={email}
                  onChange={this.handleChange}
                  maxLength={254}
                  required
                />
              </div>
    
              <button type="submit">
                {isSubmitting ? 'Saving…' : 'Submit'}
              </button>
            </fieldset>
    
            {error && <p role="alert">{error}</p>}
            {success && <p role="status">{success}</p>}
          </form>
        );
      }
    }
    
    export default MyForm;
    

    Render it from src/App.js:

    import React from 'react';
    import MyForm from './MyForm';
    
    export default function App() {
      return (
        <main>
          <h1>Send your details</h1>
          <MyForm />
        </main>
      );
    }
    

    Why the submission handler works this way

    preventDefault() stops the browser's normal form navigation. Without it, a page reload can interrupt your interface and discard local state.

    The input name attributes match the state keys, so one change handler can update either field. Explicit labels make the inputs easier to use with assistive technology; placeholders alone are not substitutes for labels.

    The native required and type="email" constraints catch simple mistakes before submission. Trimming values also prevents an all-whitespace name from passing our JavaScript validation.

    Most importantly, the form awaits add() before clearing its fields. Clearing immediately after starting a write would lose the visitor's input if authentication or database access failed.

    The synchronous submitting flag guards against repeated handler calls while React processes state updates. Disabling the fieldset also prevents users from changing values that a pending submission would subsequently erase.

    5. Verify Success and Failure Paths

    Test more than the happy path:

    1. Submit valid details and confirm the success message includes a document ID.
    2. Open Firestore in the console and inspect the new users document.
    3. Confirm ownerId is populated and createdAt is a timestamp.
    4. Try an empty name, a whitespace-only name, and an invalid email.
    5. Temporarily deny creates in a development project, then submit again. The error should appear without clearing the inputs.
    6. Restore the rules and test repeated clicks while saving.

    Also test a disconnected connection. Firestore can queue a write while offline, and its promise may remain pending until the backend acknowledges it. A pending request is not the same thing as a failed request. This simple form stays in its saving state; a production interface may need a separate connectivity explanation.

    Common Pitfalls and Design Decisions

    Using add() for a one-per-user profile

    Every successful add() creates another document. That suits separate submissions, but not necessarily a single account profile.

    For one profile per authenticated user, a deterministic path is often clearer:

    await db.collection('users').doc(user.uid).set({
      name,
      email,
      ownerId: user.uid,
      createdAt: firebase.firestore.FieldValue.serverTimestamp()
    });
    

    This is an alternative design, not a drop-in replacement under the example rules: a second call becomes an update, which those rules deny. Profile rules should require the document ID to match request.auth.uid and explicitly control which fields may change. Avoid resetting a creation timestamp on each edit.

    Treating button disabling as duplicate prevention

    The submission guard prevents accidental clicks within this component. It does not guarantee exactly-once processing across reloads, retries, or multiple tabs. Workflows that require that guarantee need a stable submission identifier and an intentional retry strategy.

    Leaving test rules open

    Rules that permit everyone to read and write expose submitted email addresses and allow arbitrary changes. Anonymous authentication alone is also not an abuse-control system. For a public production form, consider a backend endpoint with server-side validation, rate limiting, and appropriate anti-abuse checks.

    Collect only information you need, avoid logging submitted personal data, and decide how long records should be retained.

    Takeaway

    Posting a React form to Firestore is a small SDK call surrounded by important application decisions. Use controlled inputs, validate at both the interface and database boundary, await the write before clearing state, and choose document IDs that match your data model. Those habits turn a working demo into a much more dependable starting point.

    Conversation (0)

    No comments yet. Start the conversation.

    Join the conversation

    Create a free member account to comment.