Venti—Simple State Management for React

It’s now a latte easier to manage state for React.

5 min readJan 2, 2020

--

There are many options for managing state in React: Redux, MobX, Apollo, Context API, Hooks API, and the list goes on. However, they’re arguably a bit too complicated for the casual React developer — even seasoned software engineers often struggle to get it right.

So, I came up with Venti to make React fun again.

20oz. to Freedom

The goal was to create the simplest pattern possible for state management in React. Fortunately, React 16.8 introduced the Hooks API which consequently made Venti possible.

Anyway, let’s take a look at some code and I’ll let you judge for yourself:

Using Global State in Your Component

  1. Call useVenti() to get the global state
  2. Call state.get() to subscribe to a specific value
import { useVenti } from 'venti'function MyComponent() {
const state = useVenti()
const myValue = state.get('path.to.myValue')
return <div>My value is "{myValue}"</div>
}

state.get() uses lodash.get under the hood so the API should be very familiar.

If the value changes, then the component updates. That’s it.

No “higher order components”, no “providers”, no “props”, no “decorators”, no “observables”.

Keep in mind, the path to a value is in “dot notation”. For example, consider these two lines which both get the title of a book:

// updates the component when any property of the book changes
const { title } = state.get('book')
// updates the component only when the title of the book changes
const
title = state.get('book.title')

Okay, so displaying state is easy, but how about changing the state of our app?

Modifying Global State

  1. Call state.set() to modify the state
import { state } from 'venti'

state.set('path.to.myValue', 'very nice')

state.set() updates the value in the global state object, then tells the applicable components to update. lodash.set is used under the hood, so the API should again be very familiar.

That’s it.

No “reducers”, no “immutable store”, no “dispatch”, no “action creators”, no “middleware”, no “boilerplate”. You can KISS cognitive overload goodbye.

Also, you can set your state anytime, anywhere you want — no special vocabulary or patterns to learn. This makes it async-friendly without learning about new concepts like “thunks”, “sagas”, “mutations” or whatnot.

Immutability is Optional

Getting and setting immutable values makes Venti faster at detecting state changes, so keep that in mind if shaving away every millisecond matters to you. But from my experience with typical web apps, any mutation-induced latency with Venti is unnoticeable (see benchmarks below).

The point is, immutability is not something you need to be concerned with when using Venti. Forget I even mentioned it!

It Just Works™, But How?

Venti uses an event-based architecture to update components when the state changes.

When you useVenti() in a component, a state object is returned that is instrumented specifically for that component. Behind the scenes, a useEffect hook keeps track of the state.get(path) calls in that component. A subscription is created for each state path the component needs.

When the state is modified anywhere else in the app, the components that are subscribed to the paths whose values have been changed are force updated.

Venti’s secret sauce is determining, as quickly as possible, which paths have modified values after a state change. In Redux, you essentially write this logic yourself in what they call a “reducer”.

Venti takes care of this for you. However, this added layer of convenience does come with a performance tradeoff.

Performance

What is the cost of simplicity? Let’s find out with some benchmarks.

There are many read/write patterns that we can try to benchmark, but let’s just make a general performance comparison by filling 100 boxes with random colors as many times as possible in 30 seconds.

You’ll notice from the screenshot below, Venti and Redux were able to color 1,024 boxes and 1,646 boxes, respectively, within 30 seconds on my old MacBook. That means Redux performed 1.6 times as fast as Venti.

The color matrix benchmark shows Redux is about 60% faster than Venti

Try for yourself: [Venti benchmark] [Redux benchmark]

Despite being slower than Redux here, it’s still pretty good in my opinion, especially on 6-year-old hardware. After (subjectively) factoring in the much better developer experience, Venti is still a great option for most of my use cases.

Other Tradeoffs

Venti was designed for simplicity and aims to have as few pitfalls as possible, but alas:

  1. Avoid large deeply nested objects
    Since Venti doesn’t rely on immutability, it may be wise to avoid large deeply nested objects in your state due to the performance overhead of comparing them.
  2. No “time travel”
    Redux logs “actions” which makes it easy to undo/replay/debug state changes. Venti doesn’t do this. But you can roll your own state change logger with state.on(cb).
  3. Unopinionated
    Venti doesn’t tell you how to structure your code. You’re on your own!

Should I Use It?

If “time-travel” undo/replay/debugging is important to you, then Redux may be a good option.

If you are exclusively using GraphQL to fetch remote data, then Apollo may be worth considering.

If you’re a fan of decorator syntax, then MobX is definitely something to look into.

On the other hand, if you just want something easy and want to write less code, then give Venti a try.

Conclusion

I love React, but Redux et al. have been harshing my mellow. Venti is a game changer for me as a casual React developer. My React apps have significantly fewer lines of code now and my development velocity has increased big time. It feels good.

Venti may not be appropriate for all use cases, but it’s really easy to build apps quickly!

For me, that’s huge.

Get Started With Venti

https://github.com/will123195/venti#readme

--

--