Multiple observers

If you have a large-scale application, or you want to manage different the state of each part of your app separately, you can create multiple instances of the Observer class for this purpose.

If you to make instance of the Observer class, you must import the Observer from rosma and create an instance of it using the new keyword.

import { Observer } from 'rosma'; const myObserver = new Observer();
import { Observer } from 'rosma'; const myObserver = new Observer();

Note that you can pass the initial values of the state to the constructor of the observer class.

import { Observer } from 'rosma'; const myObserver = new Observer({ foo: 'bar' }); console.log(myObserver.state.foo); //"bar"
import { Observer } from 'rosma'; type State = { foo: string; }; const myObserver = new Observer<State>({ foo: 'bar' }); console.log(myObserver.state.foo); //"bar"

Also, in order for the useObserver hook to use your personal observer, you can send your own observer to it.

import { Observer, useObserver } from 'rosma'; const myObserver = new Observer({ random: 'Click Generate to generate random number', }); function App() { const { random, setRandom } = useObserver(myObserver); return ( <> <p>{random}</p> <button onClick={generate}>Generate</button> </> ); function generate() { setRandom(Math.random() * 1000); } }
import { Observer, useObserver } from 'rosma'; type State = { random: string | number; }; const myObserver = new Observer<State>({ random: 'Click Generate to generate random number', }); function App() { const { random, setRandom } = useObserver<State>(myObserver); return ( <> <p>{random}</p> <button onClick={generate}>Generate</button> </> ); function generate() { setRandom(Math.random() * 1000); } }

Click Generate to generate random number

Custom useObserver hook

In order to not repeat passing the observer to the useObserver hook throughout your app, you can export your personal hook once and import it everywhere in the app, as in the example below.

import { Observer, useObserver } from 'rosma'; const initialUIState = { mustShowSidebar: true }; const initialAuthState = { user: undefined, token: undefined }; const initialDataState = { posts: [], users: [] }; export const UIObserver = new Observer(initialUIState); export const authObserver = new Observer(initialAuthState); export const dataObserver = new Observer(initialDataState); export function useUIObserver() { return useObserver(UIObserver); } export function useAuthObserver() { return useObserver(authObserver); } export function useDataObserver() { return useObserver(dataObserver); }
import { Observer, useObserver } from 'rosma'; type UIState = { mustShowSidebar: boolean; }; type AuthState = { user?: User; token?: string; }; type DataState = { posts: Post[]; users: User[]; }; const initialUIState: UIState = { mustShowSidebar: true }; const initialAuthState: AuthState = { user: undefined, token: undefined }; const initialDataState: DataState = { posts: [], users: [] }; export const UIObserver = new Observer<UIState>(initialUIState); export const authObserver = new Observer<AuthState>(initialAuthState); export const dataObserver = new Observer<DataState>(initialDataState); export function useUIObserver() { return useObserver<UIState>(UIObserver); } export function useAuthObserver() { return useObserver<AuthState>(authObserver); } export function useDataObserver() { return useObserver<DataState>(dataObserver); }

This example is demonstrating how to create and export multiple instances of the Observer class with different initial states, using the new keyword. Three instances of the observer are created with the names UIObserver, authObserver, and dataObserver. Each instance has a different initial state, which is defined using objects.

In addition to creating the observers, we exports three functions, useUIObserver(), useAuthObserver(), and useDataObserver(), which use the useObserver hook to provide access to the respective observers throughout the application.

This approach can help in managing the state of different parts of an application separately. By having separate observers, you can easily modify the state of a particular part of the app without affecting the state of the other parts.

Separating observers

To keep your code clean, it is better to keep the observers for each part of your app in a separate file. For example, the above code can be placed in three separate files as shown below:

UI part

src/observers/ui/observer.ts

import { Observer, useObserver } from 'rosma'; const initialUIState = { mustShowSidebar: true }; export const UIObserver = new Observer(initialUIState); export function useUIObserver() { return useObserver(UIObserver); }
import { Observer, useObserver } from 'rosma'; type UIState = { mustShowSidebar: boolean; }; const initialUIState: UIState = { mustShowSidebar: true }; export const UIObserver = new Observer<UIState>(initialUIState); export function useUIObserver() { return useObserver<UIState>(UIObserver); }

src/observers/ui/actions.ts

import { UIObserver } from './observer'; export function toggleSidebar() { UIObserver.set({ mustShowSidebar: !UIObserver.state.mustShowSidebar }); }
import { UIObserver } from './observer'; export function toggleSidebar() { UIObserver.set({ mustShowSidebar: !UIObserver.state.mustShowSidebar }); }

Auth part

src/observers/auth/observer.ts

import { Observer, useObserver } from 'rosma'; const initialAuthState = { user: undefined, token: undefined }; export const authObserver = new Observer(initialAuthState); export function useAuthObserver() { return useObserver(authObserver); }
import { Observer, useObserver } from 'rosma'; type AuthState = { user?: User; token?: string; }; const initialAuthState: AuthState = { user: undefined, token: undefined }; export const authObserver = new Observer<AuthState>(initialAuthState); export function useAuthObserver() { return useObserver<AuthState>(authObserver); }

src/observers/auth/actions.ts

import { authObserver } from './observer'; export function login({ username, password }) { // logics for login authObserver.set({ user, token }); } export function logout() { // logics for logout authObserver.set({ user: undefined, token: undefined }); }
import { authObserver } from './observer'; export function login({ username, password }) { // logics for login authObserver.set({ user, token }); } export function logout() { // logics for logout authObserver.set({ user: undefined, token: undefined }); }

Data part

src/observers/data/observer.ts

import { Observer, useObserver } from 'rosma'; const initialDataState = { posts: [], users: [] }; export const dataObserver = new Observer(initialDataState); export function useDataObserver() { return useObserver(dataObserver); }
import { Observer, useObserver } from 'rosma'; type DataState = { posts: Post[]; users: User[]; }; const initialDataState: DataState = { posts: [], users: [] }; export const dataObserver = new Observer<DataState>(initialDataState); export function useDataObserver() { return useObserver<DataState>(dataObserver); }

src/observers/data/actions.ts

import { dataObserver } from './observer'; export function getPosts() { // logics for getting posts dataObserver.set({ posts }); } export function getUsers() { // logics for getting users dataObserver.set({ users }); }
import { dataObserver } from './observer'; export function getPosts() { // logics for getting posts dataObserver.set({ posts }); } export function getUsers() { // logics for getting users dataObserver.set({ users }); }

Note: This example is given in order to provide a concept of how to manage the state in an app. The parts provided above are for illustrative purposes. For instance, in the data section, it is advisable to use more specialized libraries for fetch and caching data, like react-query.

Keywords: React, rosma, observer, useObserver, multiple observers, custom useObserver

Previous: Observer class

Next: Subscribe for state changes