Closed fabian-hiller closed 1 year ago
The way to do this is to store the prop signal in a local signal - signals track value changes over time, not signal changes over time, so if you need to react to a signal reference being changed over time you need to store it in another signal.
I typically use a helper called useLiveSignal()
for this, which creates a Signal whose value is updated on every render if the provided argument is different than the signal's current value.
/**
* useSignal(), but rerendering with a different value arg updates the signal.
* Useful for: const a = useLiveSignal(props.a)
*/
export function useLiveSignal<T = any>(value: T): Signal<T> {
const s = useSignal(value);
if (s.peek() !== value) s.value = value;
return s;
}
Using that helper, here's how you'd write the code to make effects work the way you're wanting them to:
function Item(props) {
// create a signal to hold the changing value of `props.value`: (which is itself a signal)
const propValue = useLiveSignal(props.value);
const value = useSignal('');
useSignalEffect(() => (value.value = propValue.value.value));
return <span>{value}</span>;
}
Thank you very much for your precise answer. I am now using a slightly modified version of useLiveSignal
internally in Modular Forms, which ensures that the signals of a field within a list remain the same.
export function useLiveSignal<T = any>(value: T): Signal<T> { const s = useSignal(value); if (s.peek() !== value) s.value = value; return s; }
Is it okay to mutate it in the render function? In React, you can only call setState
in the render function if it belongs to the same component, but changing the signal could trigger other component re-renders, right? Is it fine in Preact?
As most things, blank statements have to be taken with a gran of salt ;) but yes you can do so in Preact as long as you don't create an infinite loop
I think useLiveSignal
should be used on user side, because he exactly know that he will swap signals. I think in most cases signals should be static
Wrapping all props in useLiveSignal
, mostly when some props can be optional can be tricky
Well, in react - this is one of the reasons you have dependencies. If you have component like this:
const Component: React.FC = () => {
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
console.log(ref.current);
}, []); // Here you dont need ref as a dependency because both - useRef and useEffect will be alive for the same
return (
<div ref={ref}>
<ComponentWithWrapperRef parentRef={ref} />
</div>
);
};
const ComponentWithWrapperRef: React.FC<{ parentRef: React.RefObject<HTMLDivElement> }> = ({
parentRef
}) => {
React.useEffect(() => {
console.log(parentRef.current);
}, [parentRef]); // Here you need parentRef as a dependency because the ref itself can change, not just the .current
return <div />;
};
}
In my opinion, here - a dependency would be reasonable. Otherwise - how would you handle - if you would pass that signal down even further? Would you pass argument signal, and it would have to be wrapper there again in useLiveSignal
? Would you pass "liveSignal" - and then wrap it again in useLiveSignal
? Then access it through .value.value.value.value.value
?
I think useLinkedSignal
is great for this type of cases. Because it unwraps signal deeply
There is a strange, but currently probably expected behavior when the reference of a signal passed as a prop changes. Here is a simple scenario:
useSignalEffect
.useSignalEffect
has no dependency array, the signal is not updated, causing bugs.A simplified real-world example: Preact RPEL
item0
is always located at index0
in the list.key={position}
the wrong signal moves inuseSignalEffect
to another index.I noticed the problem when implementing Modular Forms for Preact. In order to have the ability to set a listener to the value at a specific index in a list, the signal must not change in SolidJS. Therefore, I swap the values instead of moving the signals in the list.