“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
oruseReducer
, 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
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
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
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
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
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
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
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>
</>
)
}
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>
</>
)
}
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>
</>
)
}
Summary
Using F-Box React can really simplify React state management:
It feels just like
useState
Pass in the initial value, calluseRBox
, and you’re good to go. It’s super easy to get started.RBox works outside React
Because it’s independent from React, you can use it in other environments or on the server side.Sharing state is a breeze
Just define a global RBox and calluseRBox
wherever you need it. No need foruseContext
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)