DEV Community

dbxDev
dbxDev

Posted on

Elevating React Development with Custom Hooks and Props Getters

Hi everyone 👋🏻, I’m a Software Engineer working in Website Development aimed in both sides of the application.

In my recent project, I have worked with ReactJS using functional components, which very new for me because I was using the class components for a long time, and learned a lot from it. One of them is the patterns applied, it dramatically makes the source code more easier to manage, enhance the structure and developer experience as well as performance of the application. In this post, I will show you these two patterns and its combination: Custom React Hook and Props Getters.

Custom Hooks

Custom Hooks stepped in as the first solution, offering a way to extract and reuse logic across components. This not only reduced redundancy but also enhanced the readability and maintainability of the code. For instance, the useLocalStorage hook:

// useLocalStorage
import { useState, useEffect } from 'react';

function useLocalStorage(key, initialValue) {
  const [storedValue, setStoredValue] = useState(() => {
    try {
      const item = window.localStorage.getItem(key);
      return item ? JSON.parse(item) : initialValue;
    } catch (error) {
      console.log(error);
      return initialValue;
    }
  });

  useEffect(() => {
    try {
      window.localStorage.setItem(key, JSON.stringify(storedValue));
    } catch (error) {
      console.log(error);
    }
  }, [key, storedValue]);

  return [storedValue, setStoredValue];
}
Enter fullscreen mode Exit fullscreen mode

You can hook this function to set the theme for the app.

import React from 'react';
import useLocalStorage from './useLocalStorage';

function App() {
  const [theme, setTheme] = useLocalStorage('theme', 'light');

  return (
    <div className={theme}>
      <button onClick={() => setTheme('light')}>Light Theme</button>
      <button onClick={() => setTheme('dark')}>Dark Theme</button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And even re-use it to remember the form input.

import React from 'react';
import useLocalStorage from './useLocalStorage';

function Form() {
  const [formData, setFormData] = useLocalStorage('formData', {});

  const handleChange = (event) => {
    setFormData({ ...formData, [event.target.name]: event.target.value });
  };

  return (
    <form>
      <input
        name="name"
        value={formData.name || ''}
        onChange={handleChange}
      />
      <input
        name="email"
        value={formData.email || ''}
        onChange={handleChange}
      />
      <button type="submit">Submit</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

As you can see, the Custom Hook is very useful to separate the logic from the component, and easy to re-use it in everywhere that needed. However, when dealing with extensive business logic, a Custom Hook can become cluttered with numerous props.

Props Getters

Let’s take a look on the original, without using the Props Getters pattern

import { useState, useCallback } from 'react';

function useForm(initialValues, validate) {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});

  const handleChange = useCallback((event) => {
    const { name, value } = event.target;
    setValues(values => ({ ...values, [name]: value }));
    if (validate) {
      const validationErrors = validate(values);
      setErrors(validationErrors);
    }
  }, [validate]);

  const handleSubmit = useCallback((event) => {
    event.preventDefault();
    if (validate) {
      const validationErrors = validate(values);
      setErrors(validationErrors);
      if (Object.keys(validationErrors).length === 0) {
        // Submit logic or callback
      }
    }
  }, [values, validate]);

  const handleReset = useCallback(() => {
    setValues(initialValues);
    setErrors({});
  }, [initialValues]);

  return {
    values,
    errors,
    handleChange,
    handleSubmit,
    handleReset,
  };
}
Enter fullscreen mode Exit fullscreen mode

And you might guess how it’s used:

const validate = (name, value) => {
  // Simple validation logic
  if (!value) return 'Field is required';
  return '';
};

function MyForm() {
  const { values, errors, handleChange, handleSunmit, resetForm, validateForm } = useForm({ name: '', email: '' }, validate);

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          name="name"
          value={values.name}
          onChange={handleChange}
        />
        {errors.name && <span>{errors.name}</span>}
      </div>
      <div>
        <label>Email:</label>
        <input
          name="email"
          value={values.email}
          onChange={handleChange}
        />
        {errors.email && <span>{errors.email}</span>}
      </div>
      <button type="submit">Submit</button>
      <button type="button" onClick={resetForm}>Reset</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

With the combination of the Custom Hook and the Props Getters, the logic is even more cleaner, readable and manageable.

Now let’s make some twists 🔮 for our hook:

import { useState } from 'react';

const useForm = (initialValues, validate) => {
  const [values, setValues] = useState(initialValues);
  const [errors, setErrors] = useState({});

  // Handle changes in form fields
  const handleChange = (event) => {
    const { name, value } = event.target;
    setValues({ ...values, [name]: value });

    // Validate on change
    if (validate) {
      setErrors({ ...errors, [name]: validate(name, value) });
    }
  };

  // Reset form to initial values
  const resetForm = () => {
    setValues(initialValues);
    setErrors({});
  };

  // Validate form
  const validateForm = () => {
    if (!validate) return true;

    const newErrors = {};
    let isValid = true;
    for (const key in values) {
      const error = validate(key, values[key]);
      if (error) {
        isValid = false;
        newErrors[key] = error;
      }
    }

    setErrors(newErrors);
    return isValid;
  };

  // Get input field properties
  const getInputProps = (name) => ({
    name,
    value: values[name] || '',
    onChange: handleChange,
    // Additional props like onBlur can be added here if needed
  });

  return {
        errors,
    handleChange,
    resetForm,
    validateForm,
    getInputProps
  };
};
Enter fullscreen mode Exit fullscreen mode

And look how it’s used in the component. Very easy to read, isn’t it?

import React from 'react';
import useForm from './useForm';

const validate = (values) => {
  let errors = {};
  if (!values.name) {
    errors.name = 'Name is required';
  }
  // Additional validation logic here
  return errors;
};

function MyForm() {
  const { errors, handleSubmit, handleReset, getInputProps } = useForm({ name: '', email: '' }, validate);

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" {...getInputProps('name')} />
      {errors.name && <p>{errors.name}</p>}

      <input type="email" {...getInputProps('email')} />
      {errors.email && <p>{errors.email}</p>}

      <button type="submit">Submit</button>
      <button type="button" onClick={handleReset}>Reset</button>
    </form>
  );
}
Enter fullscreen mode Exit fullscreen mode

As you can see, it’s significantly reduced the line on code and very easy to read. A simple comparison for these two approaches based on some criteria below:

  1. Simplicity vs. Control: The props getters pattern offers a more streamlined interface at the cost of some direct control, whereas the direct approach provides more explicit control over form elements at the cost of slightly more boilerplate.
  2. Encapsulation: The props getters pattern encapsulates more of the form logic, hiding the details from the consumer. The direct approach exposes more of the internal workings to the consumer.
  3. Flexibility and Customizability: Both approaches offer flexibility, but in different aspects. The props getters approach allows for internal customizations within the hook, while the direct approach allows for more customization in how the hook is used.
  4. Readability and Maintenance: The props getters approach can lead to cleaner and more maintainable consumer components, as it abstracts away repetitive logic. The direct approach, while slightly more verbose, offers clearer insight into the form's behavior at the component level.

Pros and Cons

Everything has it pros and cons. Here are some pros and cons when using Custom Hook and Props Getters patterns.

With Custom Hook Pattern:

Pros

  1. Reusability: Custom Hooks allow you to extract component logic into reusable functions, which can be shared across multiple components.
  2. Separation of Concerns: They help in organizing the logic by separating UI from state management and side effects, leading to cleaner and more maintainable code.
  3. Composition: Custom Hooks can be composed together to build complex functionality from simpler hooks.
  4. Simplicity: They provide a simpler and more intuitive way to use stateful logic, compared to higher-order components (HOCs) and render props patterns.

Cons

  1. Overhead in Small Applications: For small, simple applications, custom hooks can add unnecessary complexity and overhead.
  2. Testing Complexity: Testing components that use custom hooks can be more challenging, as it might require mocking hooks or their internal state.
  3. Learning Curve: Developers new to React or hooks might find it difficult to understand and use custom hooks effectively. So it’s required you to understand the core concept of React Custom Hook.
  4. Potential for Overuse: There's a risk of creating too many small hooks, leading to fragmented code that can be hard to follow.

With Props Getters Pattern:

Pros:

  1. Ease of Use: This pattern makes it easy to use and share common behaviors across components, reducing boilerplate and improving consistency.
  2. Control and Customization: It provides a controlled way to pass down props and behavior, while still allowing the consumer to customize or override certain aspects.
  3. Improved Readability: By encapsulating and abstracting away complex logic, it makes the consuming components cleaner and more readable.
  4. Flexibility: It offers flexibility in how props and behaviors are applied to components, which is beneficial in complex or dynamic UIs.

Cons:

  1. Abstraction Complexity: The abstraction can sometimes hide too much detail, making it harder for developers to understand what’s happening under the hood.
  2. Potential for Misuse: If not used carefully, it can lead to tightly coupled code, where changing the implementation of the hook could require changes in all consuming components.
  3. Prop Collision Risk: There’s a risk of prop name collisions, especially in larger, more complex components or with multiple composed hooks.
  4. Decreased Discoverability: For new developers or those unfamiliar with the codebase, understanding how and where props are applied can be challenging.

Conclusion

In the fast-paced world of web development, combining Custom Hooks with Props Getters in ReactJS is like having a superpower. It's like using two great tools together to build something even more awesome. Custom Hooks help us reuse code easily, keeping our projects neat and tidy. On the other hand, Props Getters make our code smarter and more flexible.

When we mix these two, we get a powerful combo that makes our code not only clean and easy to manage but also super adaptable. This combination can simplify complex tasks, making our code easier to read, maintain, and efficient.

So, if you're working on a ReactJS project and want to make your life easier, give this combo a try. You might find this approach as transformative as discovering a new favorite tool in your toolkit. And if you've already used these patterns together, I'd love to hear about your experience. How did it go? What did you build? Let's share and learn from each other's adventures in coding! Thank you for your reading.

Top comments (0)