Languages
[Edit]
EN

React - optimal way to create form (uncontrolled components)

6 points
Created by:
Dirask Community
4140

In this article, we would like to show you an optimal way to create a form in React.

The approach used in the below example is useful when we want to create a form with a big amount of fields avoiding adding change handlers or references to each one field separately.

The main idea of the below approach is to use object property paths as field names that are used to generate data object on form submit event or to bind fields for indicated form data.

The concept of the below article is to provide Form component that binds/gets field values automatically what looks following way when we use it (all useful logic is composed inside Form component):

const handleSubmit = data => {
    // some code here...
};
return (
  <div>
    {/*  Form component definition in the below code  */}
    {/*                                               */}
    <Form data={data} onSubmit={handleSubmit}>
      <input type="text" name="user.username" />
      <input type="password" name="user.password" />
      <input type="text" name="report.title" />
      <input type="text" name="report.goal" />
      <button type="submit">Submit</button>  
    </Form>
  </div>
);

Notes:

  • go to this article to read more about setProperty function (setObjectProperty),
  • go to this article to read┬ámore about getProperty function (getObjectProperty).

Practical example:

// ONLINE-RUNNER:browser;

// Note: Uncomment import lines during working with JSX Compiler.
// import React from 'react';
// import ReactDOM from 'react-dom';
  
const getProperty = (object, path) => {
  	if (path === undefined || path === null) {
    	return object;
    }
  	const parts = path.split('.');
	for (let i = 0; i < parts.length; ++i) {
      	if (object === undefined || object === null) {
        	return undefined;
        }
      	const key = parts[i];
    	object = object[key];
    }
  	return object;
};

const setProperty = (data, path, value) => {
    const parts = path.split('.');
    const limit = parts.length - 1;
    for (let i = 0; i < limit; ++i) {
        const key = parts[i];
        data = data[key] ?? (data[key] = {});
    }
    const key = parts[limit];
    data[key] = value;
};
  
const iterateElements = (form, callback) => {
    const elements = form.elements;
    for (let i = 0; i < elements.length; ++i) {
        const element = elements[i];
        const tag = element.tagName;
        if (tag === 'INPUT' || tag === 'SELECT' || tag === 'TEXTAREA') {
            callback(element);
        }
    }
};

const getData = (form) => {
  	const data = {};
  	iterateElements(form, element => {
    	setProperty(data, element.name, element.value);
    });
  	return data;
};

const bindData = (form, data) => {
  	iterateElements(form, element => {
        const value = getProperty(data, element.name);
        element.value = String(value ?? '');
    });
};

const Form = React.forwardRef(({data, children, onSubmit}, ref) => {
    const formRef = ref ?? React.useRef();
    React.useEffect(() => {
		const form = formRef.current;
      	if (form) {
          	bindData(form, data ?? {});
        }
    }, [data]);
    const handleSubmit = e => {
        e.preventDefault();
      	if (onSubmit) {
      		onSubmit(getData(formRef.current));
        }
    };
    return (
      <form ref={formRef} onSubmit={handleSubmit}>
        {children}
      </form>
    );
});

// Usage example:
  
const areaStyle = { border: '1px solid gray' };
  
const App = () => {
  	const data = {
        user: {
            username: 'john',
            password: 'Secret$$'
        },
      	report: {
        	title: 'My raport title',
          	goal: 'My raport goal'
        }
    };
    const handleSubmit = data => {
        const json = JSON.stringify(data, null, 4);
        console.clear();
        console.log(json);
    };
    return (
      <div>
        <Form data={data} onSubmit={handleSubmit}>
          <div style={areaStyle}>
            <div>User:</div>
            <div>
              <label>Username: </label>
              <input type="text" name="user.username" />
            </div>
            <div>
              <label>Password: </label>
              <input type="password" name="user.password" />
            </div>
          </div>
          <div style={areaStyle}>
            <div>Report:</div>
            <div>
              <label>Title: </label>
              <input type="text" name="report.title" />
            </div>
            <div>
              <label>Goal: </label>
              <input type="text" name="report.goal" />
            </div>
          </div>
          <button type="submit">Submit</button>  
        </Form>
      </div>
    );
};

  
const root = document.querySelector('#root');
ReactDOM.render(<App />, root);

It is possible to get form data from outside of Form component in the following way:

// ...

const App = () => {
  	const formRef = React.useRef();
  	// ...
  	// It is possible to get data from outside of the Form component when reference is ready:
  	//   const data = getData(formRef.current);
    // It is possible to bind data too:
    //   bindData(formRef.current, data);
    return (
      <div>
        <Form ref={formRef} data={data} onSubmit={handleSubmit}>
          {/* ... */}
        </Form>
      </div>
    );
};

  
const root = document.querySelector('#root');
ReactDOM.render(<App />, root);
Native Advertising
­čÜÇ
Get your tech brand or product in front of software developers.
For more information Contact us
Dirask - we help you to
solve coding problems.
Ask question.

ÔŁĄ´ŞĆ­čĺ╗ ­čÖé

Join