Table of Contents

Method UseSyncExternalStore

Namespace
Velvet
Assembly
Velvet.Docs.dll

UseSyncExternalStore<T>(Func<Action, Action>, Func<T>)

Subscribes to a store Velvet does not own and returns its current snapshot — the counterpart of React's useSyncExternalStore(subscribe, getSnapshot). Must be used inside Render() only.

public static T UseSyncExternalStore<T>(Func<Action, Action> subscribe, Func<T> getSnapshot)

Parameters

subscribe Func<Action, Action>

Registers a store-change callback and returns its unsubscribe action. Must not be null.

getSnapshot Func<T>

Returns the store's current snapshot. Must not be null.

Returns

T

The store snapshot this render observes.

Type Parameters

T

Snapshot type.

Remarks

getSnapshot must return the same value, compared with Object.is, on every call while the store has not changed: cache the snapshot instead of building a new one per read. A store-change notification re-renders the component when getSnapshot then returns a value that is not Object.is-equal to the one it last committed, or throws. A render that returns another snapshot than the previous one, or that subscribes, checks the snapshot again in its passive effects, so a snapshot built afresh per read re-renders the component after every render until the scheduler's update-depth limit drops the update and logs an error. In the Editor, a hook whose two consecutive reads in one render differ logs an error once.

subscribe receives the callback to invoke when the store changes and returns the action that unsubscribes it. It is called in the passive effects of the first commit, and again, after the previous subscription is removed, in those of a commit whose render passed a subscribe that is not System.Delegate.Equals(object) to the previous one, or that follows a call that threw. A render whose body throws or suspends, and an attempt a render-phase update re-runs, subscribe and unsubscribe nothing, and the StrictMode diagnostic render never calls it. A method group on the same instance compares equal across renders; a lambda capturing a local is a new closure each render and re-subscribes on each later commit, as an inline subscribe function does in React. Unmounting removes the subscription. A change the store makes between the render and the subscription, or while subscribe runs, re-renders the component once the subscription is made.

The callback must be invoked on the Unity main thread: invoked from another thread, it throws System.InvalidOperationException to its invoker and schedules nothing. The re-render it schedules takes the Urgent lane and never the Transition lane, including when the store is mutated inside startTransition, and the scheduler flushes it from the main thread's posted work instead of waiting for its next frame-boundary callback. The scheduler's resume of a parked time-sliced pass flushes such a re-render first, so readers the pass committed in an earlier slice show the changed snapshot before the next slice renders it.

Within one batch drain pass, readers passing equal getSnapshot delegates observe the snapshot the pass's first read pinned, as readers of one store do through UseStore<TStore, TSel>(Store<TStore>, Func<TStore, TSel>, IEqualityComparer<TSel>?); the next pass, the delayed tier's included, pins afresh. A render outside a drain reads the live snapshot. A render that returns a pin the store has moved past asks for a re-render in its passive effects.