add mapProps for reflect() to derive view props from store state and props#102
Merged
AlexandrHoroshih merged 3 commits intoJun 25, 2026
Merged
Conversation
Adds an optional `mapProps` field to `reflect` (and `createReflect`,
`variant`, `list`) that computes a prop for the view from a store value
combined with the component's own props:
reflect({
view: A,
bind: { foo: $data },
mapProps: {
bar: { source: $data, fn: (data, props) => data[props.key] },
},
})
- `source` is read reactively via useUnit, so the component re-renders
only when that store changes
- `fn` receives the store value and the props (bound + incoming)
- derived props are made optional in the resulting component type and
can still be overridden explicitly at the usage site (external props win)
Covered by runtime tests (no-ssr + ssr/scope), type tests and docs.
|
|
Reworks the public types so the `source` stores are captured in a
separate generic (`Sources`). Because the stores are inferred
independently of the `fn`s, the `fn` value argument is now inferred as
the source store value - no manual annotation needed - while `props`
stays typed as the view's props:
mapProps: {
label: {
source: $user, // Store<{ name: string }>
fn: (user, props) => user.name, // `user` inferred, not `any`
},
}
A key that is not a prop of the view resolves the fn return type to
`never`. Applies to reflect, createReflect, variant and list.
`source` now accepts not only a single store but - like `combine` /
`useUnit` - an object or array of stores, so several stores can feed a
derived prop without a manual `combine`:
mapProps: {
summary: {
source: { count: $count, total: $total },
fn: (cart, props) => `${cart.count} / ${cart.total}`,
},
}
The resolved value type is inferred (object -> object of values, array ->
tuple of values). At runtime a non-store source is normalized via
`combine` once, at component creation.
AlexandrHoroshih
approved these changes
Jun 25, 2026
AlexandrHoroshih
left a comment
Member
There was a problem hiding this comment.
Awesome work, thank you!
mapProps for reflect() to derive view props from store state and props
AlexandrHoroshih
added a commit
that referenced
this pull request
Jun 25, 2026
Follow-up to PR #102 (mapProps feature). Addresses all review points: #1 — Runtime tests added for variant, list, createReflect (no-ssr + ssr). Previously only reflect had mapProps coverage. #2 — Unknown mapProps keys now error at the key site itself, not on fn's return. MapPropsFromSources resolves unknown-key entries to never, producing a TS2322 at the key line. Replaces the old 'K extends keyof Props ? Props[K] : never' fn-return approach that had a silent-failure path (never-returning fn compiled cleanly). #3 — Documented the Store<any> variable-source widening limitation. Type tests (3a positive, 3b widening) pin the current behavior. #4 — fn is skipped when its key is overridden by an external prop. 'if (key in props) continue' in src/core/reflect.ts. Spy assertions in reflect and createReflect tests verify fn is not called. B1 — bind + mapProps key collision is now a type error. MapPropsFromSources takes Bind as a type parameter; a key in both bind and mapProps resolves to never. Runtime skip ('if (key in storeProps) continue') is a defense-in-depth for JS/type-bypass scenarios (covers stores; events/data/functions are covered by the type fix). B2 — mapItem + mapProps key collision in list is a type error. MapItem's mapped type now omits keyof Sources alongside keyof Bind. Partial: bypassable with explicit item-parameter annotation (known TS limitation with mapped-type extends constraints), documented in the type-test comment. All four operators (reflect, createReflect, list, variant) are covered by the type fixes and runtime tests. Docs updated in docs/pages/docs/reflect.mdx: - mapProps keys must be props of the view; unknown keys error at the key - a key must not appear in both bind and mapProps - in list, a key must not appear in both mapItem and mapProps - source should be an inline literal for best inference - fn is not invoked when its key is overridden 80 runtime tests + type tests pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #13
Adds an optional
mapPropsfield toreflect(andcreateReflect,variant,list) that computes a prop for the view from a store value combined with the component's own props:sourceis read reactively via useUnit, so the component re-renders only when that store changesfnreceives the store value and the props (bound + incoming)Covered by runtime tests (no-ssr + ssr/scope), type tests and docs.
Example: