DEV Community

rshlinchak
rshlinchak

Posted on

RxJS Store: A Simple Alternative to Redux and Zustand in React

I want to share an approach to state management in React that solves most problems with minimal code. RxJS Store combines the simplicity of Zustand with the power of reactive programming.

What is RxJS Store

RxJS Store is a state management pattern using BehaviorSubject and RxJS operators. No third-party libraries needed, just what's already in the ecosystem.

Integration requires only one hook:

function useObservable<T>(observable: Observable<T>, initialValue: T): T {
  const [value, setValue] = useState<T>(initialValue);

  useEffect(() => {
    const subscription = observable.subscribe(setValue);
    return () => subscription.unsubscribe();
  }, [observable]);

  return value;
}
Enter fullscreen mode Exit fullscreen mode

Creating a Store:

interface AppState {
  user: User | null;
  theme: 'light' | 'dark';
  notifications: Notification[];
}

class AppStore {
  private state$ = new BehaviorSubject<AppState>({
    user: null,
    theme: 'light',
    notifications: []
  });

  user$ = this.state$.pipe(map(state => state.user));
  theme$ = this.state$.pipe(map(state => state.theme));
  unreadCount$ = this.state$.pipe(
    map(state => state.notifications.filter(n => !n.read).length)
  );

  setUser(user: User | null) {
    this.updateState({ user });
  }

  setTheme(theme: 'light' | 'dark') {
    this.updateState({ theme });
  }

  addNotification(notification: Notification) {
    const current = this.state$.value;
    this.updateState({
      notifications: [...current.notifications, notification]
    });
  }

  private updateState(partial: Partial<AppState>) {
    const current = this.state$.value;
    this.state$.next({ ...current, ...partial });
  }
}
export const appStore = new AppStore();
Enter fullscreen mode Exit fullscreen mode

Using in Components:

function Header() {
  const user = useObservable(appStore.user$, null);
  const theme = useObservable(appStore.theme$, 'light');
  const unreadCount = useObservable(appStore.unreadCount$, 0);

  return (
    <header className={theme}>
      {user ? (
        <div>
          Welcome, {user.name}! 
          {unreadCount > 0 && <span>({unreadCount})</span>}
        </div>
      ) : (
        <button>Login</button>
      )}
      <button onClick={() => appStore.setTheme(theme === 'light' ? 'dark' : 'light')}>
        Toggle theme
      </button>
    </header>
  );
}
Enter fullscreen mode Exit fullscreen mode

Async Operations:

class UserStore {
  private state$ = new BehaviorSubject({
    user: null,
    loading: false,
    error: null
  });

  user$ = this.state$.pipe(map(state => state.user));
  loading$ = this.state$.pipe(map(state => state.loading));
  error$ = this.state$.pipe(map(state => state.error));

  loadUser(userId: string) {
    this.updateState({ loading: true, error: null });

    from(fetch(`/api/users/${userId}`).then(res => res.json())).pipe(
      tap(user => this.updateState({ user, loading: false })),
      catchError(error => {
        this.updateState({ loading: false, error: error.message });
        return EMPTY;
      })
    ).subscribe();
  }

  private updateState(partial: any) {
    const current = this.state$.value;
    this.state$.next({ ...current, ...partial });
  }
}

Enter fullscreen mode Exit fullscreen mode

Problems RxJS Store Solves

  1. Redux Boilerplate
    Redux: Actions, reducers, types, selectors in separate files
    RxJS Store: Everything in one class, minimal code

  2. Derived Values
    Zustand: Calculations in components or through additional selectors
    RxJS Store: Selectors automatically recalculate values

  3. Async Operations
    Redux: Redux-Thunk/Saga for async operations
    RxJS Store: RxJS operators work with async natively

  4. Automatic Unsubscription
    Zustand: Manual cleanup in useEffect
    RxJS Store: useObservable automatically unsubscribes

  5. State Composition
    Redux: Complex selectors or multiple useSelector calls
    RxJS Store: combineLatest elegantly merges streams

  6. Request Cancellation
    Redux/Zustand: AbortController + manual management
    RxJS Store: switchMap automatically cancels previous requests

When to Use RxJS Store

  • Complex state with derived values
  • Multiple async operations
  • Real-time data and WebSocket
  • Combining data from different sources

Conclusion

RxJS Store is a universal tool that helps solve most state management tasks and makes code cleaner, more elegant, and truly reactive.
The main advantage is you don't have to choose between simplicity and power. RxJS Store gives you both.

Top comments (0)