Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,47 @@ stopScope();
count(3); // No console output
```

#### Manual Triggering

The `trigger()` function allows you to manually trigger updates for downstream dependencies when you've directly mutated a signal's value without using the signal setter:

```ts
import { signal, computed, trigger } from 'alien-signals';

const arr = signal<number[]>([]);
const length = computed(() => arr().length);

console.log(length()); // 0

// Direct mutation doesn't automatically trigger updates
arr().push(1);
console.log(length()); // Still 0

// Manually trigger updates
trigger(arr);
console.log(length()); // 1
```

You can also trigger multiple signals at once:

```ts
import { signal, computed, trigger } from 'alien-signals';

const src1 = signal<number[]>([]);
const src2 = signal<number[]>([]);
const total = computed(() => src1().length + src2().length);

src1().push(1);
src2().push(2);

trigger(() => {
src1();
src2();
});

console.log(total()); // 2
```

#### Creating Your Own Surface API

You can reuse alien-signals’ core algorithm via `createReactiveSystem()` to build your own signal API. For implementation examples, see:
Expand Down
26 changes: 26 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,32 @@ export function effectScope(fn: () => void): () => void {
return effectScopeOper.bind(e);
}

export function trigger(fn: () => void) {
const sub: ReactiveNode = {
deps: undefined,
depsTail: undefined,
flags: ReactiveFlags.Watching,
};
const prevSub = setActiveSub(sub);
try {
fn();
} finally {
setActiveSub(prevSub);
do {
const link = sub.deps!;
const dep = link.dep;
unlink(link, sub);
if (dep.subs !== undefined) {
propagate(dep.subs);
shallowPropagate(dep.subs);
}
} while (sub.deps !== undefined);
if (!batchDepth) {
flush();
}
}
}

function updateComputed(c: Computed): boolean {
++cycle;
c.depsTail = undefined;
Expand Down
46 changes: 46 additions & 0 deletions tests/trigger.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { expect, test } from 'vitest';
import { computed, effect, trigger, signal } from '../src';

test('should trigger updates for dependent computed signals', () => {
const arr = signal<number[]>([]);
const length = computed(() => arr().length);

expect(length()).toBe(0);
arr().push(1);
trigger(arr);
expect(length()).toBe(1);
});

test('should trigger updates for the second source signal', () => {
const src1 = signal<number[]>([]);
const src2 = signal<number[]>([]);
const length = computed(() => src2().length);

expect(length()).toBe(0);
src2().push(1);
trigger(() => {
src1();
src2();
});
expect(length()).toBe(1);
});

test('should trigger effect once', () => {
const src1 = signal<number[]>([]);
const src2 = signal<number[]>([]);

let triggers = 0;

effect(() => {
triggers++;
src1();
src2();
});

expect(triggers).toBe(1);
trigger(() => {
src1();
src2();
});
expect(triggers).toBe(2);
});