-
-
Notifications
You must be signed in to change notification settings - Fork 97
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync between useSignal and its parameter #247
Comments
The parameter to If you want to update the valid in the signal you should set it on the Do it either after data fetching (side-effect) or on user interaction (side-effect) and so on. |
The similarities between
I do agree that updating a signal's function EffectTest({ value = 0 }) {
const source = useSignal(value);
const timesTwo = useComputed(() => source.value * 2);
const renders = useRef(0);
useEffect(() => void (source.value = value), [value]);
useEffect(() => void renders.current++);
return (
<div>
<p>{source.value}</p>
<p>{timesTwo.value}</p>
<p>{renders.current}</p>
</div>
);
}
function SetTest({ value = 0 }) {
const source = useSignal(value);
const timesTwo = useComputed(() => source.value * 2);
const renders = useRef(0);
source.value = value;
useEffect(() => void renders.current++);
return (
<div>
<p>{source.value}</p>
<p>{timesTwo.value}</p>
<p>{renders.current}</p>
</div>
);
} From what I've found, both methods keep the signal
During render signal update:
All of these details to properly utilize |
You can also pass a signal as prop rather than a value. It's similar how with The difference with signals is that a component is also an effect (signal effect) so accessing a signal in a child doesn't necessarily mean that both the parent and child component will update when the signal changes. function Test() {
const s = useSignal(42);
s.value = 123; // <<< This doesn't cause 'Test' to re-render
return (
<Foo s={s} />
)
}
function Foo({ s }) {
s.value; // <<< Accessing value will subscribe Foo to signal s
return ...
} Also yes, your |
Thanks for the tip. Passing down signal props definitely seems like the most straightforward approach, and at least for Preact, using signal props can also take advantage of the rendering and attribute optimization features. The signal hooks seem like a better alternative to the dependency array hooks. Signal Hook ZombienessI find that when using the signal hooks, specifically This limitation means that trying to derive from a combination of a signal and a non-signal value can potentially not update correctly: function Foo({ s, n }: { s: ReadonlySignal<number>, n: number }) {
const combined = useComputed(() => s.value * n * n);
// combined only recomputes if s is modified
return <p>{combined}</p>;
} There are a few options to fix the data staleness:
Trying to use a mix of both signals and non-signals conflict with each other, Using 3rd party libraries become awkward to use, as whether or not the 3rd party library stores state using signals affects whether or not The conflict between signals and non-signals is a common enough problem that I believe a solution should be added to decrease the friction, either:
Between the two options, the first option seems worse as the core I now realize that the semantics of the // modified useSignal
export function useSignal<T>(value: T, options?: { update: boolean }) {
const source = useMemo(() => signal(value), []);
if (options && options.update) {
source.value = value;
}
return source;
}
// OR new hook added
export function useWatcher<T>(value: T): ReadonlySignal<T> {
const source = useSignal(value);
source.value = value;
return source;
} |
Hm, const c = useComputed(() => {
return s.value /* signal dep */ + dep1 /* React/Preact dep */
}, [dep1]); So it'd re-compute if needed if the computed sources have changed but it'd also re-compute on re-render if the React/Preact deps also changed. I think this makes sense because within a component, you may have 2 kinds of sources (dependencies), one may be other signals but also React/Preact dependencies such as props or other state. |
That could be an option as well. Tbh, one part I like about This is definitely more of a matter of preference, but I feel that having a hook that essentially casts a non-signal into a signal, like for example // some hook to create a signal that updates when the value param updates
export declare function useWatcher<T>(value: T): ReadonlySignal<T>; I view that a hook like const d = useWatcher(dep1);
const c = useComputed(() => s.value + d.value); // both s and d are signals, so c will update correctly |
You can turn @eddyw 's suggestion into a reusable hook: export function useWatcher<T>(value: T): ReadonlySignal<T> {
const signal = useSignal(value);
signal.value = value;
return signal;
} |
(Don’t update the value outside of a useEffect though, at least in React, as it may run the render but never commit, e.g. if it hits an error boundary in a nearby node.) |
@billybimbob said
Indeed. I think |
For both React and Preact, the
useSignal
hook implementation is currently defined as:signals/packages/preact/src/index.ts
Lines 334 to 336 in ccb677b
This implementation would cause the returned signal's
.value
to only be kept in sync with thevalue
parameter on the very first call.One way to get around this behavior is to immediately set the
.value
on the returned signal:signals/packages/preact/src/index.ts
Lines 70 to 71 in ccb677b
I am wondering, is there a specific reason that the
.value
is not set directly in theuseSignal
function? One possible limitation I can see is that batching multiple signal updates would become not possible.I do think however that the benefit of synchronizing between a signal's
.value
and itsvalue
parameter outweighs that limitation.The text was updated successfully, but these errors were encountered: