· Tech  Â· 5 min read

Jotai and the Pseudo-Flux Pattern: Rethinking React State Management

A practical journey from Redux to Recoil to Jotai, exploring abstraction and flexible state patterns. This post introduces a pseudo-Flux/Reducer pattern with Jotai and its real-world benefits.

A practical journey from Redux to Recoil to Jotai, exploring abstraction and flexible state patterns. This post introduces a pseudo-Flux/Reducer pattern with Jotai and its real-world benefits.

You never truly go stateless with the frontend.

First, there was Redux. Then, we moved from our internal framework to Next.js, and from Flow to TypeScript. So, we tried new things: Context, useReducer, and Recoil. Then came Covid, and the maintener is gone with Covid related tech industry layoffs, and React 19 came (tho forgot which one was first), which does not work with Recoil, so we moved to Jotai.

One thing stuck with me. While full-fledged Redux with all its boilerplate was a bit time-consuming, I still embraced the idea of Flux—particularly the predictability of dispatch and reducer patterns.

Like with any other React state library, you want to avoid calling them directly. So, when adapting to Recoil, I put most of the calls inside custom hooks that managed Recoil state. When it became clear that React 19 wouldn’t work with Recoil and Recoil was archived, I moved to Jotai. In the process, I eventually came up with a simplified reducer pattern.

I know some people don’t like the reducer pattern at all—finding it too redundant, too verbose, or too rigid. And I know some people might not like the Flux/Redux pattern either.

Still, I liked it because it provides a sensible way of grouping state granularly, instead of putting everything in a central store. It gives us predictability and easy debugging. Most importantly, abstracting state access—whether it’s Recoil, Jotai, or Redux—so you don’t call it directly is a good idea. Avoiding direct calls enabled us to move from Recoil to Jotai very quickly.

That said, now I am talking to hyporthetical future hiring managers and possible co-workers, I’m not dogmatic about any particular solution. This is just one approach that fit a particular stack and architecture I worked with during a period of my career. I’m happy to work with any stack or framework to solve the problem at hand.

Of course, in real repos, there would be more layers of abstractions and edge case scenarios, but here is an example:

import { Getter, Setter } from "jotai";
import { atom, useAtomValue } from "jotai";
import { useAtomCallback } from "jotai/utils";
import { useCallback, useMemo } from "react";

const name = atom<string>();
const age = atom<number>();
const mail = atom<string>();
const occupation = atom<string>();

// Action type definition: This follows the Redux pattern of using discriminated unions
// to ensure type safety when dispatching actions. Each action type has its own payload.
type Action =
  | {
      type: "update_name";
      name: string;
    }
  | {
      type: "update_age";
      age: number;
    }
  | {
      type: "update_mail";
      mail: string;
    }
  | {
      type: "update_occupation";
      occupation: string;
    };

// Collection of all user form atoms for easy access
// This allows us to dynamically access atoms by key name
const userFormState = {
  name,
  age,
  mail,
  occupation,
};

/**
 * Hook to access individual form field values
 *
 * This is a generic hook that lets you read the current value of any form field.
 * It uses Jotai's useAtomValue to subscribe to atom updates, causing re-renders
 * only when the specific field changes.
 *
 * @param key - The field name to read (e.g., 'name', 'age', 'mail', 'occupation')
 * @returns The current value of the specified field
 *
 * @example
 * const name = useUserFormStateProps('name');
 */
const useUserFormStateProps = <K extends keyof typeof userFormState>(key: K) => {
  const baseAtom = userFormState[key];
  const returnValue = useAtomValue(baseAtom);
  return returnValue;
};

/**
 * Main hook for managing user form state
 *
 * This hook provides:
 * 1. A dispatch function to update form fields (psudo Flux pattern)
 * 2. The useUserFormStateProps hook to read field values
 *
 * The dispatch function uses useAtomCallback, which is Jotai's way of creating
 * callbacks that can read/write atoms without causing unnecessary re-renders.
 *
 * @returns Object containing {dispatch, useUserFormStateProps}
 *
 * @example
 * const { dispatch, useUserFormStateProps } = useUserFormState();
 * const name = useUserFormStateProps('name');
 * dispatch({ type: 'update_name', name: 'John' });
 */
export const useUserFormState = () => {
  // useAtomCallback allows us to create a stable callback that can interact with atoms
  // the callback should be wrapped by useCallback
  // _get: Read atom values (not used here but available for reading current state)
  // set: Function to update atom values
  const dispatch = useAtomCallback(
    useCallback((_get: Getter, set: Setter, action: Action) => {
      // Prefer return instead of break for cleaner code and consistency
      switch (action.type) {
        case "update_name":
          // Update the name atom with the new value
          set(name, action.name);
          return;
        case "update_age":
          set(age, action.age);
          return;
        case "update_mail":
          set(mail, action.mail);
          return;
        case "update_occupation":
          set(occupation, action.occupation);
          return;
        default:
          return;
      }
    }, [])
  ); // empty dependency array: this callback will be referentiall stable
  // memoize the return value to prevent creating new objects on every render
  return useMemo(() => {
    return {
      dispatch,
      useUserFormStateProps,
    };
  }, [dispatch]); // only recreate if dispatch changes (which it shouldn't due to useCallback)
};

The advantage of this Psudo flux pattern is that this is really just a psudo pattern. As get is available, you can refer any other jotai state inside the reducer, and update any other Jotai state. In that sense this is just a convention you can conveniently ignore, in that sense, this is more lenient than Redux. On the other hand, now Redux fully enbrace hooks, it might be just choice of having a central Jotai store or Redux store.

Back to Blog

Related Posts

View All Posts »
React: Why Prop Change Does NOT Cause Re-render

React: Why Prop Change Does NOT Cause Re-render

A relatively deep dive into React's re-render mechanics, why prop changes alone don't trigger re-renders, and how state libraries like Jotai and Recoil manage surgical updates. Includes step-by-step code and practical patterns.

JavaScript Event Loop

JavaScript Event Loop

Understanding how JavaScript handles asynchronous operations through the Event Loop, Macro Stack, and Micro Stack mechanisms.