Contents
data/authors/Paul Logan.json

Adding a React Custom Hook

This is the third post in the series.

React.js has been successfully added to out Asp.Net MVC web app in part 1. I created a React driven user listing and maintenance page on part 2.

In this post, I will create a Custom Hook to remove the need to use the same piece of code in each React web page.

Copy Paste Programming, DRY and Technical Debt

As more and more pages are added to the project, I found that certain blocks of code are repeated in each. This is especially true of the data-binding code, which is a copy-and-paste action onto every new page that needs to use controlled components for data entry and modification.

Copy Paste Programming violates the DRY Principle (Don’t Repeat Yourself) and should be considered for refactoring taking into account the Techical Debt introduced with each repitition of the code.

Let’s add a new product listing and editing page to our web app, which will be very similar to our user pages.

The product listing page
The product listing page
The product editing page
The product editing page

And a snippet of the React javascript that wires up the Product component:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function Product(props) {
    const [values, setValues] = React.useState(props.product);

    const handleInputChange = (e) => {
        setValues(prev => ({ ...prev, [e.target.name]: e.target.value }));
    };

    const handleCheckBoxChange = (e) => {
        setValues(prev => ({ ...prev, [e.target.name]: e.target.checked }));
    };

    const handleSubmit = (event) => {
        if (event) {
            event.preventDefault();
            $.ajax({
                url: '/Home/UpdateProduct',
                data: { editedProduct: values },
                type: 'POST',
                success: function () {
                    const originalProduct = products.find(prd => prd.Code === props.product.Code);
                    originalProduct.Code = values.Code;
                    originalProduct.Description = values.Description;
                    originalProduct.IsAvailable = values.IsAvailable;
                    ReactDOM.render(<ProductSummary products={products} />, document.getElementById('productRecords'));
                    $('#ProductsListContainer').show();
                    $('#EditRecord').hide();
                },
                successFalse: function (data) {
                    alert("Error updating Product");
                }
            });
        }
    };

    return (
        <form id="ProductForm" onSubmit={handleSubmit} >
            <div className="maintSection">
                <label>Code</label><input type="text" name="Code" className="form-control" value={values.Code} onChange={handleInputChange} required />
                <label>Description</label><input type="text" name="Description" className="form-control" value={values.Description} onChange={handleInputChange} required />
                <label>IsAvailable</label><input type="checkbox" name="IsAvailable" checked={values.IsAvailable} onChange={handleCheckBoxChange} />
            </div>
            <button type="submit" className="btn btn-success">Save</button>
        </form>
    );
}

Notice the handleInputChange and handleCheckBoxChange functions, that are exact copies of the same functions in the user controlled component. There is also some duplication in the handleSubmit function, preventing the default submit action and performing an Ajax call. I will need this functionality on each web page that contains a controlled component.

As the React documentation explains so simply:

When we want to share logic between two JavaScript functions, we extract it to a third function. Both components and Hooks are functions, so this works for them too!

So let’s pay off some technical debt, and refactor this common code into a custom hook.

A Custom Hook for React data-binding

My first refactoring will look at DRYing up the input change event handlers

//reactFormBinding.js
'use strict';

export const reactFormBinding = (state, callBack) => {
    const [values, setValues] = React.useState(state);

    const handleInputChange = (e) => {
        setValues(prev => ({ ...prev, [e.target.name]: e.target.value }));
    };

    const handleCheckBoxChange = (e) => {
        setValues(prev => ({ ...prev, [e.target.name]: e.target.checked }));
    };

    return {
        handleInputChange,
		handleCheckBoxChange,
        values,
    };
};

The destructuring code changes from

'use strict';

let products;

......

function Product(props) {
    const [values, setValues] = React.useState(props.product);

to:

'use strict';
import { reactFormBinding } from "./reactFormBinding.js";

let products;

......
function Product(props) {
    const { values, handleInputChange, handleCheckBoxChange } = reactFormBinding(props.product);

Notice that handleInputChange and handleCheckBoxChange only differ on the e.target property that is used to assign the value to state. The second refactoring consolidates the two event handlers into one, the target element type check determining whcih property to use:


    const handleInputChange = (e) => {
        setValues(inputs => {
            return { ...inputs, [e.target.name]: e.target.type === 'checkbox' ? e.target.checked : e.target.value };
        })
    };

    return {
        handleInputChange,
        values,
    };
	

Regards,

Paul.