DEV Community

Marcel Bos
Marcel Bos

Posted on

Introducing fluent-state: A Lightweight, Proxy-Based React State Hook

fluent-state

Hi all!

Managing complex nested state in React often leads to verbose code and tangled dependencies. With useFluentState, you get a clean, immutable state management solution that feels natural to use. Thanks to its proxy-powered API and built-in smart effect tracking, your components update only when truly necessary — no more boilerplate, no more hassle. Discover how useFluentState can simplify your React development and supercharge your productivity.

Why another React state hook?

If you’ve worked with React, you know that managing state can quickly get complicated — especially when dealing with nested data structures or when you want to run side effects that depend on parts of your state.
Traditional approaches like useState and useReducer often require you to write boilerplate code and manually manage dependencies. Libraries like Redux, MobX, or Zustand add complexity or rely on external patterns that don’t always feel intuitive.
useFluentState offers a fresh take: a proxy-based state hook that feels fluent and natural, with immutable updates baked in and automatic effect tracking — all without dependency arrays.

How does it work?

At its core, useFluentState uses lightweight proxy functions — not proxies of the state object itself, but proxies wrapping getter/setter functions — to provide:

  • Fluent, nested state access: Access and update nested properties by calling them like functions, e.g. state.user.settings.theme('Dark').
  • Immutable updates: Under the hood, updates create new copies only where needed, keeping React happy and state predictable.
  • Automatic effect tracking: Effects run only when their actually accessed parts of the state change — no more manual dependency arrays or stale closures.

Demo: A simple email listing app

Here’s a minimal example showing how easy it is to build an app with useFluentState and effects:

import React from "react";
import { useFluentState } from "use-fluent-state";

type EmailEntry = {
  email: string;
  isValid: boolean;
};

type State = {
  emails: EmailEntry[];
  newEmail: string;
  validationMessage: string;
};

const EmailListForm: React.FC = () => {
  const [state, effect] = useFluentState<State>({
    emails: [],
    newEmail: "",
    validationMessage: "",
  });

  // Effect: validate all emailaddresses & update validationMessage
  effect(() => {
    const allValid = state.emails().every((e) => e.isValid);
    state.validationMessage(
      allValid
        ? "All emails are valid"
        : "Some emails are invalid, please check."
    );
  });

  // Add emailaddress & validate
  const addEmail = () => {
    const email = state.newEmail().trim();
    if (!email) return;
    const isValid = email.includes("@") && email.includes(".");
    state.emails((prev) => [...prev, { email, isValid }]);
    state.newEmail("");
  };

  // Update emailaddress & validate for specific index
  const updateEmail = (index: number, value: string) => {
    const isValid = value.includes("@") && value.includes(".");
    state.emails((prev) =>
      prev.map((e, i) =>
        i === index ? { email: value, isValid } : e
      )
    );
  };

  return (
    <div>
      <h2>Email list with validation</h2>
      <input
        type="email"
        placeholder="Add new email"
        value={state.newEmail()}
        onChange={(e) => state.newEmail(e.target.value)}
      />
      <button onClick={addEmail}>Add Email</button>

      <ul>
        {state.emails().map((entry, i) => (
          <li key={i}>
            <input
              type="email"
              value={entry.email}
              onChange={(e) => updateEmail(i, e.target.value)}
              style={{ borderColor: entry.isValid ? "green" : "red" }}
            />
          </li>
        ))}
      </ul>

      <p
        style={{
          color: state.validationMessage().includes("valid")
            ? "green"
            : "red",
          fontWeight: "bold",
        }}
      >
        {state.validationMessage()}
      </p>
    </div>
  );
};

export default EmailListForm;

Enter fullscreen mode Exit fullscreen mode

Why this example?

  • Managing nested arrays This example demonstrates how to easily handle arrays of complex objects (like emails with validation state) using useFluentState without messy deep cloning.

  • Automatic and precise effect tracking The effect only triggers when actual changes occur in the emails array, preventing unnecessary re-renders and keeping your code clean.

  • Clear, concise updates with updater functions Using updater functions in setters (state.emails(prev => [...])) allows you to write short, expressive, and safe immutable updates.

  • Visual validation feedback Inline input styling immediately shows which emails are valid, enhancing user experience.

  • Realtime global validation status The validationMessage is automatically synchronized via the effect, so you always have an up-to-date overview of validation state.


Why useFluentState rocks

  • No boilerplate: No reducers, no dispatchers, just direct fluent calls.
  • Immutable & performant: State updates create new objects only for the changed parts of the state, while keeping references for unchanged parts intact. This copy-on-write strategy ensures minimal re-renders and predictable behavior.
  • Automatic effect tracking: Effects re-run only when their accessed state changes — no more dependency arrays or bugs.
  • Lightweight: Small core, no external dependencies or magic.
  • Developer-friendly: Intuitive API that fits React’s mental model but extends it beautifully.

Where to get it

You can check out the GitHub repo here and try a live CodeSandbox demo.

What’s next?

I’m actively working on adding features like persistence, computed properties, and devtools integration — stay tuned!

Thanks for reading! If you try useFluentState, let me know your thoughts — I’d love to hear how it fits your projects.
Happy coding!

Top comments (0)