DEV Community

Cover image for Why It's Time to Ditch useState and Upgrade to useEasyState!
shivam singh for Middleware

Posted on

Why It's Time to Ditch useState and Upgrade to useEasyState!

Custom React Hook: useEasyState 🎉

Let's dive into useEasyState, a custom React hook that streamlines state management. With this hook, you can handle state updates, resets, and more with minimal fuss. It’s a handy tool to keep your state management clean and straightforward, so you can spend less time on boilerplate code and more on building features.



export const useEasyState = <T = any>(defaultValue?: T) => {
  const [value, _set] = useState<T>(defaultValue);
  const valueRef = useRef<T>(defaultValue);
  valueRef.current = value;
  const [defaultState] = useState(defaultValue);
  const [touched, setTouched] = useState(false);
  const [dirty, setDirty] = useState(false);

  const set: Dispatch<SetStateAction<T>> = useCallback((...args) => {
    _set(...args);
    setDirty(true);
    setTouched(true);
  }, []);

  const eventHandler = useCallback(
    (e: ChangeEvent<HTMLInputElement>) => set((e?.target?.value as any) || ""),
    [set]
  );
  const reset = useCallback(() => {
    setDirty(false);
    set(defaultState);
  }, [defaultState, set]);

  const clear = useCallback(() => {
    if (Array.isArray(defaultState)) return set([] as any);
    if (defaultState === null || defaultState === undefined)
      return set(defaultState);
    if (defaultState instanceof Date) return set(null);

    switch (typeof defaultState) {
      case "boolean":
        return set(false as any);
      case "number":
        return set(0 as any);
      case "string":
        return set("" as any);
      case "object":
        return set({} as any);
      default:
        return set(defaultState);
    }
  }, [defaultState, set]);

  return useMemo(
    () => ({
      value,
      valueRef,
      set,
      eventHandler,
      initial: defaultState,
      reset,
      clear,
      touched,
      dirty,
    }),
    [clear, defaultState, dirty, eventHandler, reset, set, touched, value]
  );
};

export type EasyState<T> = ReturnType<typeof useEasyState<T>>;


Enter fullscreen mode Exit fullscreen mode

Code Breakdown

1. State Management 🧠

State Management in action



const [value, _set] = useState<T>(defaultValue);
const valueRef = useRef<T>(defaultValue);
valueRef.current = value;
const [defaultState] = useState(defaultValue);
const [touched, setTouched] = useState(false);
const [dirty, setDirty] = useState(false);


Enter fullscreen mode Exit fullscreen mode
  • value: Your current state.
  • valueRef: Reference to track value changes.
  • defaultState: Your "reset to factory settings" button. Because sometimes, you just need a fresh start. 🔄
  • touched: Did someone touch the state? Now you'll know. 👀
  • dirty: Tracks if the state has been modified. 🕵️

State management in action

2. State Setter with Side Effects 🔄

UseState state setter



const set: Dispatch<SetStateAction<T>> = useCallback((...args) => {
  _set(...args);
  setDirty(true);
  setTouched(true);
}, []);


Enter fullscreen mode Exit fullscreen mode
  • set: Updates the state and flags it as touched and dirty.

3. Event Handler

Use EasyState event handler



const eventHandler = useCallback(
  (e: ChangeEvent<HTMLInputElement>) => set((e?.target?.value as any) || ""),
  [set]
);


Enter fullscreen mode Exit fullscreen mode
  • eventHandler: Automatically updates your state based on user input. It's like magic, but without the wand. 🎩✨

Event handler magic

4. Reset Functionality 🧹

UseEasyState Reset Functionality



const reset = useCallback(() => {
  setDirty(false);
  set(defaultState);
}, [defaultState, set]);


Enter fullscreen mode Exit fullscreen mode
  • reset: Reverts everything back to square one. It's like hitting the undo button. 💥

Resetting your mistakes

5. Clear Functionality

UseEasyState clear functionality



const clear = useCallback(() => {
  if (Array.isArray(defaultState)) return set([] as any);
  if (defaultState === null || defaultState === undefined)
    return set(defaultState);
  if (defaultState instanceof Date) return set(null);

  switch (typeof defaultState) {
    case "boolean":
      return set(false as any);
    case "number":
      return set(0 as any);
    case "string":
      return set("" as any);
    case "object":
      return set({} as any);
    default:
      return set(defaultState);
  }
}, [defaultState, set]);


Enter fullscreen mode Exit fullscreen mode
  • clear: Clears the state based on its type. 🧹✨

Clearing all the clutter

6. Memoized Return Object 🧳



return useMemo(
  () => ({
    value,
    valueRef,
    set,
    eventHandler,
    initial: defaultState,
    reset,
    clear,
    touched,
    dirty,
  }),
  [clear, defaultState, dirty, eventHandler, reset, set, touched, value]
);


Enter fullscreen mode Exit fullscreen mode
  • useMemo: Neatly packages everything together, It only changes when it absolutely needs to, so you’re not dealing with any unnecessary re-renders.

Real-World Usage of useEasyState 🌎

Ready to see this in action? Let's check out how useEasyState makes your life easier and your code cleaner.

Example 1: Managing Form State ✍️

Let’s say you’re building a form to collect user info. You need something that can handle all that input without breaking a sweat.



import React from "react";
import { useEasyState } from "./useEasyState";

const UserForm = () => {
  const nameState = useEasyState<string>("");
  const emailState = useEasyState<string>("");

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    console.log("Name:", nameState.value);
    console.log("Email:", emailState.value);

    // Simulate form submission...
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>Name:</label>
        <input
          type="text"
          value={nameState.value}
          onChange={nameState.eventHandler}
        />
      </div>
      <div>
        <label>Email:</label>
        <input
          type="email"
          value={emailState.value}
          onChange={emailState.eventHandler}
        />
      </div>
      <div>
        <button type="submit">Submit</button>
        <button type="button" onClick={nameState.reset}>
          Reset Name
        </button>
        <button type="button" onClick={emailState.clear}>
          Clear Email
        </button>
      </div>
    </form>
  );
};

export default UserForm;


Enter fullscreen mode Exit fullscreen mode

Key Points:

  • nameState and emailState make managing your form a breeze.
  • reset and clear are there when you need them.

Form handling like a pro

Example 2: Controlled Component with Default Value 🎛️

Need to control an input with a default value? useEasyState has your back.



import React from "react";
import { useEasyState } from "./useEasyState";

const ControlledInput = () => {
  const defaultText = "Hello World";
  const textState = useEasyState(defaultText);

  return (
    <div>
      <input
        type="text"
        value={textState.value}
        onChange={textState.eventHandler}
      />
      <button onClick={textState.reset}>Reset</button>
      <button onClick={textState.clear}>Clear</button>
      <p>Current Value: {textState.value}</p>
      <p>Dirty: {textState.dirty ? "Yes" : "No"}</p>
      <p>Touched: {textState.touched ? "Yes" : "No"}</p>
    </div>
  );
};

export default ControlledInput;


Enter fullscreen mode Exit fullscreen mode

Key Points:

  • The default value of "Hello World" is easy to manage, modify, or reset.
  • It tracks whether you've messed with the input like a responsible adult.

Managing inputs like a boss

Example 3: Managing Non-String State 🚀

Who says useEasyState is only for strings? It’s ready to handle arrays, objects, and whatever else you throw at it.



import React from "react";
import { useEasyState } from "./useEasyState";

const ManageArrayState = () => {
  const itemsState = useEasyState<string[]>([]);

  const addItem = () => {
    itemsState.set((prevItems) => [
      ...prevItems,
      `Item ${prevItems.length + 1}`,
    ]);
  };

  return (
    <div>
      <button onClick={addItem}>Add Item</button>
      <button onClick={itemsState.clear}>Clear Items</button>
      <ul>
        {itemsState.value.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
};

export default ManageArrayState;


Enter fullscreen mode Exit fullscreen mode

Key Points:

  • This hook handles arrays like a champ.
  • addItem and clear keep things fresh and clean.

Handling arrays like a pro

Why useEasyState Makes Development Faster and Easier ⏳🧠

  • Less Boilerplate 🚫📄: Forget about juggling multiple useState hooks, writing event handlers, and managing reset logic. useEasyState consolidates these tasks into one hook, reducing code clutter and saving you time for other important tasks.

  • Consistent State Management 🔑: Achieve uniform and reliable state management across your components. useEasyState ensures that state handling is smooth and consistent, making your codebase more dependable and easier to maintain.

  • Versatile and Flexible 🌀: Whether you're dealing with strings, numbers, arrays, or other data types, useEasyState adapts to your needs. The hook’s clear and reset functions handle different types automatically, freeing you from manual type-checking.

  • Focus on What Matters 🎉: Skip the repetitive tasks and dive into the exciting parts of development. useEasyState simplifies state management, allowing you to concentrate on building features and enhancing your application.

⚠️ Caveat: Handling useEffect Dependencies with useEasyState

When using useEasyState, a common issue arises when setting state within a useEffect hook. The hook tends to add the entire useEasyState object as a dependency if you attempt to set it directly from within the useEffect. This can lead to unintended re-renders and performance issues. 😅

🧩 The Problem

Say you've got this:



const something = useEasyState();


Enter fullscreen mode Exit fullscreen mode

And then you decide to do this in a useEffect:



useEffect(() => {
  something.set("some value");
}, [something.set]);


Enter fullscreen mode Exit fullscreen mode

Now, you'd think everything's fine, right? But no. React sees something.set and says, "Hey, why not take the whole something object along for the ride?" And boom, your effect starts re-running more often than you'd like. 😩

React Hook Dependency Warning

The useEffect will consider something (the entire object) as a dependency, leading to unnecessary re-executions of the effect. 😬

🛠️ Why This Happens

Technically, you don't need to use the depFn function here. If you add something.set to the dependency list and use it as expected, React will behave. But—there's always a "but"—the ESLint rule that checks your dependencies doesn't quite get it. It sees you accessing a function within an object and thinks, "Hmm, better play it safe and throw the whole object in there." 🤦‍♂️

It's frustrating, right? If you don't actually invoke the function but just refer to it, ESLint stays quiet. But the moment you start using it, the complaints roll in. One day, maybe we'll live in a world where this rule is smarter. Until then, we have a workaround.
Come On, ESLint!

🛠️ The Solution: depFn

To avoid this, you can use a utility function like depFn to limit the dependencies to just the setter method:



type AnyFunction = (...args: any[]) => any | Promise<any>;
export const depFn = <T extends AnyFunction>(fn: T, ...args: Parameters<T>) =>
  fn?.(...args);


Enter fullscreen mode Exit fullscreen mode

When using depFn, your useEffect would look like this:



useEffect(() => {
  depFn(something.set, "some value");
}, [something.set]);


Enter fullscreen mode Exit fullscreen mode

This way, the dependency array only includes something.set instead of the entire something object, which prevents unnecessary re-renders. 🚀

Efficient Dependency Management

📜 Summary

While useEasyState simplifies state management significantly, it's important to be aware of how React handles dependencies in hooks like useEffect. By using a helper like depFn, you can keep your effects efficient and avoid performance pitfalls. 💪


If you found this article helpful and enjoyed exploring the useEasyState, please consider starring middleware repository where we use it live in action.
Your support helps us continue improving and sharing valuable content with the community. Thank you!

GitHub logo middlewarehq / middleware

✨ Open-source DORA metrics platform for engineering teams ✨

Middleware Logo

Open-source engineering management that unlocks developer potential

continuous integration Commit activity per month contributors
license Stars

Join our Open Source Community

Middleware Opensource

Introduction

Middleware is an open-source tool designed to help engineering leaders measure and analyze the effectiveness of their teams using the DORA metrics. The DORA metrics are a set of four key values that provide insights into software delivery performance and operational efficiency.

They are:

  • Deployment Frequency: The frequency of code deployments to production or an operational environment.
  • Lead Time for Changes: The time it takes for a commit to make it into production.
  • Mean Time to Restore: The time it takes to restore service after an incident or failure.
  • Change Failure Rate: The percentage of deployments that result in failures or require remediation.

Table of Contents





Top comments (7)

Collapse
 
webjose profile image
José Pablo Ramírez Vargas • Edited

Just useEffect()? I ditched React completely a year ago. I'll never go back.

Collapse
 
samadyarkhan profile image
Samad Yar Khan

As far as I know, react is user across the industry for front end, why did you quit ?

Collapse
 
webjose profile image
José Pablo Ramírez Vargas

Nope. Svelte is my religion now.

Thread Thread
 
jayantbh profile image
Jayant Bhawal

Svelte has pretty solid decisions made at a framework level. Good choice.
Were it more popular, I'd consider using it more myself.

Thread Thread
 
webjose profile image
José Pablo Ramírez Vargas

Be the change you want! Svelte can do anything React can, and more. Contribute to its popularity by switching to Svelte today! 😄

Thread Thread
 
eforeshaan profile image
Eshaan

He talks like a cult leader, haha! I guess I have to check Svelte out

Collapse
 
samadyarkhan profile image
Samad Yar Khan

Great read! hooks can be tricky but this summed dit up fairly well.