Skip to content

Commit 6c0f1fc

Browse files
committed
fix runWithOwner return, add createReaction
1 parent 24a44a7 commit 6c0f1fc

6 files changed

Lines changed: 88 additions & 11 deletions

File tree

CHANGELOG.md

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,25 @@ const html = renderToString(() => <Island1 />, { renderId: "island1" });
3838
hydrate(() => <Island1 />, mountEl, { renderId: "island1" });
3939
```
4040

41+
#### `createReaction`
42+
43+
This new primitive is mostly for more advanced use cases and is very helpful for interopt with purely pull based systems (like integrating with React's render cycle). It registers an untracked side effect and returns a tracking function. The tracking function is used to track code block, and the side effect is not fired until the first time any of the dependencies in the tracking code is updated. `track` must be called to track again.
44+
45+
```js
46+
const [s, set] = createSignal("start");
47+
48+
const track = createReaction(() => console.log("something"));
49+
50+
// next time s changes run the reaction
51+
track(() => s());
52+
53+
set("end"); // "something"
54+
55+
set("final"); // no-op as reaction only runs on first update, need to call track again.
56+
```
57+
58+
This primitive is niche for certain use cases but where it is useful it is indispensible (like the next feature which uses a similar API).
59+
4160
#### External Sources (experimental)
4261

4362
Ever wanted to use a third party reactive library directly in Solid, like MobX, Vue Reactivity, or Kairo. We are experimenting with adding native support so reactive atoms from these libraries can be used directly in Solid's primitives and JSX without a wrapper. This feature is still experimental since supporting Transitions and Concurrent Rendering will take some more effort. But we have added `enableExternalSource` enable this feature. Thanks @3Shain for designing this solution.
@@ -175,6 +194,7 @@ const [data] = createResource(sourceSignal, (source, { value }) => {});
175194
- Fixed self owning source infinite recursion.
176195
- Fixed faulty treesplitting for hydration in client only render.
177196
- Fixed return type of `preload` on lazy components to always be a promise.
197+
- Fixed compile error with leading white space after opening tags when generating ssr.
178198

179199
## 1.2.0 - 2021-10-25
180200

packages/solid/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {
44
createEffect,
55
createRenderEffect,
66
createComputed,
7+
createReaction,
78
createDeferred,
89
createSelector,
910
createMemo,

packages/solid/src/reactive/signal.ts

Lines changed: 36 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -115,15 +115,13 @@ export function createRoot<T>(fn: RootFunction<T>, detachedOwner?: Owner): T {
115115

116116
Owner = root;
117117
Listener = null;
118-
let result: T;
119118

120119
try {
121-
runUpdates(() => (result = fn(() => cleanNode(root))), true);
120+
return runUpdates(() => fn(() => cleanNode(root)), true)!;
122121
} finally {
123122
Listener = listener;
124123
Owner = owner;
125124
}
126-
return result!;
127125
}
128126

129127
export type Accessor<T> = () => T;
@@ -308,7 +306,35 @@ export function createEffect<Next, Init = Next>(
308306
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
309307
if (s) c.suspense = s;
310308
c.user = true;
311-
Effects && Effects.push(c);
309+
Effects ? Effects.push(c) : queueMicrotask(() => updateComputation(c));
310+
}
311+
312+
/**
313+
* Creates a reactive computation that runs after the render phase with flexible tracking
314+
* ```typescript
315+
* export function createReaction(
316+
* onInvalidate: () => void,
317+
* options?: { name?: string }
318+
* ): (fn: () => void) => void;
319+
* ```
320+
* @param invalidated a function that is called when tracked function is invalidated.
321+
* @param options allows to set a name in dev mode for debugging purposes
322+
*
323+
* @description https://www.solidjs.com/docs/latest/api#createreaction
324+
*/
325+
export function createReaction(onInvalidate: () => void, options?: EffectOptions) {
326+
let fn: (() => void) | undefined;
327+
const c = createComputation(() => {
328+
fn ? fn() : untrack(onInvalidate);
329+
fn = undefined;
330+
}, undefined, false, 0, "_SOLID_DEV_" ? options : undefined),
331+
s = SuspenseContext && lookup(Owner, SuspenseContext.id);
332+
if (s) c.suspense = s;
333+
c.user = true;
334+
return (tracking: () => void) => {
335+
fn = tracking;
336+
updateComputation(c);
337+
}
312338
}
313339

314340
interface Memo<Prev, Next = Prev> extends SignalState<Next>, Computation<Next> {
@@ -326,7 +352,7 @@ export interface MemoOptions<T> extends EffectOptions {
326352
* fn: (v: T) => T,
327353
* value?: T,
328354
* options?: { name?: string, equals?: false | ((prev: T, next: T) => boolean) }
329-
* ): T;
355+
* ): () => T;
330356
* ```
331357
* @param fn a function that receives its previous or the initial value, if set, and returns a new value used to react on a computation
332358
* @param value an optional initial value for the computation; if set, fn will never receive undefined as first argument
@@ -464,7 +490,7 @@ export function createResource<T, S>(
464490
if (options.globalRefetch !== false) {
465491
Resources || (Resources = new Set());
466492
Resources.add(load);
467-
onCleanup(() => Resources.delete(load));
493+
Owner && onCleanup(() => Resources.delete(load));
468494
}
469495

470496
const contexts = new Set<SuspenseContextType>(),
@@ -870,11 +896,11 @@ export function getOwner() {
870896
return Owner;
871897
}
872898

873-
export function runWithOwner(o: Owner, fn: () => any) {
899+
export function runWithOwner<T>(o: Owner, fn: () => T): T {
874900
const prev = Owner;
875901
Owner = o;
876902
try {
877-
return runUpdates(fn, true);
903+
return runUpdates(fn, true)!;
878904
} finally {
879905
Owner = prev;
880906
}
@@ -1340,15 +1366,15 @@ function runTop(node: Computation<any>) {
13401366
}
13411367
}
13421368

1343-
function runUpdates(fn: () => void, init: boolean) {
1369+
function runUpdates<T>(fn: () => T, init: boolean) {
13441370
if (Updates) return fn();
13451371
let wait = false;
13461372
if (!init) Updates = [];
13471373
if (Effects) wait = true;
13481374
else Effects = [];
13491375
ExecCount++;
13501376
try {
1351-
fn();
1377+
return fn();
13521378
} catch (err) {
13531379
handleError(err);
13541380
} finally {

packages/solid/src/server/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export {
44
createComputed,
55
createRenderEffect,
66
createEffect,
7+
createReaction,
78
createDeferred,
89
createSelector,
910
createMemo,

packages/solid/src/server/reactive.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,10 @@ export const createRenderEffect = createComputed;
6262

6363
export function createEffect<T>(fn: (v?: T) => T, value?: T): void {}
6464

65+
export function createReaction(fn: () => void) {
66+
return (fn: () => void) => { fn(); }
67+
}
68+
6569
export function createMemo<T>(fn: (v?: T) => T, value?: T): () => T {
6670
Owner = { owner: Owner, context: null };
6771
let v: T;

packages/solid/test/signals.spec.ts

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
createEffect,
55
createRenderEffect,
66
createComputed,
7+
createReaction,
78
createDeferred,
89
createMemo,
910
createSelector,
@@ -619,4 +620,28 @@ describe("runWithOwner", () => {
619620
dispose();
620621
expect(cleanupRun).toBe(true);
621622
});
622-
});
623+
});
624+
625+
describe("createReaction", () => {
626+
test("Create and trigger a Reaction", (done) => {
627+
createRoot(() => {
628+
let count = 0;
629+
const [sign, setSign] = createSignal("thoughts");
630+
const track = createReaction(() => count++);
631+
expect(count).toBe(0);
632+
track(sign)
633+
expect(count).toBe(0);
634+
setTimeout(() => {
635+
expect(count).toBe(0);
636+
setSign("mind");
637+
expect(count).toBe(1);
638+
setSign("body");
639+
expect(count).toBe(1);
640+
track(sign)
641+
setSign("everything");
642+
expect(count).toBe(2);
643+
done();
644+
});
645+
});
646+
});
647+
})

0 commit comments

Comments
 (0)