solid-primitives icon indicating copy to clipboard operation
solid-primitives copied to clipboard

Add `createLens` primitive

Open nathanbabcock opened this issue 1 year ago • 6 comments

Utilities for working with nested reactivity in a modular way.

  • createLens - Given a path within a Store object, return a derived or "focused" getter and setter pair.

  • createFocusedGetter - The first half of the lens tuple; a derived signal using path syntax on an object.

  • createFocusedSetter - The second half of the lens tuple; a Setter for a specific path within a Store.

How to use it

// Start with an ordinary SolidJS Store
const storeTuple = createStore([
  { myString: 'first' }
])

// Create a lens to focus on one particular item in the Store.
// Any valid path accepted by `setStore` works here!
const [firstString, setFirstString] = createLens(storeTuple, 0, myString)

// Setters and Getters work just like ordinary Signals
setFirstString("woohoo") // equivalent to `setStore(0, "myString", "woohoo")
console.log(firstString()) // "woohoo"

Motivation

1. Separation of Concerns

Components can receive scoped Setters for only the parts of state they need access to, rather than needing a top-level setStore function.

2. Type-safety

Essentially, we are just partially applying a setStore function with an initial path, and returning a function that will apply the remainder of the path. It is just syntactic sugar, and under the hood everything is using calls to native Store functionality.

The same approach can already be used by the Setter returned by createStore. However, Typescript users will find it hard to maintain type-safety for the arguments passed to a "derived"/partially-applied Setter. The type definitions for SetStoreFunction are... daunting.

The lenses package alleviates this friction by providing both StorePath<T> and EvaluatePath<T, P> generic type helpers.

3. Shared path syntax between Getters and Setters

The path syntax defined in Solid Stores is incredibly expressive and powerful. By introducing createScopedGetter, the same syntax can be also be used to access Store values as derived Signals. This is particularly relevant to child components which may both display and modify items from a Store collection.

Closes #453

nathanbabcock avatar May 28 '23 23:05 nathanbabcock