Level Up Your React.js Forms: Tips and Tricks for Optimizing State Logic
React.js, a frontend UI library for creating highly dynamic single-page applications (SPAs). It is common to use forms when creating dynamic content. There are numerous ways to create and manage forms in React.js, but they all follow the same basic structure, which is to create a state to store the value and a handler function to update its value. This process works fine initially, but as we move towards more complex applications, we may require to optimize these forms.
In this article, we’ll go over some tips and tricks for optimizing performance and reducing the amount of JavaScript code to create fast and efficient forms in React.js.
Tip-1: Using one state variable and one handler
Assume you’re creating a signup form and need to collect the user’s name, email, and password. The simplest way to achieve this is to create three state variables and bind their values to the <input>
elements with an onChange
handler attached.
import { useState } from 'react';
export default function SignUp() {
const [name, setName] = useState('');
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
return (
<form className='form'>
<h3>Sign Up</h3>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder='Name'
/>
<input
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder='Email'
/>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder='Password'
/>
<button type='submit'>Submit</button>
</form>
);
}
Code language: JavaScript (javascript)
Although, this approach works great in most cases where there are fewer form fields. However, for forms that require more input fields, using this approach will result in a lot of state variables just to create a simple form.
To fix this, we’ll change the current setup by removing all of the state variables and keeping only one for storing all of the data.
const [state, setState] = useState({
name: '',
email: '',
password: '',
});
Code language: JavaScript (javascript)
We have stored the inside an object, with each key corresponding to one input field’s data.
To update the values, we’ll also write a handler function that takes an event object as an argument, extracts the name
and value
, then uses the name
as the dynamic key to update the state object.
const handleChange = (e) => {
const { name, value } = e.target;
setState({ ...state, [name]: value });
};
Code language: JavaScript (javascript)
The
name
is the attribute we pass to the<input>
field. It helps in determining the value contained in the input field. To use the current method, we must pass thename
attribute to the<input>
field with a value that corresponds to the name of the actual state.
The final setup looks something like this:
import { useState } from 'react';
export default function SignUp() {
const [state, setState] = useState({
name: '',
email: '',
password: '',
});
const handleChange = (e) => {
const { name, value } = e.target;
setState({ ...state, [name]: value });
};
return (
<form className='form'>
<h3>Sign Up</h3>
<input
name='name'
value={state.name}
onChange={handleChange}
placeholder='Name'
/>
<input
name='email'
value={state.email}
onChange={handleChange}
placeholder='Email'
/>
<input
name='password'
value={state.password}
onChange={handleChange}
placeholder='Password'
/>
<button type='submit'>Submit</button>
</form>
);
}
Code language: JavaScript (javascript)
As can be seen, we have simply reduced one state variable for each input field to one state variable for all input fields with a common handler function. With this setup in place, we can add more form fields without having to create new state variables; instead, we can simply insert a new key inside our existing one.
However, if the form is very large, with many different fields, this approach is not recommended as it may degrade the performance. But don’t worry, I’ve got you covered; the next tip covers such situations.
Tip-2: Use uncontrolled inputs for large forms
There are two types of input fields in React.js: controlled and uncontrolled.
In controlled inputs, React is in charge of managing the state of the inputs. In simple words, controlled inputs are those whose value is bound to a state variable.
<input type='email' value={email} onChange={handleChange} />
Code language: JavaScript (javascript)
Unlike controlled inputs, uncontrolled inputs are not controlled by React. Each input keeps track of its own state. Uncontrolled inputs are essentially input fields with no state variable bound to them.
<input type='email' />
Code language: JavaScript (javascript)
However, If we use controlled inputs, the performance may degrade as the number of form fields increases. Therefore, for large forms, using uncontrolled inputs is a better choice.
Using uncontrolled inputs doesn’t require us to create any state variables or handler functions. The input fields maintain their state themselves. We can extract the input values using either a ref
or the event object returned at form submission.
In the previous example, let’s now use uncontrolled inputs to see the difference:
export default function SignUp() {
function handleSubmit (e) {
// To prevent the page from reloading
e.preventDefault();
// Generate a new form data object
const formData = new FormData(e.target);
// Get the required values
const formObject = Object.fromEntries(formData.entries());
console.log(formObject);
}
return (
<form className='form' onSubmit={handleSubmit} >
<h3>Sign Up</h3>
<input
name='name'
placeholder='Name'
/>
<input
name='email'
placeholder='Email'
/>
<input
name='password'
placeholder='Password'
/>
<button type='submit'>Submit</button>
</form>
);
}
Code language: JavaScript (javascript)
You can see that the code looks a lot more simple and clean now. On clicking the submit button, we can see all the input data.
Hence, we may now add as many form fields without needing to worry about any state management logic or performance bottlenecks.
Tip-3: Debounce search forms
When it comes to getting results from a user query, search fields are very important. Most search fields use an API and the user query to fetch the matched results. API calls can be time-consuming and expensive. Calling the API with each keystroke made by the user may cause the API to become overloaded. This may break the application’s essential functionality, which is not good for the user experience.
Assume the search field below calls an API with each keystroke. To know how many times we call an API, we simply log “API called” inside the handler function.
export default function SearchForm() {
function fetchResults() {
console.log('API called');
}
return <input onChange={fetchResults} />;
}
Code language: JavaScript (javascript)
Our search form is calling the API 8 times for the search query. It’s very inefficient and unnecessary.
We use the debouncing method to solve this problem. It works by adding a timer to the function that calls the API.
The search field calls the debounced function with each keystroke, which starts a timer. When the timer expires, the actual function gets invoked, which fetches the result from the API. If another keystroke occurs in between, the old timer gets cleared and a new timer gets created, resetting the process.
Here’s an example of a debouncing function:
export const debounce = (callback, timer) => {
let timeoutId;
return (...args) => {
// save the current context (this)
const context = this;
// clear the existing timeout
clearTimeout(timeoutId);
// create a new timeout
timeoutId = setTimeout(() => {
callback.apply(context, args);
}, timer);
};
}
Code language: JavaScript (javascript)
As can be seen, it accepts a function and a timer as arguments and returns the debounced version. On every call, the debounced function will wait for the timer to expire before invoking the actual function.
This debouncing function is used in the search form below. Instead of directly calling the fetchResults
function, we’ll use the debounced version.
import { debounce } from "../utils";
export default function SearchForm() {
function fetchResults() {
console.log('API called');
}
const debouncedFetchResults = debounce(fetchResults, 500)
return <input onChange={debouncedFetchResults} />;
}
Code language: JavaScript (javascript)
Now, we may type something on this search form, and it won’t call the fetchResults
function until we stop typing for 500 milliseconds. You can test this out using the embedded playground below.
Tip-4: Use a form library
As we’ve seen, there are numerous considerations when building forms in React. Therefore, as a beginner, using a form library is easier to get started. There are a ton of form libraries for React.js, but the most popular one is Formik.
To get started, install the library using NPM:
npm install --save formik
Code language: Bash (bash)
Now, import its built-in components and begin building the form. Here’s an example of how to use Formik to recreate our previous signup form.
import React from 'react';
import { Formik, Field, Form } from 'formik';
export default function SignUp() {
return (
<Formik
initialValues={{ name: '', email: '', password: '' }}
onSubmit={(values) => {
console.log(values);
}}
>
<Form className='form'>
<h3>Sign Up</h3>
<Field name='name' type='text' placeholder='Name' />
<Field name='email' type='email' placeholder='Email' />
<Field name='password' type='password' placeholder='Password' />
<button type='submit'>Submit</button>
</Form>
</Formik>
);
}
Code language: JavaScript (javascript)
As shown above, it has a lot less code and is also very easy to understand. But that doesn’t mean the library can only do basic stuff. Using this library, we can easily create extremely complex forms. To learn more about Formik, you can refer to its documentation here.
Conclusion
In this article, we learned how to easily create great React.js forms. We also looked into debouncing, which helps in reducing the number of API calls in the case of a search form. Finally, we learned about the Formik library, which allows us to quickly get started with form building in React.js.
You can find the complete source code on Codedamn Playgrounds.
This article hopefully provided you with some new information. Share it with your friends if you enjoy it. Also, please provide your feedback in the comments section.
Thank you so much for reading 😄
Sharing is caring
Did you like what Varun Tiwari wrote? Thank them for their work by sharing it on social media.
No comments so far
Curious about this topic? Continue your journey with these coding courses: