DEV Community

Cover image for Tired of Overcomplicated React State Management? Try F-Box React!
KentaroMorishita
KentaroMorishita

Posted on

Tired of Overcomplicated React State Management? Try F-Box React!

“Ugh... my state is a mess again.”

Anyone who has used React for state management has likely encountered these pains:

  • Even though we can manage state with useState or useReducer, it becomes cumbersome to pass state around when components multiply
  • To share state among multiple components, we end up doing the “props drilling” relay or introducing useContext
  • Libraries like Redux are powerful, but they come with their own learning curve

“Isn’t there a simpler way to manage state?”

At the moment, libraries such as Jotai and Zustand are pretty solid options if you want simpler state management.

But I decided to take a different approach and created F-Box React.

With F-Box React, you can potentially free yourself from state management boilerplate and make your code simpler!

What Is F-Box React?

F-Box React is a custom hook library that aims to make React state management easier.

It feels almost like using useState, but it lets you naturally share state across multiple components and also handle reactive values (called RBoxes) that can be used independently of React.

  • Managing parent-child props drilling starts to get out of hand
  • To share the same state across multiple components, you need useContext
  • As state becomes more complex, you feel compelled to bring in a bigger library with a higher learning curve

These kinds of issues can potentially be solved by F-Box React.

Documentation

https://f-box-docs.com/f-box-react/introduction

Check out the docs for more details and advanced usage examples.

Installation

F-Box React is easily installed via npm or yarn. Just pick the version that matches the React version you’re using.

Note that it depends on a package called f-box-core, which must be installed alongside it.

(Requires React 18+.)

# If you're using React 19 (no version specifier for the latest)
npm install f-box-react f-box-core

# If you're using React 18
npm install f-box-react@0.1 f-box-core
Enter fullscreen mode Exit fullscreen mode

Background: Why Did I Build This?

At first, I didn’t even set out to create a “state management” library.

I built two libraries, F-Box (f-box-core) and F-Box React (f-box-react).

F-Box itself isn’t specifically aimed at “state management.”

First, it defines a “box” data type called Box that can store any type of data. Then it adds reactivity to that box, resulting in a data type called RBox (Reactive Box). More on that later, but RBox itself is just a reactive data type that can be handled in plain TypeScript, totally independent of React.

F-Box React is a collection of hooks that allow you to use that reactive power (RBox) inside React components.

The Concept of F-Box React

At the heart of F-Box React is a container called RBox and a hook called useRBox that plugs it into the React lifecycle.

RBox is a reactive container that doesn’t depend on React. It holds a value, and whenever that value changes, any parts of your app that are using it get updated automatically.

  • RBox: A container that enables reactive data flow, independent of React
  • useRBox: A hook that makes it simple to use RBox in React components

useRBox is pretty close to useState in that you just give it an initial value and start manipulating the “box” it returns.

  • Feels like useState
  • Effortlessly share state across multiple components
  • RBox can be used outside React too

Basic Example with a Counter: useRBox Works Almost Like useState

The Usual React Way (useState)

import { useState } from "react"

function Counter() {
  const [count, setCount] = useState(0)

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  )
}

export default Counter
Enter fullscreen mode Exit fullscreen mode

This is the classic approach.

Using F-Box React

import { useRBox, set } from "f-box-react"

function Counter() {
  const [count, countBox] = useRBox(0) // Create an RBox with initial value 0
  const setCount = set(countBox)       // Get a convenient updater function for the RBox

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  )
}

export default Counter
Enter fullscreen mode Exit fullscreen mode

useRBox returns a [value, RBox] pair, so it’s almost the same feel as useState.

The set function is a helper, but RBox itself also has a setValue method. setValue takes a callback to update the value, while set transforms it into a direct function call. For example:

countBox.setValue((prev) => prev + 1) // Updates by callback
set(countBox)(10)                     // Directly sets 10
Enter fullscreen mode Exit fullscreen mode

You can choose whichever style fits your use case.

RBox Can Be Used Without React

Since RBox doesn’t depend on React, it can manage reactive state outside of React components.

Basically, anywhere in your TypeScript code—front-end or server-side, it just works.

Example: Using RBox By Itself

import { RBox } from "f-box-core"

const numberBox = RBox.pack(0)

// Subscribe to changes and log updates
numberBox.subscribe((newValue) => {
  console.log(`Updated numberBox: ${newValue}`)
})

// Change the value, which notifies subscribers reactively
numberBox.setValue((prev) => prev + 1)  // Updated numberBox: 1
numberBox.setValue((prev) => prev + 10) // Updated numberBox: 11
Enter fullscreen mode Exit fullscreen mode

RBox provides a reactive mechanism that notifies subscribers when the value changes.

In React, we tap into that mechanism with useRBox to trigger rerenders whenever the value updates.

You can think of F-Box React as “the bridge that lets you take advantage of RBox within React.”

Sharing State Across Multiple Components

When multiple components need to share the same state, we typically use useContext or an external library. With F-Box React, it’s as simple as defining an RBox in a global scope.

The Usual React Way (with useContext)

import React, { createContext, useContext, useState } from "react"

const CounterContext = createContext()

function CounterProvider({ children }) {
  const [count, setCount] = useState(0)
  return (
    <CounterContext.Provider value={{ count, setCount }}>
      {children}
    </CounterContext.Provider>
  )
}

function CounterDisplay() {
  const { count } = useContext(CounterContext)
  return <p>Count: {count}</p>
}

function CounterButton() {
  const { setCount } = useContext(CounterContext)
  return <button onClick={() => setCount((prev) => prev + 1)}>+1</button>
}

function App() {
  return (
    <CounterProvider>
      <CounterDisplay />
      <CounterButton />
    </CounterProvider>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Creating a context, wrapping with a provider, calling useContext to consume state... sure, it works, but it can be a bit verbose.

F-Box React Way

import { RBox } from "f-box-core"
import { useRBox } from "f-box-react"

// Define the RBox outside the component
const counterBox = RBox.pack(0)

function CounterDisplay() {
  // Grab the global RBox using useRBox
  const [count] = useRBox(counterBox)

  return <p>Count: {count}</p>
}

function CounterButton() {
  return (
    <button onClick={() => counterBox.setValue((prev) => prev + 1)}>+1</button>
  )
}

function App() {
  return (
    <div>
      <CounterDisplay />
      <CounterButton />
    </div>
  )
}

export default App
Enter fullscreen mode Exit fullscreen mode

Just define the RBox in a global scope and call useRBox wherever you need it. No provider, no useContext, and fewer lines of boilerplate.

Replacing useReducer with useRBox

useReducer is handy for consolidating state logic in one spot, but it can become bloated for small-scale scenarios (especially with defining action types, a switch statement, etc.).

Here’s a side-by-side of useReducer versus an equivalent approach using useRBox.

The Usual React Way (with useReducer)

import { useReducer } from "react"

type State = {
  name: string
  age: number
}

type Action =
  | { type: "incremented_age" }
  | { type: "changed_name"; nextName: string }

function reducer(state: State, action: Action): State {
  switch (action.type) {
    case "incremented_age": {
      return {
        name: state.name,
        age: state.age + 1,
      }
    }
    case "changed_name": {
      return {
        name: action.nextName,
        age: state.age,
      }
    }
  }
}

const initialState = { name: "Taylor", age: 42 }

export default function Form() {
  const [state, dispatch] = useReducer(reducer, initialState)

  function handleButtonClick() {
    dispatch({ type: "incremented_age" })
  }

  function handleInputChange(e: React.ChangeEvent<HTMLInputElement>) {
    dispatch({
      type: "changed_name",
      nextName: e.target.value,
    })
  }

  return (
    <>
      <input value={state.name} onChange={handleInputChange} />
      <button onClick={handleButtonClick}>Increment age</button>
      <p>
        Hello, {state.name}. You are {state.age}.
      </p>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

We gather all our state transitions in reducer and call dispatch for different actions. It keeps logic in one place, but can get verbose.

F-Box React Approach

import { useRBox, set } from "f-box-react"

function useUserState(_name: string, _age: number) {
  const [name, nameBox] = useRBox(_name)
  const [age, ageBox] = useRBox(_age)

  return {
    user: { name, age },
    changeName(e: React.ChangeEvent<HTMLInputElement>) {
      set(nameBox)(e.target.value)
    },
    incrementAge() {
      ageBox.setValue((prev) => prev + 1)
    },
  }
}

export default function Form() {
  const { user, changeName, incrementAge } = useUserState("Taylor", 42)

  return (
    <>
      <input value={user.name} onChange={changeName} />
      <button onClick={incrementAge}>Increment age</button>
      <p>
        Hello, {user.name}. You are {user.age}.
      </p>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

By using useRBox, we don’t need a reducer function or action types.

We just split the state into small RBoxes and define the update logic directly, which keeps things simpler.

We can also wrap this in a custom hook to keep the logic organized and reusable across different components. Since useRBox’s returned values are reactive, any updates trigger a rerender automatically.

If you want multiple components to share the same state, just pass in a global RBox instead, like so:

import { RBox } from "f-box-core"
import { useRBox, set } from "f-box-react"

const userBoxes = {
  nameBox: RBox.pack("Taylor"),
  ageBox: RBox.pack(42),
}

function useUserState() {
  const { nameBox, ageBox } = userBoxes
  const [name] = useRBox(nameBox)
  const [age] = useRBox(ageBox)

  return {
    user: { name, age },
    changeName(e: React.ChangeEvent<HTMLInputElement>) {
      set(nameBox)(e.target.value)
    },
    incrementAge() {
      ageBox.setValue((prev) => prev + 1)
    },
  }
}

export default function Form() {
  const { user, changeName, incrementAge } = useUserState()

  return (
    <>
      <input value={user.name} onChange={changeName} />
      <button onClick={incrementAge}>Increment age</button>
      <p>
        Hello, {user.name}. You are {user.age}.
      </p>
    </>
  )
}
Enter fullscreen mode Exit fullscreen mode

Summary

Using F-Box React can really simplify React state management:

  1. It feels just like useState

    Pass in the initial value, call useRBox, and you’re good to go. It’s super easy to get started.

  2. RBox works outside React

    Because it’s independent from React, you can use it in other environments or on the server side.

  3. Sharing state is a breeze

    Just define a global RBox and call useRBox wherever you need it. No need for useContext or Redux. Less boilerplate!

Give It a Try

F-Box React is easy to set up and works great with TypeScript. If you’re looking to simplify your state management, give it a shot:

https://www.npmjs.com/package/f-box-react

https://github.com/KentaroMorishita/f-box-react

In Closing

We only scratched the surface of F-Box here—there are plenty of other features available.

You can combine it with async operations, error handling, and more complex scenarios.

For more details, check out the F-Box Docs.

I hope it makes your React and TypeScript development more enjoyable!

Top comments (0)