Level Up Your React.js Forms: Tips and Tricks for Optimizing State Logic

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)
A simple signup form.

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 the name 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.

Sign Up form using uncontrolled inputs.
Form data printed on the console.

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)
Search form without debouncing.

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 formikCode 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)
Sign up form using Formik.

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.

0/10000

No comments so far