You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: content/en/guide/v10/signals.md
+227Lines changed: 227 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -702,3 +702,230 @@ If you're using Preact Signals in your application, there are specialized debugg
702
702
- **[Signals DevTools](https://github.com/preactjs/signals/blob/main/packages/devtools-ui)** - Visual DevTools UI for debugging and visualizing Preact Signals in real-time. You can embed it directly in your page for demos, or integrate it into custom tooling.
703
703
704
704
> **Note:** These are framework-agnostic tools from the Signals library. While they work great with Preact, they're not Preact-specific.
705
+
706
+
## Models
707
+
708
+
Models provide a structured way to build reactive state containers that encapsulate signals, computed values, effects, and actions. They offer a clean pattern for organizing complex state logic while ensuring automatic cleanup and batched updates.
709
+
710
+
### createModel(factory)
711
+
712
+
The `createModel(factory)` function creates a model constructor from a factory function. The factory function can accept arguments for initialization and should return an object containing signals, computed values, and action methods.
counter.increment(); // Updates are automatically batched
740
+
console.log(counter.count.value); // 6
741
+
console.log(counter.doubled.value); // 12
742
+
743
+
// Clean up all effects when done
744
+
counter[Symbol.dispose]();
745
+
```
746
+
747
+
#### Key Features
748
+
749
+
- **Factory arguments**: Factory functions can accept arguments for initialization, making models reusable with different configurations.
750
+
- **Automatic batching**: All methods returned from the factory are automatically wrapped as actions, meaning state updates within them are batched and untracked.
751
+
- **Automatic effect cleanup**: Effects created during model construction are captured and automatically disposed when the model is disposed via `Symbol.dispose`.
752
+
- **Composable models**: Models compose naturally - effects from nested models are captured by the parent and disposed together when the parent is disposed.
753
+
754
+
#### Model Composition
755
+
756
+
Models can be nested within other models. When a parent model is disposed, all effects from nested models are automatically cleaned up:
757
+
758
+
```js
759
+
constTodoItemModel=createModel((text) => {
760
+
constcompleted=signal(false);
761
+
762
+
return {
763
+
text,
764
+
completed,
765
+
toggle() {
766
+
completed.value=!completed.value;
767
+
}
768
+
};
769
+
});
770
+
771
+
constTodoListModel=createModel(() => {
772
+
constitems=signal([]);
773
+
774
+
return {
775
+
items,
776
+
addTodo(text) {
777
+
consttodo=newTodoItemModel(text);
778
+
items.value= [...items.value, todo];
779
+
},
780
+
removeTodo(todo) {
781
+
items.value=items.value.filter(t=> t !== todo);
782
+
todo[Symbol.dispose]();
783
+
}
784
+
};
785
+
});
786
+
787
+
consttodoList=newTodoListModel();
788
+
todoList.addTodo('Buy groceries');
789
+
todoList.addTodo('Walk the dog');
790
+
791
+
// Disposing the parent also cleans up all nested model effects
792
+
todoList[Symbol.dispose]();
793
+
```
794
+
795
+
### action(fn)
796
+
797
+
The `action(fn)` function wraps a function to run in a batched and untracked context. This is useful when you need to create standalone actions outside of a model:
798
+
799
+
```js
800
+
import { signal, action } from'@preact/signals';
801
+
802
+
constcount=signal(0);
803
+
804
+
constincrementBy=action((amount) => {
805
+
count.value+= amount;
806
+
});
807
+
808
+
incrementBy(5); // Batched update
809
+
```
810
+
811
+
### useModel(modelOrFactory)
812
+
813
+
The `useModel` hook is available in both `@preact/signals` and `@preact/signals-react` packages. It handles creating a model instance on first render, maintaining the same instance across re-renders, and automatically disposing the model when the component unmounts.
counter.count.value=10; // TypeScript error: Cannot assign to 'value'
894
+
```
895
+
896
+
#### Custom Dispose Logic
897
+
898
+
If your model needs custom cleanup logic that isn't related to signals (such as closing WebSocket connections), use an effect with no dependencies that returns a cleanup function:
899
+
900
+
```js
901
+
constWebSocketModel=createModel((url) => {
902
+
constmessages=signal([]);
903
+
constws=newWebSocket(url);
904
+
905
+
ws.onmessage= (e) => {
906
+
messages.value= [...messages.value, e.data];
907
+
};
908
+
909
+
// This effect runs once; its cleanup runs on dispose
This pattern mirrors `useEffect(() => { return cleanup }, [])` in React and ensures that cleanup happens automatically when models are composed together - parent models don't need to know about the dispose functions of nested models.
0 commit comments