What Is the $$observable Symbol in Redux? TC39 Observable Interop Explained
The $$observable symbol in Redux serves as the interoperability point for the TC39 Observable proposal, allowing libraries like RxJS to treat the Redux store as a reactive stream using store[$$observable]().
The reduxjs/redux library implements the $$observable symbol to provide a standardized mechanism for reactive programming libraries to subscribe to state changes without coupling Redux to any specific observable implementation. This lightweight interop point enables powerful stream-based state management while maintaining a minimal core bundle size.
Defining the $$observable Symbol in Redux Source Code
The symbol is defined in src/utils/symbol-observable.ts as a cross-environment compatible identifier:
// src/utils/symbol-observable.ts
const $$observable = /* #__PURE__ */ (() =>
(typeof Symbol === 'function' && Symbol.observable) || '@@observable')()
export default $$observable
This implementation checks for the native Symbol.observable defined by the TC39 proposal. If available, it uses that; otherwise, it falls back to the string "@@observable". This approach guarantees a stable, collision-free property name that any reactive library can reference consistently across different JavaScript environments.
How $$observable Exposes the Store as an Observable
In src/createStore.ts, the store factory attaches the observable implementation using the $$observable computed property key:
// src/createStore.ts (excerpt)
const store = {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable // <‑‑ the interop point
}
The observable() function closes over the store's subscribe method and returns an object implementing the minimal observable contract:
// src/createStore.ts (excerpt)
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) { … },
[$$observable]() {
return this
}
}
}
The returned object provides a subscribe method that accepts an observer object with a next callback, and a self-referencing [$$observable]() method that returns the observable instance. This minimal implementation satisfies the interop requirements of libraries like RxJS, most.js, and Bacon.js while keeping Redux independent of any reactive library dependencies.
Practical Usage: Converting Redux Store to RxJS Streams
Accessing the Minimal Observable Directly
You can obtain the raw observable from the store to subscribe to state changes:
import { createStore } from 'redux'
import rootReducer from './reducers'
import $$observable from './utils/symbol-observable' // internal symbol
const store = createStore(rootReducer)
// Obtain the minimal observable via the interop point
const observable = store[$$observable]()
// Subscribe using the observable API
const subscription = observable.subscribe({
next(state) {
console.log('New state via observable:', state)
},
})
// Later – unsubscribe
subscription.unsubscribe()
Using RxJS from() to Create a Full Observable
RxJS automatically recognizes the $$observable property and converts the store into a full-featured observable:
import { from } from 'rxjs'
import { distinctUntilChanged, map } from 'rxjs/operators'
import { createStore } from 'redux'
import rootReducer from './reducers'
const store = createStore(rootReducer)
// `from` recognises the $$observable interop point
const redux$ = from(store)
// Now you can use all RxJS operators
redux$
.pipe(
map(state => state.todos),
distinctUntilChanged()
)
.subscribe(todos => console.log('Todos from RxJS:', todos))
Verifying Fallback Behavior in Older Environments
The symbol utility ensures compatibility when Symbol.observable is undefined:
// Force the fallback path (e.g., in older browsers)
console.assert(typeof Symbol.observable === 'undefined')
console.assert($$observable === '@@observable')
Summary
- The
$$observablesymbol provides the TC39 Observable proposal interop point inreduxjs/redux - Defined in
src/utils/symbol-observable.ts, it usesSymbol.observablewhen available or falls back to"@@observable"for maximum compatibility - The store exposes a minimal observable implementation via
store[$$observable]()insrc/createStore.ts, using the store's internalsubscribemethod - The TypeScript declarations in
src/types/store.tsensure type safety for the observable property - Libraries like RxJS can convert Redux stores to full observables using
from(store)without adding dependencies to the Redux core
Frequently Asked Questions
What is the purpose of the $$observable symbol in Redux?
The $$observable symbol serves as a well-known interoperability point that allows the Redux store to be treated as an observable by reactive libraries. According to the reduxjs/redux source code in src/createStore.ts, it exposes a minimal observable implementation via store[$$observable](), enabling subscription to state changes through the standard observer contract without bundling RxJS or similar libraries into Redux itself.
How do I use Redux with RxJS?
You can convert a Redux store to an RxJS observable by importing from from RxJS and passing the store directly: const store$ = from(reduxStore). RxJS detects the $$observable property defined in src/createStore.ts and wraps the store's minimal observable implementation, allowing you to use operators like map, filter, and distinctUntilChanged on the state stream.
What happens if Symbol.observable is not available?
If the JavaScript environment does not define Symbol.observable, Redux falls back to the string "@@observable" as implemented in src/utils/symbol-observable.ts. This polyfill approach ensures the observable interop works in older browsers or environments that haven't implemented the TC39 proposal, maintaining compatibility across Node.js and legacy browser contexts.
Is the $$observable API stable for production use?
Yes, the $$observable implementation in Redux is stable and battle-tested. The test/createStore.spec.ts file in the reduxjs/redux repository contains comprehensive tests verifying the observable interop behavior, including the subscribe method and the self-referencing symbol property. The API follows the TC39 Observable proposal specification, making it safe to use in production applications requiring reactive state management integration.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →