This site runs best with JavaScript enabled.

Simply Learn Full-Stack Web, Part 2

June 27, 2022


Simply Learn Full-Stack React & Node.js

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:

1import React, { useEffect, useRef } from "react";
2
3const Input = ({
4id, value = "", type = "text", readOnly = false, required = false
5}) => {
6 const input = useRef(null);
7
8 useEffect(() => {
9 if (input.current) {
10 const sValue = value.toString();
11
12 if (type === 'checkbox') {
13 input.current.checked = sValue === 'true';
14 input.current.value = 'true'
15 } else {
16 input.current.value = sValue
17 }
18 }
19 }, [type, value])
20
21 return (
22 <input
23 ref={input}
24 id={id}
25 name={id}
26 type={type}
27 readOnly={readOnly}
28 disabled={readOnly}
29 required={required}
30 />
31 );
32};
33
34export default Input;

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:

1import React from "react";
2import Input from "./Input";
3
4const InputLabel = ({label, error, info, ...inputProps}) => {
5 return (
6 <p
7 className="input-label"
8 >
9 <label htmlFor={inputProps.id}>
10 {
11 label
12 }
13 </label>
14 <Input
15 {...inputProps}
16 />
17 </p>
18 );
19};
20
21export default InputLabel;

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:

1import React from 'react';
2import InputLabel from "./InputLabel";
3import './form.css'
4
5const isNullOrUndefined = prop => prop === null
6 || prop === undefined;
7const isEmptyString = prop => isNullOrUndefined(prop)
8 || prop === '';
9const capitalize = word =>
10 word.charAt(0).toUpperCase() +
11 word.slice(1).toLowerCase();
12
13function titleFromName(name) {
14 if (isEmptyString(name)) {
15 return '';
16 }
17
18 return name.split(/(?=[A-Z])|\s/).map(s => capitalize(s)).join(' ')
19}
20
21const Form = ({entity}) => {
22 return (
23 <form>
24 {
25 Object.entries(entity).map(([entityKey, entityValue]) => {
26 if (entityKey === "id") {
27 return <input
28 type="hidden"
29 name="id"
30 key="id"
31 value={entityValue}
32 />
33 } else {
34 return <InputLabel
35 id={entityKey}
36 key={entityKey}
37 label={titleFromName(entityKey)}
38 type={
39 typeof entityValue === "boolean"
40 ? "checkbox"
41 : "text"
42 }
43 value={entityValue}
44 />
45 }
46 })
47 }
48 <button
49 type="submit"
50 >
51 Submit
52 </button>
53 </form>
54 );
55};
56
57export default Form;

And create form.css:

1form {
2 padding: 1em;
3 background: #f9f9f9;
4 border: 1px solid #c1c1c1;
5 margin: 2rem auto 0 auto;
6 max-width: 600px;
7}
8
9form button[type=submit] {
10 margin-left: 159px;
11}
12
13.input-label {
14 display: flex;
15}
16
17.input-label label {
18 font-weight: bold;
19}
20
21.input-label input {
22 margin-left: 12px;
23}
24
25@media (min-width: 400px) {
26 label {
27 text-align: right;
28 flex: 1;
29 }
30
31 input,
32 button {
33 flex: 3;
34 }
35}

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

1import React from 'react';
2import Form from './Form';
3
4const noteEntity = {
5 id: 1,
6 title: 'A Note',
7 content: 'Lorem ipsum dolor sit amet',
8 author: 'neohed',
9 lang: 'en',
10 isLive: true,
11 category: '',
12}
13
14const AddEditNote = () => {
15 return (
16 <div>
17 <Form
18 entity={noteEntity}
19 />
20 </div>
21 );
22};
23
24export default AddEditNote;

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

npm run start

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:

1import React from 'react';
2import './render-data.css'
3
4const RenderData = ({data}) => {
5 return (
6 <div
7 className='render-data'
8 >
9 <pre>
10 {
11 JSON.stringify(data, null, 3)
12 }
13 </pre>
14 </div>
15 );
16};
17
18export default RenderData;

Create render-data.css:

1@import url('https://fonts.googleapis.com/css2?family=Fira+Code&display=swap');
2
3.render-data > pre {
4 font-family: 'Fira Code', monospace;
5 font-size: 1.2em;
6 padding: 8px 0 0 32px;
7}

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:

1import React from 'react';
2import RenderData from "./RenderData";
3import Form from './Form';
4
5const noteEntity = {
6 id: 1,
7 title: 'A Note',
8 content: 'Lorem ipsum dolor sit amet',
9 author: 'neohed',
10 lang: 'en',
11 isLive: true,
12 category: '',
13}
14
15const AddEditNote = () => {
16 return (
17 <div>
18 <RenderData
19 data={noteEntity}
20 />
21 <Form
22 entity={noteEntity}
23 />
24 </div>
25 );
26};
27
28export default AddEditNote;

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

Share article



neohed © 2022