Reactivity
Obsidian is first and foremost a dependency injection library. But it also includes reactive programming features that allow you to observe changes in your data and react to them. This is useful for things like updating the UI when a value changes.
Observable
Making a class field reactive is as simple as wrapping it with the Observable
decorator. Observable is a data holder classes that also allows you to subscribe to changes in the data.
Create observables
In the example below, we declare a boolean observable called isLoggedIn
. We can then subscribe to changes in the isLoggedIn
value and update the UI accordingly.
import { Observable } from 'react-obsidian';
class UserService {
public isLoggedIn = new Observable(false); // The initial value is false
}
export default new UserService();
Observe changes
Observe changes in hooks or components
Once you have declared an observable, you can subscribe to changes in the value by using the useObserver
hook. This hook will return the current value of the observable and subscribe to changes in the value. When the value changes, the hook will re-render the component.
import { useObserver } from 'react-obsidian';
import userService from './UserService';
const useLogin = () => {
const [isLoggedIn] = useObserver(userService.isLoggedIn);
return {isLoggedIn};
}
Observe changes imperatively
You can also subscribe to changes in an observable imperatively by calling the subscribe
method on the observable. This method returns a function that can be called to unsubscribe from the observable.
import userService from './UserService';
const unsubscribe = userService.isLoggedIn.subscribe((isLoggedIn: boolean) => {
// Do something with the isLoggedIn value
});
Update Observables
Observables expose the data they hold via a value
property. You can update the value of an observable by setting the value of the value
property.
This will also trigger any subscribers to the observable.
import userService from './UserService';
userService.isLoggedIn.value = true; // Update the value and notify all subscribers
Update Observables from hooks or components
The useObserver
hook also returns a setter function that can be used to update the value of the observable.
import { useObserver } from 'react-obsidian';
import userService from './UserService';
const useLogin = () => {
const [isLoggedIn, setIsLoggedIn] = useObserver(userService.isLoggedIn);
const onLogoutButtonPress = useCallback(() => {
setIsLoggedIn(false);
}, [setIsLoggedIn]);
return {isLoggedIn, onLogoutButtonPress};
}
Avoid recreating the initial observable
When using the useObserver
hook, it is important to avoid recreating the initial observable.
const useLogin = () => {
const [isLoggedIn] = useObserver(new Observable(false));
}
Even if the value of the observable is the same, this can cause unexpected behavior since it's instantiated on every render.
To solve this, you can pass a generator function to the useObserver hook instead:
const useLogin = () => {
const [isLoggedIn] = useObserver(() => new Observable(false));
}
If you pass a function to the useObserver hook, it will only be called on the first render. This ensures that the observable is only instantiated once.
Merge multiple observable sources
MediatorObservable
is a special type of observable that allows you to merge multiple observable sources into a single observable. This is useful for creating side effect from one or more observables.
In the example below, we create a MediatorObservable
called downloadStatus
that will be updated when either the networkConditions
or batteryLevel
observables are updated. We can then subscribe to changes in the downloadStatus
observable to update the UI or perform other side effects.
import { MediatorObservable, Observable } from 'react-obsidian';
const networkConditions = new Observable<'poor' | 'strong'>();
const batteryLevel = new Observable<'low' | 'normal'>();
const downloadStatus = new MediatorObservable<'paused' | 'active'>('active')
.addSource(networkConditions, (condition: 'poor' | 'strong') => {
this.value = condition === 'poor' ? 'paused' : 'active';
})
.addSource(batteryLevel, (level: 'low' | 'normal') => {
this.value = level === 'low' ? 'paused' : 'active';
});