Skip to main content

Redux Comparison

Zedux is more comparable to Redux Toolkit (RTK) than raw Redux, so that's what we'll compare against.

As its name suggests, Zedux originated from Redux. It actually started off as Redux middleware in early 2017. Zedux is now a comprehensive suite of state management tools that has several things in common with Redux Toolkit and lots of other features.

The Z in Zedux is for Zero-config. One of the main features of Zedux is its zero-config stores. This feature alone makes Zedux feel very different from Redux.

const store = createStore() // no config required!
store.setState('my state')

In this comparison, we won't use Zedux's React or atomic APIs. Zedux's React usage is vastly different from Redux', using Recoil-esque atoms to pass stores around.

TL;DR

  • Both Redux and Zedux stores can be given reducers to drive state creation and updates.
  • Zedux has zero-config stores.
  • In both, state should not be mutated.
  • RTK wraps reducers in immer producers. Zedux offers a separate @zedux/immer package.
  • Both have similar reducer splitting APIs.
  • Both have similar action creator and reducer creation APIs.
  • In both, actions and reducers have a many-to-many relationship.
  • In both, dispatched actions can be observed, saved, and replayed for a time travel debugging experience.
  • RTK uses createSlice() to create state slices. Zedux uses store composition 🤯.
  • Redux apps typically have one store. Zedux apps typically create many stores.
  • Both have similar state hydration APIs.
  • Redux uses middleware to manage side effects. Zedux uses effects subscribers.
  • Redux uses middleware for plugins. Zedux doesn't offer a dedicated plugin system at the store level (plugins are at the ecosystem level).
  • In both, stores are observables (streams) of state. In Zedux, stores are also observables of actions.
  • In Redux, synchrony depends on your middleware. In Zedux, store dispatches are completely synchronous.

Comparables

Many concepts are similar in both Zedux and Redux.

Reducers

RTK - all stores use a reducer.

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
reducer: rootReducer,
})

Zedux - stores can be given a reducer.

import { createStore } from '@zedux/react'

const store = createStore(rootReducer)

But this is optional (and not very common) in Zedux.

const zeroConfigStore = createStore()

Due to their simplicity, zero-config stores are extremely common in Zedux.

Reducer Splitting

RTK - use configureStore() and combineReducers().

import { combineReducers, configureStore } from '@reduxjs/toolkit'

const store = configureStore({
reducer: {
a: reducerA,
b: combineReducers({
c: reducerC,
d: reducerD,
}),
},
})

Zedux - use createStore().

import { createStore } from '@zedux/react'

const store = createStore({
a: reducerA,
b: {
c: reducerC,
d: reducerD,
},
})

The concept is almost the same in both. Zedux's createStore is a bit more high-level, handling nested objects.

Actions and Reducers

RTK - use createAction() and createReducer(). RTK also uses immer so you can "mutate" state.

import { createAction, createReducer } from '@reduxjs/toolkit'

const increment = createAction('counter/increment')
const decrement = createAction('counter/decrement')
const incrementByAmount = createAction<number>('counter/incrementByAmount')

const initialState = { value: 0 }

const counterReducer = createReducer(initialState, builder => {
builder
.addCase(increment, state => {
state.value++
})
.addCase(decrement, state => {
state.value--
})
.addCase(incrementByAmount, (state, action) => {
state.value += action.payload
})
})

Zedux - use actionFactory() and createReducer(). The @zedux/immer package doesn't support reducers (currently).

import { actionFactory, createReducer } from '@zedux/react'

const increment = actionFactory('counter/increment')
const decrement = actionFactory('counter/decrement')
const incrementByAmount = actionFactory<number>('counter/incrementByAmount')

const initialState = { value: 0 }

const counterReducer = createReducer(initialState)
.reduce(increment, state => state.value + 1)
.reduce(decrement, state => state.value - 1)
.reduce(incrementByAmount, (state, action) => state.value + action.payload)

State Slices

RTK - use createSlice().

import { configureStore, createSlice } from '@reduxjs/toolkit'

const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment(state) {
state.value++
},
decrement(state) {
state.value--
},
},
})

const store = configureStore({
reducer: {
counter: counterSlice.reducer,
},
})

store.dispatch(counterSlice.actions.increment())
store.dispatch(counterSlice.actions.decrement())

Zedux - use nested stores.

import { createStore } from '@zedux/react'

const counterStore = createStore(null, { value: 0 })

const increment = () =>
counterStore.setStateDeep(({ value }) => ({ value: value + 1 }))
const decrement = () =>
counterStore.setStateDeep(({ value }) => ({ value: value + 1 }))

const store = createStore({
counter: counterStore,
})

increment()
decrement()

Hydrating

RTK - pass preloadedState to configureStore().

import { configureStore } from '@reduxjs/toolkit'

const store = configureStore({
preloadedState: myData,
reducer: {},
})

Zedux - pass as the second param to createStore().

import { createStore } from '@zedux/react'

const store = createStore(null, myData)

Side Effects

RTK - use middleware.

import { configureStore } from '@reduxjs/toolkit'

const logger = store => next => action => {
const oldState = store.getState()
next(action)
const newState = store.getState()

console.log('store state updated', { newState, oldState })
}

const store = configureStore({
middleware: [logger],
reducer: rootReducer,
})

(note that this middleware depends on other middleware and store enhancers not disrupting the synchronous state update).

Zedux - use effects subscribers.

import { createStore } from '@zedux/react'

const store = createStore()

store.subscribe({
effects: ({ newState, oldState }) => {
console.log('store state updated', { newState, oldState })
},
})

(This is a very Zedux-favored example. Not all side effects flows are simpler in Zedux).

Performance

One of the primary reasons we created Zedux and switched to it from Redux at Omnistac is that Reselect selectors caused huge performance problems for highly volatile state. But as far as comparing Zedux to Redux itself:

Redux stores actually beat Zedux stores in most straight-up performance comparisons. But there's a giant factor that makes measuring the difference impossible: Zedux's atomic and composable store models are designed to split your state out into many different stores. The performance savings of this approach adds up more and more the bigger your app gets.

Bottom line: Zedux should be more performant than Redux in complex apps where performance is actually a concern. But again, it's impossible to say for sure. Here's one comparison where Zedux does beat Redux outright:

simplest store creation - Zedux: 213 million/sec; Redux: 16 million/sec

Benchmark here.

Just remember that this is a highly Zedux-favored comparison (Zedux has zero-config stores, Redux doesn't). Objective comparison is impossible between these different tools, and Zedux doesn't always win.

The store composition guide gives a little more insight.

No Middleware

Yep. It isn't necessary. Side effects like logging or running sagas or observables can all be effectively handled by Zedux's effects subscribers. Batching and canceling actions may not be possible, but those never were necessary at the store level - they can be handled just fine outside the store. Batching in particular is much less common in Zedux since the atomic model naturally makes all your state updates smaller and more granular.

In Redux, middleware can also be useful for adding plugins. Zedux has atoms, which can encapsulate the state, side effects, and exports of a "state slice". A plugin system at the store level isn't very necessary.

Some operations are easier without middleware and some are harder. Those should more or less balance out.

Read more about effects subscribers here.

Other Features

Zedux and Redux don't overlap perfectly. They each have some unique features. We won't go into RTK's here, but check out their docs.

Zedux has many features not mentioned here. Most of these revolve around the Recoil-esque atomic model and React Query-esque cache management. Check out those comparisons for more info. Or check out the walkthrough for a better picture of what Zedux is all about.