Lightweight React bindings for MobX based on React 16.8+ and Hooks
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Hooks for creating and managing observable state within React components. These hooks provide component-local observable state that integrates seamlessly with the React component lifecycle and MobX reactivity system.
Creates an observable object using the provided initializer function. This is the recommended way to create local observable state in functional components.
/**
* Creates an observable object with the given properties, methods and computed values
* @param initializer - Function that returns the initial state object
* @param annotations - Optional MobX annotations for fine-tuned observability control
* @returns Observable state object with stable reference
*/
function useLocalObservable<TStore extends Record<string, any>>(
initializer: () => TStore,
annotations?: AnnotationsMap<TStore, never>
): TStore;Usage Examples:
import { observer, useLocalObservable } from "mobx-react-lite";
import { computed, action } from "mobx";
// Basic observable state
const Counter = observer(() => {
const store = useLocalObservable(() => ({
count: 0,
increment() {
this.count++;
},
decrement() {
this.count--;
},
get doubled() {
return this.count * 2;
}
}));
return (
<div>
<p>Count: {store.count}</p>
<p>Doubled: {store.doubled}</p>
<button onClick={store.increment}>+</button>
<button onClick={store.decrement}>-</button>
</div>
);
});
// With explicit annotations
const TodoList = observer(() => {
const store = useLocalObservable(
() => ({
todos: [],
filter: 'all',
addTodo(text: string) {
this.todos.push({ id: Date.now(), text, completed: false });
},
toggleTodo(id: number) {
const todo = this.todos.find(t => t.id === id);
if (todo) todo.completed = !todo.completed;
},
get visibleTodos() {
switch (this.filter) {
case 'active': return this.todos.filter(t => !t.completed);
case 'completed': return this.todos.filter(t => t.completed);
default: return this.todos;
}
}
}),
{
addTodo: action,
toggleTodo: action,
visibleTodos: computed
}
);
return (
<div>
<input
onKeyDown={(e) => {
if (e.key === 'Enter') {
store.addTodo(e.currentTarget.value);
e.currentTarget.value = '';
}
}}
/>
{store.visibleTodos.map(todo => (
<div key={todo.id} onClick={() => store.toggleTodo(todo.id)}>
{todo.completed ? '✓' : '○'} {todo.text}
</div>
))}
</div>
);
});
// Integration with props
import { useEffect } from "react";
interface User {
name: string;
email: string;
}
// Mock API function
declare function fetchUser(userId: string): Promise<User>;
const UserProfile = observer(({ userId }: { userId: string }) => {
const store = useLocalObservable(() => ({
user: null as User | null,
loading: true,
error: null as string | null,
async loadUser() {
this.loading = true;
this.error = null;
try {
this.user = await fetchUser(userId);
} catch (err) {
this.error = err.message;
} finally {
this.loading = false;
}
}
}));
useEffect(() => {
store.loadUser();
}, [userId, store]);
if (store.loading) return <div>Loading...</div>;
if (store.error) return <div>Error: {store.error}</div>;
if (!store.user) return <div>User not found</div>;
return (
<div>
<h1>{store.user.name}</h1>
<p>{store.user.email}</p>
</div>
);
});Creates an observable store using an initializer function. This hook is deprecated in favor of useLocalObservable.
/**
* @deprecated Use useLocalObservable instead
* Creates local observable state with optional source synchronization
* @param initializer - Function that returns the initial store object
* @param current - Optional source object to synchronize with
* @returns Observable store object
*/
function useLocalStore<TStore extends Record<string, any>>(
initializer: () => TStore
): TStore;
function useLocalStore<TStore extends Record<string, any>, TSource extends object>(
initializer: (source: TSource) => TStore,
current: TSource
): TStore;Migration Example:
// Old approach (deprecated)
const store = useLocalStore(() => ({
count: 0,
increment() { this.count++; }
}));
// New approach (recommended)
const store = useLocalObservable(() => ({
count: 0,
increment() { this.count++; }
}));Converts any set of values into an observable object with stable reference. This hook is deprecated due to anti-pattern of updating observables during rendering.
/**
* @deprecated Use useEffect to synchronize non-observable values instead
* Converts values into an observable object with stable reference
* @param current - Source object to make observable
* @returns Observable version of the source object
*/
function useAsObservableSource<TSource extends object>(current: TSource): TSource;Migration Example:
// Old approach (deprecated)
function Measurement({ unit }: { unit: string }) {
const observableProps = useAsObservableSource({ unit });
const state = useLocalStore(() => ({
length: 0,
get lengthWithUnit() {
return observableProps.unit === "inch"
? `${this.length / 2.54} inch`
: `${this.length} cm`;
}
}));
return <h1>{state.lengthWithUnit}</h1>;
}
// New approach (recommended)
import { useEffect } from "react";
function Measurement({ unit }: { unit: string }) {
const state = useLocalObservable(() => ({
length: 0,
unit: unit,
get lengthWithUnit() {
return this.unit === "inch"
? `${this.length / 2.54} inch`
: `${this.length} cm`;
}
}));
// Sync prop changes to observable state
useEffect(() => {
state.unit = unit;
}, [unit, state]);
return <h1>{state.lengthWithUnit}</h1>;
}autoBind: true)useEffect to synchronize props or external state with observable state// MobX annotations from mobx package
type Annotation = {
annotationType_: string;
make_(adm: any, key: PropertyKey, descriptor: PropertyDescriptor, source: object): any;
extend_(adm: any, key: PropertyKey, descriptor: PropertyDescriptor, proxyTrap: boolean): boolean | null;
options_?: any;
};
type AnnotationMapEntry = Annotation | true | false;
type AnnotationsMap<T, AdditionalKeys extends PropertyKey> = {
[K in keyof T | AdditionalKeys]?: AnnotationMapEntry;
};