DEV Community

Cover image for Full-Stack React & Node.js - Add a form
Dave
Dave

Posted on • Updated on

Full-Stack React & Node.js - Add a form

Add a form to the React client site

We're going to add a few components here to generate our form from our data. There are much better libraries to do this, which we will look at later, but for now we will write it ourselves.

Create all the following files under the src folder in our React project!

Create Input.js and paste in this code:

import React, { useEffect, useRef } from "react";

const Input = ({
id, value = "", type = "text", readOnly = false, required = false
}) => {
  const input = useRef(null);

  useEffect(() => {
    if (input.current) {
      const sValue = value.toString();

      if (type === 'checkbox') {
        input.current.checked = sValue === 'true';
        input.current.value = 'true'
      } else {
        input.current.value = sValue
      }
    }
  }, [type, value])

  return (
    <input
      ref={input}
      id={id}
      name={id}
      type={type}
      readOnly={readOnly}
      disabled={readOnly}
      required={required}
    />
  );
};

export default Input;
Enter fullscreen mode Exit fullscreen mode

With useEffect and useRef hooks you can be certain that your uncontrolled inputs will update when the value prop changes.

Input.js creates text inputs or checkboxes depending on the data type of the value parameter. Next we need a component to render a label with an Input.js.

React has two kinds of inputs: controlled and uncontrolled. With controlled the value is managed by React in useState. With uncontrolled the value is managed by the DOM. The difference is what the "single source of truth" is. Controlled are ideal when you have a small number of inputs. Uncontrolled perform better and, when your controls are inside a form, it's easier to have a single form event handler rather than an event handler on each input.

Create InputLabel.js, like this:

import React from "react";
import Input from "./Input";

const InputLabel = ({label, error, info, ...inputProps}) => {
    return (
        <p
            className="input-label"
        >
            <label htmlFor={inputProps.id}>
                {
                    label
                }
            </label>
            <Input
                {...inputProps}
            />
        </p>
    );
};

export default InputLabel;
Enter fullscreen mode Exit fullscreen mode

And now we make a form component with some string utility functions to turn an object into a bunch of form fields using our "Input" components.

Create Form.js:

import React from 'react';
import InputLabel from "./InputLabel";
import './form.css'

const isNullOrUndefined = prop => prop === null
    || prop === undefined;
const isEmptyString = prop => isNullOrUndefined(prop)
    || prop === '';
const capitalize = word =>
    word.charAt(0).toUpperCase() +
    word.slice(1).toLowerCase();

function titleFromName(name) {
    if (isEmptyString(name)) {
        return '';
    }

    return name.split(/(?=[A-Z])|\s/).map(s => capitalize(s)).join(' ')
}

const Form = ({entity}) => {
  return (
    <form>
      {
        Object.entries(entity).map(([entityKey, entityValue]) => {
          if (entityKey === "id") {
            return <input
              type="hidden"
              name="id"
              key="id"
              value={entityValue}
            />
          } else {
            return <InputLabel
              id={entityKey}
              key={entityKey}
              label={titleFromName(entityKey)}
              type={
                typeof entityValue === "boolean"
                  ? "checkbox"
                  : "text"
                }
                value={entityValue}
              />
            }
          })
        }
      <button
        type="submit"
      >
        Submit
      </button>
    </form>
  );
};

export default Form;

Enter fullscreen mode Exit fullscreen mode

And create form.css:

form {
    padding: 1em;
    background: #f9f9f9;
    border: 1px solid #c1c1c1;
    margin: 2rem auto 0 auto;
    max-width: 600px;
}

form button[type=submit] {
    margin-left: 159px;
}

.input-label {
    display: flex;
}

.input-label label {
    font-weight: bold;
}

.input-label input {
    margin-left: 12px;
}

@media (min-width: 400px) {
    label {
        text-align: right;
        flex: 1;
    }

    input,
    button {
        flex: 3;
    }
}
Enter fullscreen mode Exit fullscreen mode

Now change AddEditNote.js to use your Form.js component:

import React from 'react';
import Form from './Form';

const noteEntity = {
    id: 1,
    title: 'A Note',
    content: 'Lorem ipsum dolor sit amet',
    author: 'neohed',
    lang: 'en',
    isLive: true,
    category: '',
}

const AddEditNote = () => {
    return (
        <div>
            <Form
                entity={noteEntity}
            />
        </div>
    );
};

export default AddEditNote;
Enter fullscreen mode Exit fullscreen mode

To test this, inside the node-react-stack/react-client folder, run:

npm run start
Enter fullscreen mode Exit fullscreen mode

You should see an HTML form with the values from the noteEntity object.

Now, to make it easier to see what data our app is using we will make a "debug" component. Create a new file, RenderData.js, like this:

import React from 'react';
import './render-data.css'

const RenderData = ({data}) => {
    return (
        <div
            className='render-data'
        >
          <pre>
            {
                JSON.stringify(data, null, 3)
            }
          </pre>
        </div>
    );
};

export default RenderData;
Enter fullscreen mode Exit fullscreen mode

Create render-data.css:

@import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap');

.render-data > pre {
    font-family: 'Fira Code', monospace;
    font-size: 1.2em;
    padding: 8px 0 0 32px;
}
Enter fullscreen mode Exit fullscreen mode

Fira Code is a nice monospace font provided by google. Monospace fonts are ideal for displaying code or data.

And finally, edit AddEditNote.js, like this:

import React from 'react';
import RenderData from "./RenderData";
import Form from './Form';

const noteEntity = {
    id: 1,
    title: 'A Note',
    content: 'Lorem ipsum dolor sit amet',
    author: 'neohed',
    lang: 'en',
    isLive: true,
    category: '',
}

const AddEditNote = () => {
    return (
        <div>
            <RenderData
                data={noteEntity}
            />
            <Form
                entity={noteEntity}
            />
        </div>
    );
};

export default AddEditNote;
Enter fullscreen mode Exit fullscreen mode

If you run the React app now, you should see a screen like this:

App Screenshot

You could just console.log the noteEntity object, but sometimes it's easier to understand things when you use a component like this to render the object in the browser window.

Next we will create the node.js server...

Code repo: Github Repository

Discussion (0)