Skip to content

Commit 993b43d

Browse files
committed
createSignal
1 parent 957cffa commit 993b43d

1 file changed

Lines changed: 206 additions & 107 deletions

File tree

src/routes/reference/basic-reactivity/create-signal.mdx

Lines changed: 206 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -15,147 +15,246 @@ description: >-
1515
values that change over time and automatically update your UI when they do.
1616
---
1717

18-
Signals are the most basic reactive primitive.
19-
They track a single value (which can be a value of any type) that changes over time.
20-
21-
```tsx
22-
import { createSignal } from "solid-js"
23-
24-
function createSignal<T>(
25-
initialValue: T,
26-
options?: {
27-
equals?: false | ((prev: T, next: T) => boolean)
28-
name?: string
29-
internal?: boolean
30-
}
31-
): [get: () => T, set: (v: T) => T]
32-
33-
// available types for return value of createSignal:
34-
import type { Signal, Accessor, Setter } from "solid-js"
18+
Creates a reactive state primitive consisting of a getter (accessor) and a setter function that forms the foundation of Solid's reactivity system.
19+
20+
## Import
21+
22+
```typescript
23+
import { createSignal } from "solid-js";
24+
```
25+
26+
## Type Signature
27+
28+
```typescript
29+
function createSignal<T>(): Signal<T | undefined>
30+
function createSignal<T>(value: T, options?: SignalOptions<T>): Signal<T>
31+
```
32+
33+
### Related Types
34+
35+
```typescript
3536
type Signal<T> = [get: Accessor<T>, set: Setter<T>]
37+
3638
type Accessor<T> = () => T
37-
type Setter<T> = (v: T | ((prev?: T) => T)) => T
3839
40+
type Setter<T> = {
41+
<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)): U;
42+
<U extends T>(value: (prev: T) => U): U;
43+
<U extends T>(value: Exclude<U, Function>): U;
44+
<U extends T>(value: Exclude<U, Function> | ((prev: T) => U)): U;
45+
}
46+
47+
interface SignalOptions<T> {
48+
name?: string;
49+
equals?: false | ((prev: T, next: T) => boolean);
50+
internal?: boolean;
51+
}
3952
```
4053

41-
The Signal's value starts out equal to the passed first argument `initialValue` (or undefined if there are no arguments).
42-
The `createSignal` function returns a pair of functions as a two-element array: a getter (or accessor) and a setter.
43-
In typical use, you would destructure this array into a named Signal like so:
54+
## Parameters
4455

45-
```tsx
46-
const [count, setCount] = createSignal(0)
47-
const [ready, setReady] = createSignal(false)
48-
```
56+
### `value`
57+
58+
**Type:** `T` (optional)
59+
60+
The initial value for the signal. If no initial value is provided, the signal's type is automatically extended with `undefined`.
61+
62+
### `options`
63+
64+
**Type:** `SignalOptions<T>` (optional)
65+
66+
Configuration object for the signal.
4967

50-
Calling the getter (e.g., `count()` or `ready()`) returns the current value of the Signal.
68+
#### `name`
5169

52-
Crucial to automatic dependency tracking, calling the getter within a tracking scope causes the calling function to depend on this Signal, so that function will rerun if the Signal gets updated.
70+
**Type:** `string` (optional)
5371

54-
Calling the setter (e.g., `setCount(nextCount)` or `setReady(nextReady)`) sets the Signal's value and updates the Signal (triggering dependents to rerun) if the value actually changed (see details below).
55-
The setter takes either the new value for the signal or a function that maps the previous value of the signal to a new value as its only argument.
56-
The updated value is also returned by the setter. As an example:
72+
A name for the signal used for debugging purposes in development mode.
5773

58-
```tsx
59-
// read signal's current value, and
60-
// depend on signal if in a tracking scope
61-
// (but nonreactive outside of a tracking scope):
62-
const currentCount = count()
74+
#### `equals`
6375

64-
// or wrap any computation with a function,
65-
// and this function can be used in a tracking scope:
66-
const doubledCount = () => 2 * count()
76+
**Type:** `false | ((prev: T, next: T) => boolean)` (optional)
6777

68-
// or build a tracking scope and depend on signal:
69-
const countDisplay = <div>{count()}</div>
78+
A custom comparison function to determine when the signal should update.
79+
If set to `false`, the signal will always update regardless of value equality. By default, signals use reference equality (`===`).
7080

71-
// write signal by providing a value:
72-
setReady(true)
81+
#### `internal`
7382

74-
// write signal by providing a function setter:
75-
const newCount = setCount((prev) => prev + 1)
83+
**Type:** `boolean` (optional)
7684

85+
Marks the signal as internal, preventing it from appearing in development tools.
86+
This is primarily used by Solid's internal systems.
87+
88+
## Return Value
89+
90+
**Type:** `Signal<T>`
91+
92+
Returns a tuple `[getter, setter]` where:
93+
94+
- **getter**: An accessor function that returns the current value and tracks dependencies when called within a reactive context
95+
- **setter**: A function that updates the signal's value and notifies all dependent computations
96+
97+
## Basic Usage
98+
99+
### Creating a signal with an initial value
100+
101+
```typescript
102+
const [count, setCount] = createSignal(0);
103+
104+
console.log(count()); // 0
105+
setCount(5);
106+
console.log(count()); // 5
77107
```
78108

79-
:::note
80-
If you want to store a function in a Signal you must use the function form:
109+
### Creating a signal without an initial value
110+
111+
```typescript
112+
const [name, setName] = createSignal<string>();
81113
82-
```tsx
83-
setValue(() => myFunction);
84-
```
114+
console.log(name()); // undefined
115+
setName("John");
116+
console.log(name()); // "John"
117+
```
85118

86-
However, functions are not treated specially as the `initialValue` argument to `createSignal`, so you can pass a
87-
function initial value as is:
119+
### Functional updates
88120

89-
```tsx
90-
const [func, setFunc] = createSignal(myFunction);
91-
```
121+
```typescript
122+
const [count, setCount] = createSignal(0);
92123
93-
:::
124+
setCount(prev => prev + 1);
125+
console.log(count()); // 1
94126
95-
## Options
127+
setCount(c => c * 2);
128+
console.log(count()); // 2
129+
```
96130

97-
| Name | Type | Default | Description |
98-
| ---------- | ------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
99-
| `equals` | `false \| ((prev: T, next: T) => boolean)` | `===` | A function that determines whether the Signal's value has changed. If the function returns true, the Signal's value will not be updated and dependents will not rerun. If the function returns false, the Signal's value will be updated and dependents will rerun. |
100-
| `name` | `string` | | A name for the Signal. This is useful for debugging. |
101-
| `internal` | `boolean` | `false` | If true, the Signal will not be accessible in the devtools. |
131+
## Advanced Usage
102132

103-
### `equals`
133+
### Using a custom equality function
104134

105-
The `equals` option can be used to customize the equality check used to determine whether the Signal's value has changed.
106-
By default, the equality check is a strict equality check (`===`).
107-
If you want to use a different equality check, you can pass a custom function as the `equals` option.
108-
The custom function will be called with the previous and next values of the Signal as arguments.
109-
If the function returns true, the Signal's value will not be updated and dependents will not rerun.
110-
If the function returns false, the Signal's value will be updated and dependents will rerun.
135+
```typescript
136+
const [user, setUser] = createSignal(
137+
{ name: "John", age: 25 },
138+
{
139+
equals: (prev, next) => prev.name === next.name && prev.age === next.age
140+
}
141+
);
111142
112-
```tsx
113-
const [count, setCount] = createSignal(0, {
114-
equals: (prev, next) => prev === next,
115-
})
143+
// This won't trigger updates because the values are considered equal
144+
setUser({ name: "John", age: 25 });
116145
```
117146

118-
Here are some examples of this option in use:
119-
120-
```tsx
121-
// use { equals: false } to allow modifying object in-place;
122-
// normally this wouldn't be seen as an update because the
123-
// object has the same identity before and after change
124-
const [object, setObject] = createSignal({ count: 0 }, { equals: false })
125-
setObject((current) => {
126-
current.count += 1
127-
current.updated = new Date()
128-
return current
129-
})
130-
131-
// use { equals: false } to create a signal that acts as a trigger without storing a value:
132-
const [depend, rerun] = createSignal(undefined, { equals: false })
133-
// now calling depend() in a tracking scope
134-
// makes that scope rerun whenever rerun() gets called
135-
136-
// define equality based on string length:
137-
const [myString, setMyString] = createSignal("string", {
138-
equals: (newVal, oldVal) => newVal.length === oldVal.length,
139-
})
140-
141-
setMyString("string") // considered equal to the last value and won't cause updates
142-
setMyString("stranger") // considered different and will cause updates
147+
### Disabling equality checking
148+
149+
```typescript
150+
const [data, setData] = createSignal([], { equals: false });
151+
152+
// This will always trigger updates, even with the same array reference
153+
setData([]);
143154
```
144155

145-
### `name`
156+
### Development debugging
146157

147-
The `name` option can be used to give the Signal a name.
148-
This is useful for debugging. The name will be displayed in the devtools.
158+
```typescript
159+
const [state, setState] = createSignal(
160+
{ loading: false, data: null },
161+
{ name: "apiState" }
162+
);
163+
```
149164

150-
```tsx
151-
const [count, setCount] = createSignal(0, { name: "count" })
165+
## Common Patterns
166+
167+
### Boolean toggle
168+
169+
```typescript
170+
const [isOpen, setIsOpen] = createSignal(false);
171+
const toggle = () => setIsOpen(prev => !prev);
172+
```
173+
174+
### Object updates
175+
176+
```typescript
177+
const [user, setUser] = createSignal({ name: "John", age: 25 });
178+
179+
// Update specific property
180+
const updateName = (newName: string) =>
181+
setUser(prev => ({ ...prev, name: newName }));
182+
```
183+
184+
## How It Works
185+
186+
Signals are the foundational primitive of SolidJS's fine-grained reactivity system.
187+
When you call the accessor function within a reactive context (like inside [`createEffect`](/reference/basic-reactivity/create-effect) or a component's JSX), it automatically establishes a dependency relationship.
188+
Any subsequent calls to the setter will trigger updates only to the specific computations that depend on that signal.
189+
190+
```typescript
191+
const [name, setName] = createSignal("John");
192+
193+
// This effect will re-run whenever name changes
194+
createEffect(() => {
195+
console.log(`Hello, ${name()}!`);
196+
});
197+
198+
setName("Jane"); // Logs: "Hello, Jane!"
152199
```
153200

154-
### `internal`
201+
## Details
202+
203+
### Reactivity System Integration
204+
205+
- **Effects**: Automatically track signal reads and re-run when values change
206+
- **Memos**: Cache computed values based on signal dependencies
207+
- **Resources**: Use signals internally for state management
208+
- **Stores**: Built on top of signals with additional mutation tracking
155209

156-
The `internal` option can be used to hide the Signal from the devtools.
157-
This is useful for Signals that are used internally by a component and should not be exposed to the user.
210+
### Equality Checking
158211

159-
```tsx
160-
const [count, setCount] = createSignal(0, { internal: true })
212+
By default, signals use strict equality (`===`) to determine if a value has changed:
213+
214+
- Primitive values are compared by value
215+
- Objects and arrays are compared by reference
216+
- Setting a signal to the same reference won't trigger updates
217+
- Setting a signal to a new object with identical properties will trigger updates
218+
219+
### Function Values
220+
221+
When your signal stores a function as its value, updating it requires special care because the setter treats functions as updater functions by default.
222+
To set a function as the actual value, wrap it in another function:
223+
224+
```typescript
225+
const [fn, setFn] = createSignal(() => "hello");
226+
227+
// ❌ This treats your function as an updater, not the new value
228+
setFn(() => "world"); // Signal becomes "world"
229+
230+
// ✅ Wrap your function to store it as the actual value
231+
setFn(() => () => "world"); // Signal becomes () => "world" (function)
161232
```
233+
234+
The pattern is: `setSignal(() => yourNewFunction)`.
235+
The outer function is the updater, the inner function is your actual value.
236+
237+
### Development vs Production
238+
239+
**Development mode:**
240+
- Signals track additional metadata for debugging
241+
- The `name` option provides useful names in dev tools
242+
- Warnings are issued for signals created outside reactive contexts
243+
244+
**Production mode:**
245+
- Debugging metadata is stripped
246+
- Dev-only options like `name` are ignored
247+
- No warnings are issued
248+
249+
### Error Handling
250+
251+
- If an equality function throws an error, it's caught and handled by Solid's error boundary system
252+
- Setter functions are synchronous and will throw immediately if errors occur
253+
- Reading a signal outside of a reactive context is safe but won't establish tracking
254+
255+
### Performance Characteristics
256+
257+
- Signals are optimized for frequent reads and infrequent writes
258+
- The equality function is called on every setter invocation
259+
- Custom equality functions should be fast and pure
260+
- Avoid creating signals inside frequently-called functions

0 commit comments

Comments
 (0)