mirror of
https://github.com/Dvorinka/Devour.git
synced 2026-06-03 20:13:03 +00:00
17 lines
18 KiB
JSON
17 lines
18 KiB
JSON
{
|
|
"id": "22ae4551aa6e6f9b77910718",
|
|
"source": "solid:signals",
|
|
"type": "github-document",
|
|
"title": "stores",
|
|
"content": "---\ntitle: Stores\norder: 6\nuse_cases: \u003e-\n complex state, nested objects, arrays, shared state, fine-grained updates,\n state trees, global state\ntags:\n - stores\n - state\n - objects\n - arrays\n - nested\n - produce\n - reconcile\nversion: '1.0'\ndescription: \u003e-\n Manage complex nested state efficiently with stores that provide fine-grained\n reactivity for objects and arrays in Solid.\n---\n\nStores are a state management primitive that provide a centralized way to handle shared data and reduce redundancy.\nUnlike [signals](/concepts/signals), which track a single value and trigger a full re-render when updated, stores maintain fine-grained reactivity by updating only the properties that change.\nThey can produce a collection of reactive signals, each linked to an individual property, making them well-suited for managing complex state efficiently.\n\n## Creating a store\n\nStores can manage many data types, including: objects, arrays, strings, and numbers.\n\nUsing JavaScript's [proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) mechanism, reactivity extends beyond just the top-level objects or arrays.\nWith stores, you can now target nested properties and elements within these structures to create a dynamic tree of reactive data.\n\n```jsx\nimport { createStore } from \"solid-js/store\"\n\n// Initialize store\nconst [store, setStore] = createStore({\n\tuserCount: 3,\n\tusers: [\n\t\t{\n\t\t\tid: 0,\n\t\t\tusername: \"felix909\",\n\t\t\tlocation: \"England\",\n\t\t\tloggedIn: false,\n\t\t},\n\t\t{\n\t\t\tid: 1,\n\t\t\tusername: \"tracy634\",\n\t\t\tlocation: \"Canada\",\n\t\t\tloggedIn: true,\n\t\t},\n\t\t{\n\t\t\tid: 2,\n\t\t\tusername: \"johny123\",\n\t\t\tlocation: \"India\",\n\t\t\tloggedIn: true,\n\t\t},\n\t],\n})\n```\n\n## Accessing store values\n\nStore properties can be accessed directly from the state proxy through directly referencing the targeted property:\n\n```jsx\nconsole.log(store.userCount) // Outputs: 3\n```\n\nAccessing stores within a tracking scope follows a similar pattern to signals.\nWhile signals are created using the [`createSignal`](/reference/basic-reactivity/create-signal) function and require calling the signal function to access their values, store values can be directly accessed without a function call.\nThis provides access to the store's value directly within a tracking scope:\n\n```jsx\nconst App = () =\u003e {\n\tconst [mySignal, setMySignal] = createSignal(\"This is a signal.\")\n\tconst [store, setStore] = createStore({\n\t\tuserCount: 3,\n\t\tusers: [\n\t\t\t{\n\t\t\t\tid: 0,\n\t\t\t\tusername: \"felix909\",\n\t\t\t\tlocation: \"England\",\n\t\t\t\tloggedIn: false,\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 1,\n\t\t\t\tusername: \"tracy634\",\n\t\t\t\tlocation: \"Canada\",\n\t\t\t\tloggedIn: true,\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 2,\n\t\t\t\tusername: \"johny123\",\n\t\t\t\tlocation: \"India\",\n\t\t\t\tloggedIn: true,\n\t\t\t},\n\t\t],\n\t})\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t\u003ch1\u003eHello, {store.users[0].username}\u003c/h1\u003e {/* Accessing a store value */}\n\t\t\t\u003cspan\u003e{mySignal()}\u003c/span\u003e {/* Accessing a signal */}\n\t\t\u003c/div\u003e\n\t)\n}\n```\n\nWhen a store is created, it starts with the initial state but does _not_ immediately set up signals to track changes.\nThese signals are created **lazily**, meaning they are only formed when accessed within a tracking scope.\n\nOnce data is used within a tracking scope, such as within the return statement of a component function, computed property, or an effect, a signal is created and dependencies are established.\n\nFor 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.\n\n```tsx ins={9}\nconst App = () =\u003e {\n\tconst [store, setStore] = createStore({\n\t\tuserCount: 3,\n\t\tusers: [ ... ],\n\t})\n\n\tconst addUser = () =\u003e { ... }\n\n\tconsole.log(store.users.at(-1)) // This won't work\n\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t\u003ch1\u003eHello, {store.users[0].username}\u003c/h1\u003e\n\t\t\t\u003cp\u003eUser count: {store.userCount}\u003c/p\u003e\n \u003cbutton onClick={addUser}\u003eAdd user\u003c/button\u003e\n\t\t\u003c/div\u003e\n\t)\n}\n```\n\nRather, this would need to be in a tracking scope, like inside a [`createEffect`](/reference/basic-reactivity/create-effect), so that a dependency is established.\n\n```tsx del={9} ins={10-12}\nconst App = () =\u003e {\n\tconst [store, setStore] = createStore({\n\t\tuserCount: 3,\n\t\tusers: [ ... ],\n\t})\n\n\tconst addUser = () =\u003e { ... }\n\n\tconsole.log(store.users.at(-1))\n\tcreateEffect(() =\u003e {\n\t\tconsole.log(store.users.at(-1))\n\t})\n\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t\u003ch1\u003eHello, {store.users[0].username}\u003c/h1\u003e\n\t\t\t\u003cp\u003eUser count: {store.userCount}\u003c/p\u003e\n \u003cbutton onClick={addUser}\u003eAdd user\u003c/button\u003e\n\t\t\u003c/div\u003e\n\t)\n}\n```\n\n## Modifying store values\n\nUpdating values within a store is best accomplished using a setter provided by the `createStore` initialization.\nThis setter allows for the modification of a specific key and its associated value, following the format `setStore(key, newValue)`:\n\n```jsx \"setStore\"\nconst [store, setStore] = createStore({\n\tuserCount: 3,\n\tusers: [ ... ],\n})\n\nsetStore(\"users\", (currentUsers) =\u003e [\n\t...currentUsers,\n\t{\n\t\tid: 3,\n\t\tusername: \"michael584\",\n\t\tlocation: \"Nigeria\",\n\t\tloggedIn: false,\n\t},\n])\n```\n\nThe value of `userCount` could also be automatically updated whenever a new user is added to keep it synced with the users array:\n\n```tsx ins={11}\nconst App = () =\u003e {\n\tconst [store, setStore] = createStore({\n\t\tuserCount: 3,\n\t\tusers: [ ... ],\n\t})\n\n\tconst addUser = () =\u003e { ... }\n\n\tcreateEffect(() =\u003e {\n\t\tconsole.log(store.users.at(-1))\n\t\tsetStore(\"userCount\", store.users.length)\n\t})\n\n\treturn (\n\t\t\u003cdiv\u003e\n\t\t\t\u003ch1\u003eHello, {store.users[0].username}\u003c/h1\u003e\n\t\t\t\u003cp\u003eUser count: {store.userCount}\u003c/p\u003e\n \u003cbutton onClick={addUser}\u003eAdd user\u003c/button\u003e\n\t\t\u003c/div\u003e\n\t)\n}\n```\n\n:::note\nSeparating the read and write capabilities of a store provides a valuable debugging advantage.\n\nThis separation facilitates the tracking and control of the components that are accessing or changing the values.\n:::\n:::advanced\n A little hidden feature of stores is that you can also create nested stores to help with setting nested properties.\n\n```jsx\n const [store, setStore] = createStore({\n userCount: 3,\n users: [ ... ],\n })\n\n const [users, setUsers] = createStore(store.users)\n\n setUsers((currentUsers) =\u003e [\n ...currentUsers,\n {\n id: 3,\n username: \"michael584\",\n location: \"Nigeria\",\n loggedIn: false,\n },\n ])\n\n```\n 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`.\n\n Note that the above relies on `store.users` to be set already in the existing store.\n\n:::\n\n## Path syntax flexibility\n\nModifying a store using this method is referred to as \"path syntax.\"\nIn 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.\n\nString keys are used to precisely target particular values with path syntax.\nBy specifying these exact key names, you can directly retrieve the targeted information.\nHowever, path syntax goes beyond string keys and offers more versatility when accessing targeted values.\n\nInstead of employing the use of just string keys, there is the option of using an array of keys.\nThis method grants you the ability to select multiple properties within the store, facilitating access to nested structures.\nAlternatively, you can use filtering functions to access keys based on dynamic conditions or specific rules.\n\n\u003cEraserLink\n\thref=\"https://app.eraser.io/workspace/maDvFw5OryuPJOwSLyK9?elements=M6Y55ScNFDD_2HmRd4OJkQ\"\n\tpreview=\"https://app.eraser.io/workspace/maDvFw5OryuPJOwSLyK9/preview?elements=M6Y55ScNFDD_2HmRd4OJkQ\u0026type=embed\"\n/\u003e\n\nThe 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.\n\n## Modifying values in arrays\n\nPath syntax provides a convenient way to modify arrays, making it easier to access and update their elements.\nInstead of relying on discovering individual indices, path syntax introduces several powerful techniques for array manipulation.\n\n### Appending new values\n\nTo append values to an array in a store, use the setter function with the spread operator (`...`) or the path syntax. Both methods add an element to the array but differ in how they modify it and their reactivity behavior.\n\nThe spread operator creates a new array by copying the existing elements and adding the new one, effectively replacing the entire `store.users` array.\nThis replacement triggers reactivity for all effects that depend on the array or its properties.\n\n```jsx\nsetStore(\"users\", (otherUsers) =\u003e [\n\t...otherUsers,\n\t{\n\t\tid: 3,\n\t\tusername: \"michael584\",\n\t\tlocation: \"Nigeria\",\n\t\tloggedIn: false,\n\t},\n])\n```\n\nThe path syntax adds the new element by assigning it to the index equal to `store.users.length`, directly modifying the existing array.\nThis triggers reactivity only for effects that depend on the new index or properties like `store.users.length`, making updates more efficient and targeted.\n\n```jsx\nsetStore(\"users\", store.users.length, {\n\tid: 3,\n\tusername: \"michael584\",\n\tlocation: \"Nigeria\",\n\tloggedIn: false,\n})\n```\n\n### Modifying multiple elements\n\nWith path syntax, you can target a subset of elements of an array,\nor properties of an object, by specifying an array or range of indices.\n\nThe most general form is to specify an array of values.\nFor example, if `store.users` is an array of objects,\nyou can set the `loggedIn` property of several indices at once like so:\n\n```jsx\nsetStore(\"users\", [2, 7, 10], \"loggedIn\", false)\n// equivalent to (but more efficient than):\nsetStore(\"users\", 2, \"loggedIn\", false)\nsetStore(\"users\", 7, \"loggedIn\", false)\nsetStore(\"users\", 10, \"loggedIn\", false)\n```\n\nThis array syntax also works for object property names.\nFor example, if `store.users` is an object mapping usernames to objects,\nyou can set the `loggedIn` property of several users at once like so:\n\n```jsx\nsetStore(\"users\", [\"me\", \"you\"], \"loggedIn\", false)\n// equivalent to (but more efficient than):\nsetStore(\"users\", [\"me\"], \"loggedIn\", false)\nsetStore(\"users\", [\"you\"], \"loggedIn\", false)\n```\n\nFor arrays specifically, you can specify a range of indices via an object\nwith `from` and `to` keys (both of which are inclusive).\nFor example, assuming `store.users` is an array again,\nyou can set the `loggedIn` state for all users except index 0 as follows:\n\n```jsx\nsetStore(\"users\", { from: 1, to: store.users.length - 1 }, \"loggedIn\", false)\n// equivalent to (but more efficient than):\nfor (let i = 1; i \u003c= store.users.length - 1; i++) {\n setStore(\"users\", i, \"loggedIn\", false)\n}\n```\n\nYou can also include a `by` key in a range object to specify a step size,\nand thereby update a regular subset of elements.\nFor example, you can set the `loggedIn` state for even-indexed users like so:\n\n```jsx\nsetStore(\"users\", { from: 0, to: store.users.length - 1, by: 2 }, \"loggedIn\", false)\n// equivalent to (but more efficient than):\nfor (let i = 1; i \u003c= store.users.length - 1; i += 2) {\n setStore(\"users\", i, \"loggedIn\", false)\n}\n```\n\nMulti-setter syntax differs from the \"equivalent\" code in one key way:\na single store setter call automatically gets wrapped in a\n[`batch`](/reference/reactive-utilities/batch), so all the elements update\nat once before any downstream effects are triggered.\n\n### Dynamic value assignment\n\nPath syntax also provides a way to set values within an array using functions instead of static values.\nThese functions receive the old value as an argument, allowing you to compute the new value based on the existing one.\nThis dynamic approach is particularly useful for complex transformations.\n\n```jsx\nsetStore(\"users\", 3, \"loggedIn\" , (loggedIn) =\u003e !loggedIn)\n```\n\n### Filtering values\n\nTo update elements in an array based on specific conditions, you can pass a function as an argument.\nThis function acts as a filter, receiving the old value and index, and gives you the flexibility to apply logic that targets specific cases.\nThis might include using methods like `.startsWith()`, `includes()`, or other comparison techniques to determine which elements should be updated.\n\n```jsx\n// update users with username that starts with \"t\"\nsetStore(\"users\", (user) =\u003e user.username.startsWith(\"t\"), \"loggedIn\", false)\n\n// update users with location \"Canada\"\nsetStore(\"users\", (user) =\u003e user.location == \"Canada\" , \"loggedIn\", false)\n\n// update users with id 1, 2 or 3\nlet ids = [1,2,3]\nsetStore(\"users\", (user) =\u003e ids.includes(user.id) , \"loggedIn\", false)\n```\n\n## Modifying objects\n\nWhen using store setters to modify objects, if a new value is an object, it will be shallow merged with the existing value.\nWhat 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.\n\nWhat this means, is that you can directly make the change to the store _without_ spreading out properties of the existing user object.\n\n```jsx\nsetStore(\"users\", 0, {\n\tid: 109,\n})\n\n// is equivalent to\n\nsetStore(\"users\", 0, (user) =\u003e ({\n\t...user,\n\tid: 109,\n}))\n```\n\n## Store utilities\n\n### Store updates with `produce`\n\nRather than directly modifying a store with setters, Solid has the `produce` utility.\nThis utility provides a way to work with data as if it were a [mutable](https://developer.mozilla.org/en-US/docs/Glossary/Mutable) JavaScript object.\n`produce` also provides a way to make changes to multiple properties at the same time which eliminates the need for multiple setter calls.\n\n```jsx\nimport { produce } from \"solid-js/store\"\n\n// without produce\nsetStore(\"users\", 0, \"username\", \"newUsername\")\nsetStore(\"users\", 0, \"location\", \"newLocation\")\n\n// with produce\nsetStore(\n\t\"users\",\n\t0,\n\tproduce((user) =\u003e {\n\t\tuser.username = \"newUsername\"\n\t\tuser.location = \"newLocation\"\n\t})\n)\n```\n\n`produce` and `setStore` do have distinct functionalities.\nWhile both can be used to modify the state, the key distinction lies in how they handle data.\n`produce` allows you to work with a temporary draft of the state, apply the changes, then produce a new [immutable](https://developer.mozilla.org/en-US/docs/Glossary/Immutable) version of the store.\nComparatively, `setStore` provides a more straightforward way to update the store directly, without creating a new version.\n\nIt's important to note, however, `produce` is specifically designed to work with **arrays** and **objects**.\nOther collection types, such as JavaScript [Sets](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set) and [Maps](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map), are not compatible with this utility.\n\n### Data integration with `reconcile`\n\nWhen new information needs to be merged into an existing store `reconcile` can be useful.\n`reconcile` will determine the differences between new and existing data and initiate updates only when there are _changed_ values, thereby avoiding unnecessary updates.\n\n```jsx\nimport { createStore, reconcile } from \"solid-js/store\"\n\nconst [data, setData] = createStore({\n\tanimals: ['cat', 'dog', 'bird', 'gorilla']\n})\n\nconst newData = getNewData() // eg. contains ['cat', 'dog', 'bird', 'gorilla', 'koala']\nsetData('animals', reconcile(newData))\n\n```\n\nIn this example, the store will look for the differences between the existing and incoming data sets.\nConsequently, only `'koala'` - the new edition - will cause an update.\n\n### Extracting raw data with `unwrap`\n\nWhen there is a need for dealing with data outside of a tracking scope, the `unwrap` utility offers a way to transform a store to a standard [object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object).\nThis conversion serves several important purposes.\n\nFirstly, it provides a snapshot of the current state without the processing overhead associated with reactivity.\nThis can be useful in situations where an unaltered, non-reactive view of the data is needed.\nAdditionally, `unwrap` provides a means to interface with third-party libraries or tools that anticipate regular JavaScript objects.\nThis utility acts as a bridge to facilitate smooth integrations with external components and simplifies the incorporation of stores into various applications and workflows.\n\n```jsx\nimport { createStore, unwrap } from \"solid-js/store\"\n\nconst [data, setData] = createStore({\n\tanimals: [\"cat\", \"dog\", \"bird\", \"gorilla\"],\n})\n\nconst rawData = unwrap(data)\n```\n\nTo learn more about how to use Stores in practice, visit the [guide on complex state management](/guides/complex-state-management).",
|
|
"url": "https://github.com/solidjs/solid-docs/blob/HEAD/src/routes/concepts/stores.mdx",
|
|
"metadata": {
|
|
"path": "src/routes/concepts/stores.mdx",
|
|
"repo": "solidjs/solid-docs",
|
|
"repo_url": "https://github.com/solidjs/solid-docs.git",
|
|
"size": 16581,
|
|
"source_type": "github"
|
|
},
|
|
"hash": "61958e52071425b33047cc268f263526ff37efb7db686275f3da4adaa6094d46",
|
|
"timestamp": "2026-02-23T11:43:00.186914113+01:00"
|
|
} |