GitHub

Core Concepts

Forms

Forms are an essential part of any application.


Introduction

By default, Radonis applications work like a traditional monolythic application: Forms are submitted and trigger a page reload, no JavaScript required. With the help of client-side hydration and flash messages, this "old school" way of handling user input comes really close to the modern UX known from Single Page Applications. But there are cases, being some small interaction like a "Add to favorites" button or a form inside a modal, where communicating with the backend via fetch delivers a better UX for the user. Radonis ships with a form component that can do both, and switching between submit and fetch is as simple as adding the noReload prop to the form component.


The different ways of using forms

The following chapter demonstrates the two ways in which the <Form> component can be used to submit user input to the AdonisJS backend.

Via fetch

The following snippet shows how a form is created that submits data via fetch.

import { Form } from '@microeinhundert/radonis'


type Data = {
  title: string
  description: string
}


type Error = { errors: { field: keyof Data, message: string, rule: string }[] }


// Helper for extracting the error message for a specific field
function extractFieldErrorMessage(field: keyof Data, { errors }: Error): string | undefined {
  return errors.find((error) => error.field === field)?.message
}


function FetchFormDemo() {
  return (
    <Form<Data, Error>
      action$="YourController.store"
      method="post" // or `get`, `put`, `delete`, `patch`
      params={{ someParam: 'hello' }}
      queryParams={{ someQueryParam: 'world' }}
      noReload // Use fetch instead of reloading the page
      hooks={{
        onMutate: ({ input }) => {
          // Do something before the mutation runs
          return () => {
            // Rollback changes if the mutation failed
          }
        },
        onSuccess: ({ data, input }) => {
          // Do something once the mutation succeeded
        },
        onFailure: ({ error, rollback, input }) => {
          // Do something once the mutation failed,
          // like executing the rollback
          rollback?.()
        },
        onSettled: ({ status, error, data, rollback, input }) => {
          switch (status) {
            case 'success': {
              // Do something if the mutation succeeded
            }
            case 'failure': {
              // Do something if the mutation failed
            }
          }
        },
      }}
    >
      {({ status, error }) => {
        const isSubmitting = status === 'loading'


        const titleErrorMessage = extractFieldErrorMessage('title', error)
        const descriptionErrorMessage = extractFieldErrorMessage('description', error)


        return (
          <>
            <label>
              <span>Title</span>
              <input name="title" type="text" required />
              {titleErrorMessage && <span>{titleErrorMessage}</span>}
            </label>
            <label>
              <span>Description</span>
              <textarea name="description" required />
              {descriptionErrorMessage && <span>{descriptionErrorMessage}</span>}
            </label>
            <button type="submit" disabled={isSubmitting}>
              {isSubmitting ? 'Submitting...' : 'Submit'}
            </button>
          </>
        )
      }}
    </Form>
  )
}

Via submission

The following snippet shows how a form is created that submits the form traditionally.

import { useFlashMessages, Form } from '@microeinhundert/radonis'


function TraditionalFormDemo() {
  const flashMessages = useFlashMessages()


  return (
    <Form
      method="post" // or `get`, `put`, `delete`, `patch`
      action$="YourController.store"
      params={{ someParam: 'hello' }}
      queryParams={{ someQueryParam: 'world' }}
    >
      <label>
        <span>Title</span>
        <input name="title" type="text" required />
        {flashMessages.has$('errors.title') && <span>{flashMessages.get$('errors.title')}</span>}
      </label>
      <label>
        <span>Description</span>
        <textarea name="description" required />
        {flashMessages.has$('errors.description') && <span>{flashMessages.get$('errors.description')}</span>}
      </label>
      <button type="submit">Submit</button>
    </Form>
  )
}
Previous
The Compiler