Error while getting meta information about the page.

SolidJS - Forms and User Input



Forms are used to collect data from users such as email, password, name, etc. Signals and event listeners handle the form in Solid.js. Fine-grained reactivity in Solid.js helps in updating only the exact part of the DOM where data changes, which makes form handling fast. We will be using the form tag to collect the data from the user

Understanding Reactivity in Forms

Reactivity in Solid.js means that when the form data changes, only the UI elements that depend on that data are updated. The whole component does not reload. In the program, the signal stores the data, and wherever the data is used, the UI is automatically updated.

We will create a file called app.tsx and will paste the code in it, or create another file(example reactivity) and import it into the app.tsx file −

import { createSignal } from "solid-js";

function BasicExample() {
  const [name, setName] = createSignal("Tutorialspoint");

  return (
    <div>
      <h2>Signal Example</h2>
      <p>Current Name: {name()}</p>
    </div>
  );
}

export default BasicExample;

The output for the above code is −

reactivity

Form Tag

The <form> tag is an HTML element used to wrap input fields and buttons into a single unit.

Common CSS for All the Codes

The CSS is common for all the forms given below. Paste the code in app.css

input,
textarea {
  width: 60%;
  margin-bottom: 6px;
  padding: 6px;
  border: 1px solid #999;
  font-size: 14px;
}

button {
  padding: 6px 10px;
  margin-right: 6px;
  border: 1px solid #666;
  background: #f2f2f2;
  cursor: pointer;
}

button:hover {
  background: #e0e0e0;
}

label {
  display: block;
  margin-bottom: 6px;
}

.error-message {
  color: red;
  font-size: 12px;
  margin-bottom: 8px;
  display: block;
}

If you create a CSS file with a different name, make sure to import that file name correctly in the component −

import './app.css'

Form Submission

Form submission is used to submit a form using signals. It prevents the page from reloading and handles the data using code. Paste the code in app.tsx or create FormSubmission.tsx and import the CSS.

import { createSignal } from 'solid-js'
import './app.css'

type FormState = {
  name: string
  email: string
  message: string
}

export default function FormSubmission() {
  const initialState: FormState = {
    name: '',
    email: '',
    message: '',
  }

  const [form, setForm] = createSignal<FormState>({ ...initialState })

  const updateField =
    (field: keyof FormState) =>
    (
      e: InputEvent & {
        currentTarget: HTMLInputElement | HTMLTextAreaElement
      }
    ) => {
      setForm({ ...form(), [field]: e.currentTarget.value })
    }

  const handleSubmit = (e: Event) => {
    e.preventDefault()
    alert('Form submitted successfully!')
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Submission Form</h2>

      <input
        type="text"
        placeholder="Enter your name"
        value={form().name}
        onInput={updateField('name')}
        required
      />

      <input
        type="email"
        placeholder="Enter your email"
        value={form().email}
        onInput={updateField('email')}
        required
      />

      <textarea
        placeholder="Enter your message"
        value={form().message}
        onInput={updateField('message')}
        rows={4}
        required
      />

      <div>
        <button type="submit">Submit</button>
      </div>
    </form>
  )
}

The output for the above code will be −

submission

Form Reset

Form reset removes all input fields and sets them back to empty values. When we want to start again, or after submitting the form. Paste the code in app.tsx or create ResettableForm.tsx and import the CSS.

import { createSignal } from 'solid-js'
import './app.css'

type FormState = {
  name: string
  email: string
  message: string
}

export default function ResettableForm() {
  const initialState: FormState = {
    name: '',
    email: '',
    message: '',
  }

  const [form, setForm] = createSignal<FormState>({ ...initialState })

  const updateField =
    (field: keyof FormState) =>
    (
      e: InputEvent & {
        currentTarget: HTMLInputElement | HTMLTextAreaElement
      }
    ) => {
      setForm({ ...form(), [field]: e.currentTarget.value })
    }

  const handleReset = () => setForm({ ...initialState })

  const handleSubmit = (e: Event) => {
    e.preventDefault()
    alert('Form submitted successfully!')
    handleReset()
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Reset Form</h2>

      <input
        type="text"
        placeholder="Enter your name"
        value={form().name}
        onInput={updateField('name')}
        required
      />

      <input
        type="email"
        placeholder="Enter your email"
        value={form().email}
        onInput={updateField('email')}
        required
      />

      <textarea
        placeholder="Enter your message"
        value={form().message}
        onInput={updateField('message')}
        rows={4}
        required
      />

      <div>
        <button type="submit">Submit</button>
        <button type="button" class="reset" onClick={handleReset}>
          Reset
        </button>
      </div>
    </form>
  )
}

The output for the above code will be −

Reset Form

Basic Login

Validation is used to make sure that the user enters the correct and required data before submitting the form. Paste the code in app.tsx or create BasicValidation.tsx and import the CSS.

import { createSignal, Show } from 'solid-js'
import './app.css'

type FormState = {
  email: string
  password: string
}

type ErrorState = {
  email?: string
  password?: string
}

export default function BasicValidation() {
  const [form, setForm] = createSignal<FormState>({
    email: '',
    password: '',
  })

  const [errors, setErrors] = createSignal<ErrorState>({})

  const updateField =
    (field: keyof FormState) =>
    (
      e: InputEvent & {
        currentTarget: HTMLInputElement
      }
    ) => {
      setForm({ ...form(), [field]: e.currentTarget.value })
    }

  const validate = () => {
    const newErrors: ErrorState = {}

    if (!form().email) {
      newErrors.email = 'Email is required'
    } else if (!/\S+@\S+\.\S+/.test(form().email)) {
      newErrors.email = 'Email is invalid'
    }

    if (!form().password) {
      newErrors.password = 'Password is required'
    } else if (form().password.length < 8) {
      newErrors.password = 'Password must be at least 8 characters'
    }

    setErrors(newErrors)
    return Object.keys(newErrors).length === 0
  }

  const handleSubmit = (e: Event) => {
    e.preventDefault()
    if (validate()) {
      alert('Form submitted successfully!')
      console.log('Valid form:', form())
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Login</h2>

      <label>
        Email:
        <input
          type="email"
          value={form().email}
          onInput={updateField('email')}
        />
      </label>

      <Show when={errors().email}>
        <span class="error-message">{errors().email}</span>
      </Show>

      <label>
        Password:
        <input
          type="password"
          value={form().password}
          onInput={updateField('password')}
        />
      </label>

      <Show when={errors().password}>
        <span class="error-message">{errors().password}</span>
      </Show>

      <button type="submit">Login</button>
    </form>
  )
}

The output for the above code will be −

Basic Login

Store-based Form

In the store-based form, we will be using createStore to manage complex and nested form data. When the form has many fields or sections, we use a store-based form. Paste the code in app.tsx or create StoreBasedForm.tsx and import the CSS.

import { createStore } from 'solid-js/store'

type FormStore = {
  personal: {
    firstName: string
    lastName: string
    email: string
  }
  address: {
    street: string
    city: string
    country: string
  }
  preferences: {
    newsletter: boolean
    notifications: boolean
  }
}

export default function StoreBasedForm() {
  const [form, setForm] = createStore<FormStore>({
    personal: {
      firstName: '',
      lastName: '',
      email: '',
    },
    address: {
      street: '',
      city: '',
      country: '',
    },
    preferences: {
      newsletter: false,
      notifications: true,
    },
  })

  const handleSubmit = (e: Event) => {
    e.preventDefault()
    console.log('Form data:', JSON.stringify(form, null, 2))
    alert('Form submitted successfully!')
  }

  return (
    <form onSubmit={handleSubmit}>
      <h2>Store Based Form</h2>
      <fieldset>
        <legend>Personal Information</legend>

        <input
          type="text"
          placeholder="First Name"
          value={form.personal.firstName}
          onInput={e => setForm('personal', 'firstName', e.currentTarget.value)}
          required
        />

        <input
          type="text"
          placeholder="Last Name"
          value={form.personal.lastName}
          onInput={e => setForm('personal', 'lastName', e.currentTarget.value)}
          required
        />

        <input
          type="email"
          placeholder="Email"
          value={form.personal.email}
          onInput={e => setForm('personal', 'email', e.currentTarget.value)}
          required
        />
      </fieldset>

      <fieldset>
        <legend>Address</legend>

        <input
          type="text"
          placeholder="Street"
          value={form.address.street}
          onInput={e => setForm('address', 'street', e.currentTarget.value)}
          required
        />

        <input
          type="text"
          placeholder="City"
          value={form.address.city}
          onInput={e => setForm('address', 'city', e.currentTarget.value)}
          required
        />

        <input
          type="text"
          placeholder="Country"
          value={form.address.country}
          onInput={e => setForm('address', 'country', e.currentTarget.value)}
          required
        />
      </fieldset>

      <fieldset>
        <legend>Preferences</legend>

        <label>
          <input
            type="checkbox"
            checked={form.preferences.newsletter}
            onChange={e =>
              setForm('preferences', 'newsletter', e.currentTarget.checked)
            }
          />
          Subscribe to newsletter
        </label>

        <label>
          <input
            type="checkbox"
            checked={form.preferences.notifications}
            onChange={e =>
              setForm('preferences', 'notifications', e.currentTarget.checked)
            }
          />
          Enable notifications
        </label>
      </fieldset>

      <button type="submit">Submit</button>
    </form>
  )
}
Store Based

Multi-step Form

A multi-step form divides a long form into small steps. Users must complete one step before moving to the next. Paste the code in app.tsx or create MultiStepForm.tsx and import the CSS.

import { createSignal, Show, For } from 'solid-js'
import { createStore } from 'solid-js/store'

export default function MultiStepForm() {
  const [currentStep, setCurrentStep] = createSignal(0)

  const [formData, setFormData] = createStore({
    firstName: '',
    lastName: '',
    email: '',
    street: '',
    city: '',
    zipCode: '',
    country: '',
    agreed: false,
  })

  const steps = [
    {
      title: 'Personal Information',
      validate: () => formData.firstName && formData.lastName && formData.email,
    },
    {
      title: 'Address Details',
      validate: () =>
        formData.street &&
        formData.city &&
        formData.zipCode &&
        formData.country,
    },
    {
      title: 'Terms & Conditions',
      validate: () => formData.agreed,
    },
  ]

  const nextStep = () => {
    if (steps[currentStep()].validate()) {
      setCurrentStep(s => Math.min(s + 1, steps.length - 1))
    } else {
      alert('Please complete all required fields')
    }
  }

  const prevStep = () => setCurrentStep(s => Math.max(s - 1, 0))

  const handleSubmit = (e: Event) => {
    e.preventDefault()
    if (steps[currentStep()].validate()) {
      alert('Form submitted successfully!')
    }
  }

  return (
    <div>
      <h2>Multi Step Form</h2>
      <div>
        <For each={steps}>
          {(step, i) => (
            <div>
              {i() + 1}. {step.title}
            </div>
          )}
        </For>
      </div>

      <form onSubmit={handleSubmit}>
        <h3>{steps[currentStep()].title}</h3>

        {/* Step 1 */}
        <Show when={currentStep() === 0}>
          <div>
            <label>First Name</label>
            <input
              value={formData.firstName}
              onInput={e => setFormData('firstName', e.currentTarget.value)}
            />
          </div>

          <div>
            <label>Last Name</label>
            <input
              value={formData.lastName}
              onInput={e => setFormData('lastName', e.currentTarget.value)}
            />
          </div>

          <div>
            <label>Email</label>
            <input
              type="email"
              value={formData.email}
              onInput={e => setFormData('email', e.currentTarget.value)}
            />
          </div>
        </Show>

        {/* Step 2 */}
        <Show when={currentStep() === 1}>
          <div>
            <label>Street</label>
            <input
              value={formData.street}
              onInput={e => setFormData('street', e.currentTarget.value)}
            />
          </div>

          <div>
            <label>City</label>
            <input
              value={formData.city}
              onInput={e => setFormData('city', e.currentTarget.value)}
            />
          </div>

          <div>
            <label>ZIP Code</label>
            <input
              value={formData.zipCode}
              onInput={e => setFormData('zipCode', e.currentTarget.value)}
            />
          </div>

          <div>
            <label>Country</label>
            <select
              value={formData.country}
              onChange={e => setFormData('country', e.currentTarget.value)}
            >
              <option value="">Select country</option>
              <option value="us">India</option>
              <option value="uk">UK</option>
              <option value="ca">Sri Lanka</option>
            </select>
          </div>
        </Show>

        {/* Step 3 */}
        <Show when={currentStep() === 2}>
          <p>
            By submitting this form, you agree to our terms and conditions.
            <br />
            Please read all policies carefully before proceeding.
          </p>

          <div>
            <label>
              <input
                type="checkbox"
                checked={formData.agreed}
                onChange={e => setFormData('agreed', e.currentTarget.checked)}
              />
              I agree to the Terms & Conditions *
            </label>
          </div>
        </Show>

        {/* Navigation */}
        <button type="button" onClick={prevStep} disabled={currentStep() === 0}>
          Previous
        </button>

        <Show
          when={currentStep() < steps.length - 1}
          fallback={<button type="submit">Submit</button>}
        >
          <button type="button" onClick={nextStep}>
            Next
          </button>
        </Show>
      </form>
    </div>
  )
}

The output for the above program will be −

Step 1 − Getting name and email

Multi Step 1

Step 2 − Getting the address of the user

Multi Step 2

Step 3 − Telling the user terms and conditions

Multi Step 3

Working of multi-step form works from start to finish, including step navigation, data entry, and final submission −

Multi Step Form

Conclusion

In this chapter, we learned about form handling. We covered different form-handling techniques such as basic form submission, form reset, login validation, store-based forms for complex data, and multi-step forms. We also understood the role of signals, stores, and the form tag in managing user input.

Advertisements