Observer class

Observer class is a tool that allows you to declare global state variables and update them from anywhere in your application.

In addition to updating state values, you can also observe them using Observer to be notified when they change.

By default, Rosma uses a single instance of the Observer class. However, you can use multiple instances of the Observer class to better manage your project. This will be explained in the next sections.

The Observer class provides several methods, which are listed below:

observer.get

The observer.get method is used to retrieve values from the state. You can use this method to get one or more values simultaneously.

Getting a Single Value

To retrieve a single value from the state, you must pass the key of the variable as an argument to the get method.

For example, suppose the value of the foo in the state is equal to 'bar'. In this case, you can retrieve the value of the foo and assign it to a variable as follows:

import { observer } from 'rosma'; const foo = observer.get('foo'); console.log(foo); // bar
import { observer } from 'rosma'; const foo: string = observer.get('foo'); console.log(foo); // bar

Getting Multiple Values

If you want to retrieve more than one value from the state, you must pass the desired keys as an array to the get method. In this case, the value returned from the get method is an object.

For example, suppose our state contains values of foo equal to 'bar' and baz equal to 'qux'. To retrieve the values of foo and baz at the same time, you can use the following code:

import { observer } from 'rosma'; const values = observer.get(['foo', 'baz']); console.log(values); // { foo: 'bar', baz: 'qux' }
import { observer } from 'rosma'; type State = { foo: string; baz: string; }; const values = observer.get<State>(['foo', 'baz']); console.log(values); // { foo: 'bar', baz: 'qux' }

You can also destructure the returned object to assign each value to a separate variable, like this:

import { observer } from 'rosma'; const { foo, baz } = observer.get(['foo', 'baz']); console.log(foo, baz); // 'bar', 'qux'
import { observer } from 'rosma'; type State = { foo: string; baz: string; }; const { foo, baz } = observer.get<State>(['foo', 'baz']); console.log(foo, baz); // 'bar', 'qux'

observer.set

The set method is used to change or add one or more values in the state. When a value in the state is changed using the set method, all components that use that value will be rerendered. However, it's also possible to change a value silently, without triggering a rerender of the components that use it.

Note that if you set a value silently, and a component using that value is later rerendered for any other reasons, the new value will be received from the state.

Demo

import { observer, useObserver } from 'rosma'; export function DisplayTime() { const { time, setTime } = useObserver(''); return ( <div> <p>Time is: {time}</p> <Button onClick={() => setTime(getTime())}> Update time with setter method </Button> <Button onClick={() => observer.set({ time: getTime() })}> Update time with observer.set </Button> <Button onClick={() => observer.set({ time: getTime() }, { silent: true })} > Update time silently </Button> </div> ); } function getTime() { return new Date().toLocaleTimeString(); } function Button(props) { return <button {...props} style={{ display: 'block' }} />; }
import { observer, useObserver } from 'rosma'; type State = { time: string; }; export function DisplayTime() { const { time, setTime } = useObserver<State>(''); return ( <div> <p>Time is: {time}</p> <Button onClick={() => setTime(getTime())}> Update time with setter method </Button> <Button onClick={() => observer.set<State>({ time: getTime() })}> Update time with observer.set </Button> <Button onClick={() => observer.set<State>({ time: getTime() }, { silent: true }) } > Update time silently </Button> </div> ); } function getTime() { return new Date().toLocaleTimeString(); } function Button(props) { return <button {...props} style={{ display: 'block' }} />; }

Time is:


In this example, the DisplayTime component retrieves the time value from the Observer state using useObserver and displays it in a paragraph element.

The component also includes three buttons that allow the user to update the time value in different ways. The first button calls the setTime method to update the value using the useObserver hook. The second button calls the observer.set method to update the value directly in the Observer state. The third button calls the observer.set method with the silent option to update the value silently, without triggering a rerender of any components that use the time value.

Modals Example

This code is a simple example of using global state management with the Observer tool. It allows the user to open and close modal components and displays them in the app.

export type Modal = { title: string; body: string; id?: number; }; export type State = { modals: Modal[]; };

app.tsx

The App component is the entry point of the application. It contains a button that triggers the opening of a modal and renders the Modals component that shows all open modals. The newModal function is imported from actions.ts to handle the opening of the new modal.

import Modals from './modals'; import { newModal } from './actions'; export function App() { return ( <> <button onClick={() => newModal({ title: 'Modal title', body: 'Modal body' })} > Open Modal </button> <Modals /> </> ); }
import Modals from './modals'; import { newModal } from './actions'; export function App() { return ( <> <button onClick={() => newModal({ title: 'Modal title', body: 'Modal body' })} > Open Modal </button> <Modals /> </> ); }

modals.tsx

The Modals component uses the useObserver hook to get the modals array from the state, which holds the data for each open modal. The map function is used to render a Modal component for each open modal. The Modal component is responsible for rendering the modal component with its title, body, and close button.

import { useObserver } from 'rosma'; import { closeModal } from './actions'; export default function Modals() { const { modals } = useObserver([]); return modals.map((modal, index) => <Modal key={index} {...modal} />); } function Modal({ title, body, id }) { return ( <div style={{ backgroundColor: 'white', boxShadow: '0 0 5px #ccc', minWidth: '300px', position: 'fixed', left: '50%', top: '50%', transform: 'translate(-50%, -50%)', borderRadius: '7px', }} > <div style={{ display: 'flex', padding: '10px', borderBottom: '1px solid #ccc', gap: '5px', }} > <span>{title}</span> <span>#{id}</span> <div style={{ flex: 1 }} /> <button onClick={() => closeModal(id)}>x</button> </div> <div style={{ padding: '10px' }}>{body}</div> </div> ); }
import { useObserver } from 'rosma'; import { closeModal } from './actions'; import { Modal, State } from './types'; export default function Modals() { const { modals } = useObserver<State>([]); return ( <> {modals.map((modal, index) => ( <Modal key={index} {...modal} /> ))} </> ); } function Modal({ title, body, id }: Modal) { return ( <div style={{ backgroundColor: 'white', boxShadow: '0 0 5px #ccc', minWidth: '300px', position: 'fixed', left: '50%', top: '50%', transform: 'translate(-50%, -50%)', borderRadius: '7px', }} > <div style={{ display: 'flex', padding: '10px', borderBottom: '1px solid #ccc', gap: '5px', }} > <span>{title}</span> <span>#{id}</span> <div style={{ flex: 1 }} /> <button onClick={() => closeModal(id)}>x</button> </div> <div style={{ padding: '10px' }}>{body}</div> </div> ); }

actions.ts

The actions file exports two functions: newModal and closeModal. Both of these functions use the observer instance provided by the Rosma library to get and set the modals array in the global state. The newModal function creates a new modal object with a unique id, title, and body and adds it to the modals array. The closeModal function filters out the modal with the given id from the modals array.

import { observer } from 'rosma'; export function newModal({ title, body }) { const modals = observer.get('modals') || []; const modal = { id: modals.length + 1, title, body, }; modals.push(modal); observer.set({ modals }); } export function closeModal(id) { const modals = observer.get('modals') || []; observer.set({ modals: modals.filter((modal) => modal.id !== id) }); }
import { observer } from 'rosma'; import { Modal, State } from './types'; export function newModal({ title, body }: Modal) { const modals: Modal[] = observer.get('modals') || []; const modal: Modal = { id: modals.length + 1, title, body, }; modals.push(modal); observer.set<State>({ modals }); } export function closeModal(id: number) { const modals: Modal[] = observer.get('modals') || []; observer.set<State>({ modals: modals.filter((modal) => modal.id !== id) }); }

Demo

observer.state

Returns the current value of the state.

As mentioned previously, all variables in the state are stored in lowercase, while the names retrieved from the state values are not case-sensitive.

Note that mutating the state using observer.state does not re-render the components or trigger listeners.

import { observer } from 'rosma'; observer.set({ foo: 'bar' }); console.log(observer.state.foo); // "bar"
import { observer } from 'rosma'; observer.set({ foo: 'bar' }); console.log(observer.state.foo); // "bar"

observer.isValid

observer.get checks whether a given key exists in the state or not. it gets a string as the key and determines whether that variable is defined in the state or not.

import { observe } from 'rosma'; observer.isValid('myVariable'); // false observer.set({ myVariable: 'something' }); observer.isValid('myVariable'); // true
import { observe } from 'rosma'; observer.isValid('myVariable'); // false observer.set({ myVariable: 'something' }); observer.isValid('myVariable'); // true

observer.subscribe

The subscribe method is utilized to monitor changes in one or more variables in the state. This method requires two input parameters:

  1. The desired key or keys that you want to observe.

  2. A listener that will execute when the desired key or keys have changed.

The method returns an unsubscribe function, allowing you to stop listening to state changes when needed. It can be used within useEffect or outside of your React component, depending on your needs. If you need to monitor the changes of multiple variables at the same time, you can provide them as an array and pass the as the first parameter. However, note that in this case, the desired values will be passed to the listener function as an object.

We will talk more about subscribe in the next sections.

Demo



import { observer, useObserver } from 'rosma'; const unsubscribe = observer.subscribe('myVar', listener); function listener(myVar) { alert('myVar changed! ' + myVar); } export function ObserverTest() { const { setMyVar } = useObserver(); return ( <> <button onClick={() => setMyVar(new Date())}>Click me</button> <button onClick={unsubscribe}>Unsubscribe</button> </> ); }
import { observer, useObserver } from 'rosma'; type State = { myVar: Date; }; const unsubscribe = observer.subscribe<State>('myVar', listener); function listener(myVar: State['myVar']) { alert('myVar changed! ' + myVar); } export function ObserverTest() { const { setMyVar } = useObserver<State>(); return ( <> <button onClick={() => setMyVar(new Date())}>Click me</button> <button onClick={unsubscribe}>Unsubscribe</button> </> ); }

This code imports the observer and useObserver from the rosma library. It then subscribes to changes of a variable named myVar by calling observer.subscribe and passing in the name of the variable and a listener function that will be called whenever the variable changes.

The ObserverTest component uses useObserver to get the setMyVar function, which can be called to update the value of myVar. The component renders a button that, when clicked, calls setMyVar with a new date object as its argument. When myVar changes, the listener function is called and an alert is displayed with the updated value of myVar.

If the user clicks on the unsubscribe button. The unsubscribe method is executed and the listener function is no longer called with the myVar changes.

Keywords: Observer, global state variables, update state, observe state, multiple instances, observer.get, observer.set, observer.isValid, observer.subscribe, retrieve values, get single value, get multiple values, change or add values, silently, Demo, modals example, app.tsx, modals.tsx

Previous: useObserver hook

Next: Multiple observers