Remix React with Uncontrolled Inputs
Tags: javascript, react, webdev, remixrun
Published: 2022-05-17 12:26:16

## React Controlled Inputs

Like most React devs I use **controlled** inputs, where you supply a value and an ``onChange`` handler to each ``<input />``.

```jsx
<input
  id='name'
  value='Zelda'
  type='text'
  onChange={({target}) => changeHandler(target.value)}
/>
```

The alternative is to use **un-controlled** inputs, which I overlooked because, **controlled** inputs work just fine.  Controlled inputs perform slightly less well (each key press causes all inputs to re-render), but you'd probably need 50 inputs before you even notice!

Then I started using [Remix](https://remix.run/)...

## Remix

> [Remix](https://remix.run/) embraces HTML forms.

``<input />`` tags inside forms don't need event handlers or fancy state management:

```html
<form>
  <input id="name" type="text" />
  <button type="submit">
    Submit Form
  </button>
</form>
```

The **HTML form** posts the input values back to the server.

## Server Postback, 100% Free!

[Remix](https://remix.run/) provides the ``Form`` component, from the ``@remix-run/react`` namespace, which builds on a standard HTML ``form`` to provide extra functionality, such as, automatically hooking up to a server side function:

```jsx
import { Form } from "@remix-run/react";

export const action = async ({ request }) => {
  const formData = await request.formData();
  const name = formData.get("name");
  //TODO: Save to Database
}

export default function MyForm() {
  return (
    <Form method="post">
      <input id="name" type="text" />
      <button type="submit">
        Submit Form
      </button>
    </Form>
  )
}
```

> It's not a meme, with [Remix](https://remix.run/) it really is that easy!

The ``input`` above is an **un-controlled** input.

This gives us a form for adding data, but what about edit?  If you supply a value to those input elements, React will complain:

> Warning: A component is changing an uncontrolled input of type text to be controlled.

You've probably seen this React error, when you supply an ``input`` with a value but no ``onChange`` handler!

### Wrap our ``input`` elements in a component, so we can also handle edit...

To get the simplicity and performance of **un-controlled** inputs with the convenience of **controlled** ones you can use a ``ref``.

```jsx
import React, {useEffect, useRef} from 'react'

const UncontrolledInput = ({
   id,
   label,
   value = '',
   type = 'text',
   ...rest
}) => {
    const input = useRef();

    useEffect(() => {
        input.current.value = value
    }, [value])

    return (
        <p>
            <label>
                {
                    label
                }
                <input
                    ref={input}
                    id={id}
                    name={id}
                    type={type}
                    {...rest}
                />
            </label>
        </p>
    )
}
```

The ``input`` value is set with the ``useEffect`` and ``useRef`` hooks from React; and Remix provides ``Form`` to handle the server post-back:

```jsx
<Form method="post">
  <UncontrolledInput
      id='name'
      label='Name'
      value={'Zelda'}
  />
</Form>
```

We can now set values in our ``input`` elements and post that back to the server without event handlers or state management.  Next, we only need to load the data from the server.

## Full Server Round Trip, also 100% Free!

Let's complete the picture with Remix:

```jsx
import { Form, useLoaderData } from "@remix-run/react";

export const loader = async () => {
  //TODO: Load name from Database...
  return json({ name: 'Zelda' });
};

export const action = async ({ request }) => {
  const formData = await request.formData();
  const name = formData.get("name");
  //TODO: Save to Database
}

export default function MyForm() {
  const { name } = useLoaderData();

  return (
    <Form method="post">
      <UncontrolledInput
          id='name'
          label='Name'
          value={name}
      />
      <button type="submit">
        Submit Form
      </button>
    </Form>
  )
}
```

That's the easiest full-stack I've ever seen!

### What about form validation?

Since we're "using the platform", remember "event bubbling"?

DOM events, like ``onChange``, bubble up the DOM tree, hitting each parent node, until they reach the ``Body`` tag or an event handler cancels that event.

### Event Bubbling in React

Here's a simple React component to demonstrate. The first button triggers both the button.onClick and the form.onClick. The second button only triggers its own onClick handler.

```jsx
const MultiEventHandler = () => (
    <form
        onClick={() => console.log('Form click handler')}
    >
        <button
            onClick={() => console.log('Button click handler')}
        >
            Fire Both Event Handlers!
        </button>
        <button
            onClick={(e) => {
                console.log('Button click handler');

                e.stopPropagation()
            }}
        >
            Fire My Event Handler
        </button>
    </form>
)
```

> Taken to an extreme, you could have single event handlers on the body tag, to handle all events, such as, onchange and onclick (don't try)

This Remix example uses a single onChange handler on the ``Form`` tag to handle all events for any nested ``input`` controls:

```jsx
<Form method="post"
    onChange={(e) => {
        const {id, name, value} = e.target;

        // Perform validation here!

        e.stopPropagation()
    }}
>
    <UncontrolledInput
        id='name'
        label='Name'
        value={name}
    />
    <UncontrolledInput
        id='jobTitle'
        label='Job Title'
        value={jobTitle}
    />
    <button type="submit">
        Submit Form
    </button>
</Form>
```

The ``onChange`` event from each nested ``input`` bubbles up to the ``Form`` where it is "captured" by the event handler.  By default, after running the code inside our event handler, the event would continue bubbling up the DOM tree, triggering any event handlers it encounters along the way, but we call ``stopPropagation()`` to prevent the event from bubbling up any further.