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;
}
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();
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>
);
}
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 });
}
}
Problems RxJS Store Solves
Redux Boilerplate
Redux: Actions, reducers, types, selectors in separate files
RxJS Store: Everything in one class, minimal codeDerived Values
Zustand: Calculations in components or through additional selectors
RxJS Store: Selectors automatically recalculate valuesAsync Operations
Redux: Redux-Thunk/Saga for async operations
RxJS Store: RxJS operators work withasyncnativelyAutomatic Unsubscription
Zustand: Manual cleanup inuseEffect
RxJS Store:useObservableautomatically unsubscribesState Composition
Redux: Complex selectors or multiple useSelector calls
RxJS Store: combineLatest elegantly merges streamsRequest Cancellation
Redux/Zustand:AbortController+ manual management
RxJS Store:switchMapautomatically 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)