Stores
Edit this pageSimilar to signals, stores are a state management primitive. However, while signals manage a single piece of state, stores create a centralized location to reduce code redundancy. Within Solid, these stores can spawn a collection of reactive signals, each corresponding to a particular property which can be useful when working with complex state.
Creating a store
Stores can manage many data types, including: objects, arrays, strings, and numbers.
Using JavaScript's proxy mechanism, reactivity extends beyond just the top-level objects or arrays. With stores, you can now target nested properties and elements within these structures to create a dynamic tree of reactive data.
Accessing store values
Store properties can be accessed directly from the state proxy through directly referencing the targeted property:
Accessing stores within a tracking scope follows a similar pattern to signals.
While signals are created using the createSignal
function and require calling the signal function to access their values, store values can be directly accessed without a function call.
This provides access to the store's value directly within a tracking scope:
When a store is created, it starts with the initial state but does not immediately set up signals to track changes. These signals are created lazily, meaning they are only formed when accessed within a reactive context.
Once data is used within a reactive context, such as within the return statement of a component function, computed property, or an effect, a signal is created and dependencies are established.
For example, if you wanted to print out every new user, adding the console log below will not work because it is not within a tracked scope.
Rather, this would need to be in a tracking scope, like inside a createEffect
, so that a dependency is established.
Modifying store values
Updating values within a store is best accomplished using a setter provided by the createStore
initialization.
This setter allows for the modification of a specific key and its associated value, following the format setStore(key, newValue)
:
The value of userCount
could also be automatically updated whenever a new user is added to keep it synced with the users array:
Separating the read and write capabilities of a store provides a valuable debugging advantage.
This separation facilitates the tracking and control of the components that are accessing or changing the values.
A little hidden feature of stores is that you can also create nested stores to help with setting nested properties.
Changes made through setUsers
will update the store.users
property and reading users
from this derived store will also be in sync with the values from store.users
.
Note that the above relies on store.users
to be set already in the existing store.
Path syntax flexibility
Modifying a store using this method is referred to as "path syntax." In this approach, the initial arguments are used to specify the keys that lead to the target value you want to modify, while the last argument provides the new value.
String keys are used to precisely target particular values with path syntax. By specifying these exact key names, you can directly retrieve the targeted information. However, path syntax goes beyond string keys and offers more versatility when accessing targeted values.
Instead of employing the use of just string keys, there is the option of using an array of keys. This method grants you the ability to select multiple properties within the store, facilitating accessed to nested structures. Alternatively, you can use filtering functions to access keys based on dynamic conditions or specific rules.
The flexibility in path syntax makes for efficient navigation, retrieval, and modification of data in your store, regardless of the store's complexity or the requirement for dynamic access scenarios within your application.
Modifying values in arrays
Path syntax provides a convenient way to modify arrays, making it easier to access and update their elements. Instead of relying on discovering individual indices, path syntax introduces several powerful techniques for array manipulation.
Appending new values
To append a new element to an array within a store, you specify the target array and set the index to the desired position.
For example, if you wanted to append the new element to the end of the array, you would set the index to array.length
:
Range specification
With path syntax, you can target a subset of elements to update or modify by specifying a range of indices. You can do this using an array of values:
If your store is an array, you can specify a range of indices using an object with from
and to
keys.
In addition to this, including the by
key, can help you perform iterative updates within an array, which can be useful when you want to update elements at regular intervals.
This key defines the step size for index increments, similar to a for
loop:
Dynamic value assignment
Path syntax also provides a way to set values within an array using functions instead of static values. These functions receive the old value as an argument, allowing you to compute the new value based on the existing one. This dynamic approach is particularly useful for complex transformations.
Filtering values
In scenarios where you want to update elements in an array based on a specific condition, you can pass a function as an argument. This function will act as a filter, allowing you to select elements that satisfy the condition. It receives the old value and index as arguments, providing the flexibility to make conditional updates.
In addition to .startsWith
, you can use other array methods like .find
to filter for the values that you need.
Modifying objects
When using store setters to modify objects, if a new value is an object, it will be shallow merged with the existing value. What this refers to is that the properties of the existing object will be combined with the properties of the "new" object you are setting, updating any overlapping properties with the values from the new object.
What this means, is that you can directly make the change to the store without spreading out properties of the existing user object.
Store utilities
Store updates with produce
Rather than directly modifying a store with setters, Solid has the produce
utility.
This utility provides a way to work with data as if it were a mutable JavaScript object.
produce
also provides a way to make changes to multiple properties at the same time which eliminates the need for multiple setter calls.
produce
and setStore
do have distinct functionalities.
While both can be used to modify the state, the key distinction lies in how they handle data.
produce
allows you to work with a temporary draft of the state, apply the changes, then produce a new immutable version of the store.
Comparatively, setStore
provides a more straightforward way to update the store directly, without creating a new version.
It's important to note, however, produce
is specifically designed to work with arrays and objects.
Other collection types, such as JavaScript Sets and Maps, are not compatible with this utility.
Data integration with reconcile
When new information needs to be merged into an existing store reconcile
can be useful.
reconcile
will determine the differences between new and existing data and initiate updates only when there are changed values, thereby avoiding unnecessary updates.
In this example, the store will look for the differences between the existing and incoming data sets.
Consequently, only 'koala'
- the new edition - will cause an update.
Extracting raw data with unwrap
When there is a need for dealing with data outside of a reactive context, the unwrap
utility offers a way to transform a store to a standard object.
This conversion serves several important purposes.
Firstly, it provides a snapshot of the current state without the processing overhead associated with reactivity.
This can be useful in situations where an unaltered, non-reactive view of the data is needed.
Additionally, unwrap
provides a means to interface with third-party libraries or tools that anticipate regular JavaScript objects.
This utility acts as a bridge to facilitate smooth integrations with external components and simplifies the incorporation of stores into various applications and workflows.
To learn more about how to use Stores in practice, visit the guide on complex state management.