@praxisjs/decorators
Changelog for @praxisjs/decorators — @State, @Prop, @Computed, @Watch, @Emit, @Compose, and all built-in decorators.
@praxisjs/decorators
1.4.0
@Ref<T>() — DOM element ref decorator.
Replaces the manual { current: null as T | null } pattern with a typed decorator. The decorated field becomes a Ref<T> — callable as the JSX ref callback and holding the element in .current:
import { Ref } from '@praxisjs/decorators'
@Component()
class Card extends StatefulComponent {
@Ref<HTMLDivElement>()
cardRef!: Ref<HTMLDivElement>
render() {
return <div ref={this.cardRef}>...</div>
}
}Ref<T> is both the decorator function and the type annotation — one import, used in both positions.
Works with @Compose exactly as before — pass the field name as a string:
@Ref<HTMLDivElement>()
containerRef!: Ref<HTMLDivElement>
@Compose(ElementSize, 'containerRef')
size!: ElementSizeFor refs outside of a class, use createRef<T>():
import { createRef } from '@praxisjs/decorators'
const scrollRoot = createRef<HTMLDivElement>()
// pass to @Lazy, store in module scope, etc.1.3.0
@Computed — writable computed with getter + setter support.
@Computed now accepts get and set options for a TypeScript-compatible compact form using the accessor keyword — TypeScript sees the field as read-write, no cast required:
@Computed({
get(this: MyComponent): number {
return (this.celsius * 9) / 5 + 32
},
set(this: MyComponent, value: number) {
this.celsius = ((value - 32) * 5) / 9
},
})
accessor fahrenheit!: numberAlternatively, pass only set alongside a plain get accessor. This has identical runtime behavior but TypeScript cannot see the setter:
@Computed({
set(this: MyComponent, value: number) {
this.celsius = ((value - 32) * 5) / 9
},
})
get fahrenheit(): number {
return (this.celsius * 9) / 5 + 32
}The getter is always a cached reactive computed() that recomputes when its signal dependencies change.
1.2.0
@When — optional condition function.
@When(propName, condition?) now accepts an optional predicate. The method fires exactly once, on the first value for which condition(value) returns true. Without a condition, the existing behaviour is preserved — fires on the first truthy value.
@When('score', score => score >= 100)
onWin() { ... }Internal fix: @When now reads the property inside a reactive effect(), so it works correctly with @State-decorated fields in addition to raw Signal/Computed properties.
Internal test coverage raised to 100% across all decorators.
1.1.0
@Resource — cache, SWR, deduplication, invalidation, refetch on focus.
New options forwarded to the core resource() primitive:
key— shared cache key. Resources with the same key share a cache entry and a single in-flight request.staleTime— ms before cached data is stale. At0(default) the cached value is shown immediately while fresh data loads in the background (SWR). Set toInfinityfor a fully static cache.refetchOnFocus— automatically refetches when the tab regains visibility.
New lifecycle behaviour:
- The decorator now calls
r.destroy()inonUnmount, ensuring focus listeners and cache registrations are cleaned up when the component is unmounted.
New export:
invalidateResource(key)— re-exported from@praxisjs/decoratorsfor convenience. Clears the named cache entry and triggers all active resources under that key to refetch.
Internal: serializeArgs in @Memo — removed an unreachable null branch from the catch block (JSON.stringify(null) never throws).
import { invalidateResource } from '@praxisjs/decorators'
async function saveProfile(data: ProfileData) {
await api.save(data)
invalidateResource('current-user') // refetches every component subscribed to this key
}1.0.2
@Resource now accepts a self-receiving fetcher — pass the component instance as the first argument to access reactive fields safely:
@Resource((self: MyComponent) => api.getPage(self.page))
items!: ResourceInstance<Item[]>When the fetcher accepts one argument, the component instance is passed automatically at bind time. Any signals read on self become reactive dependencies. The zero-argument form (() => api.getAll()) continues to work unchanged.
1.0.1
Updated dependencies — @praxisjs/[email protected].
1.0.0
Breaking — @Virtual removed
@Virtual is removed. It had two fundamental flaws: items were a snapshot at render time and never updated reactively, and JSX in renderItem silently failed on scroll.
Migrate to the VirtualList composable — it exposes reactive signals (visibleItems, totalHeight, offsetTop, offsetBottom) and renders with normal JSX.
Other changes in this release:
@Lazyfixed — the component now renders after entering the viewport. The previous implementation returned a staticnullfrom the render enhancement so the IntersectionObserver callback had no effect@Lazyoptions object — now accepts{ placeholder, root, rootMargin }for scoping intersection to a specific scroll container@Composestring literals —@Compose(KeyCombo, 'ctrl+s')no longer requires an intermediate instance property. Newgetter(propName)helper exported for live reactive sources- Bug fix — stacked class decorators (e.g.
@Lazy @Component) no longer cause infinite recursion
0.8.1
@State and @DeepState now produce a TypeScript error when applied to plain classes. Store classes must extend ReactiveStore from @praxisjs/store.
0.8.0
- New:
@Synced(channelName?)— syncs a field across browser tabs viaBroadcastChannel - New:
@DeepState()— deepProxyso nested mutations are reactive without replacing the reference
0.7.0
Breaking — @History redesigned
@History now decorates a separate field (not the @State field itself):
// before
@History(100) @State() text = ''
// after
@State() text = ''
@History('text', 100) textHistory!: HistoryOf<MyClass, 'text'>0.6.0
- New:
@Until(propName)— replaces the method with one that returns aPromiseresolving to the first truthy value @Memofalls back to object identity for non-JSON-serializable arguments@Debouncecancels its timer on component unmount@Throttleclamps negativemsto0
0.5.0
- Decorator factory helpers —
createFieldDecorator,createClassDecorator,createMethodDecorator,createLifecycleMethodDecorator,createGetterDecorator,createGetterObserverDecorator. See Creating Decorators - New:
@Compose— mixes aComposableclass into a component - New:
@Resource— async data field on a component
0.4.3
Bug fixes in @Lazy, @Virtual, and @Watch: type constraint incompatibility, infinite recursion on render after becoming visible, reactive effect leak on unmount.
0.4.0
Added @Computed() for read-only cached reactive getters. @Debug() now supports @Computed getters.
0.3.0
Migrated to TC39 decorator context API. Introduced StatefulComponent / StatelessComponent.
0.1.0
Initial beta release.