Files
Devour/devour_data/docs/complex-state-management.json
T
Tomas Dvorak 898a3c303f update
2026-02-24 10:33:59 +01:00

17 lines
12 KiB
JSON

{
"id": "95a23241d0d2704a48261618",
"source": "solid:signals",
"type": "github-document",
"title": "complex-state-management",
"content": "---\ntitle: Complex state management\norder: 5\nuse_cases: \u003e-\n scaling applications, multiple components, backend communication, state\n synchronization, prop drilling, shared state\ntags:\n - stores\n - state\n - context\n - scaling\n - components\n - management\nversion: '1.0'\ndescription: \u003e-\n Master complex state management in Solid using stores and context to build\n scalable, maintainable applications efficiently.\n---\n\nAs applications grow and start to involve many components, more intricate user interactions, and possibly communication with backend services, you may find that staying organized with more [basic state management methods](/guides/state-management) can become difficult to maintain.\n\nConsider this example:\n\n```jsx\nimport { For, createSignal, Show, createMemo } from \"solid-js\"\n\nconst App = () =\u003e {\n\tconst [tasks, setTasks] = createSignal([])\n\tconst [numberOfTasks, setNumberOfTasks] = createSignal(tasks.length)\n\tconst completedTasks = createMemo(() =\u003e tasks().filter((task) =\u003e task.completed))\n\tlet input\n\n\tconst addTask = (text) =\u003e {\n\t\tsetTasks([...tasks(), { id: tasks().length, text, completed: false }])\n\t\tsetNumberOfTasks(numberOfTasks() + 1)\n\t}\n\tconst toggleTask = (id) =\u003e {\n\t\tsetTasks(\n\t\t\ttasks().map((task) =\u003e\n\t\t\t\ttask.id !== id ? task : { ...task, completed: !task.completed }\n\t\t\t)\n\t\t)\n\t}\n\n\treturn (\n\t\t\u003c\u003e\n\t\t\t\u003ch1\u003eMy list\u003c/h1\u003e\n\t\t\t\u003cspan\u003eYou have {numberOfTasks()} task(s) today!\u003c/span\u003e\n\t\t\t\u003cdiv\u003e\n\t\t\t\t\u003cinput ref={input} /\u003e\n\t\t\t\t\u003cbutton\n\t\t\t\t\tonClick={(e) =\u003e {\n\t\t\t\t\t\tif (!input.value.trim()) return\n\t\t\t\t\t\taddTask(input.value)\n\t\t\t\t\t\tinput.value = \"\"\n\t\t\t\t\t}}\n\t\t\t\t\u003e\n\t\t\t\t\tAdd Task\n\t\t\t\t\u003c/button\u003e\n\t\t\t\u003c/div\u003e\n\t\t\t\u003cFor each={tasks()}\u003e\n\t\t\t\t{(task) =\u003e {\n\t\t\t\t\tconst { id, text } = task\n\t\t\t\t\tconsole.log(`Creating ${text}`)\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\u003cdiv\u003e\n\t\t\t\t\t\t\t\u003cinput\n\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\tchecked={task.completed}\n\t\t\t\t\t\t\t\tonChange={[toggleTask, id]}\n\t\t\t\t\t\t\t/\u003e\n\t\t\t\t\t\t\t\u003cspan\n\t\t\t\t\t\t\t\tstyle={{\n\t\t\t\t\t\t\t\t\t\"text-decoration\": task.completed ? \"line-through\" : \"none\",\n\t\t\t\t\t\t\t\t}}\n\t\t\t\t\t\t\t\u003e\n\t\t\t\t\t\t\t\t{text}\n\t\t\t\t\t\t\t\u003c/span\u003e\n\t\t\t\t\t\t\u003c/div\u003e\n\t\t\t\t\t)\n\t\t\t\t}}\n\t\t\t\u003c/For\u003e\n\t\t\u003c/\u003e\n\t)\n}\n\nexport default App\n```\n\nThere are several challenges to managing state in this way:\n\n- Increased verbosity with the multiple `createSignal` calls for `tasks`, `numberOfTasks`, as well as a `createMemo` function for `completedTasks`.\n Additionally, with each state update, there requires manual updates to other related states which risks the application becoming out of sync.\n\n- While Solid is optimized, this components design leads to frequent recalculations, such as updating `completedTasks` with every toggle action, which can negatively impact performance.\n In addition, the dependence on the component's logic on the current state for `numberOfTasks` and `completedTasks` can complicate code understanding.\n\nAs an application like this scales, managing state in this manner becomes even more complex.\nIntroducing other dependent state variables would require updates across the _entire_ component which would likely introduce more errors.\nThis would likely make it more difficult to separate specific functionalities into distinct, reusable components without transferring a substantial portion of state management logic, as well.\n\n## Introducing stores\n\nThrough recreating this list using Stores, you will see how stores can improve the readability and management of your code.\n\nIf you're new to the concept of stores, see the [stores section](/concepts/stores).\n\n## Creating a store\n\nTo reduce the amount of signals that were used in the original example, you can do the following using a store:\n\n```jsx\nimport { createStore } from \"solid-js/store\"\n\nconst App = () =\u003e {\n\tconst [state, setState] = createStore({\n\t\ttasks: [],\n\t\tnumberOfTasks: 0,\n\t})\n}\n\nexport default App\n```\n\nThrough using a store, you no longer need to keep track of separate signals for `tasks`, `numberOfTasks`, and `completedTasks`.\n\n## Accessing state values\n\nOnce you have created your store, the values can be accessed directly through the first value returned by the `createStore` function:\n\n```jsx\nimport { createStore } from \"solid-js/store\"\n\nconst App = () =\u003e {\n\tconst [state, setState] = createStore({\n\t\ttasks: [],\n\t\tnumberOfTasks: 0,\n\t})\n\treturn (\n\t\t\u003c\u003e\n\t\t\t\u003ch1\u003eMy Task List for Today\u003c/h1\u003e\n\t\t\t\u003cspan\u003eYou have {state.numberOfTasks} task(s) for today!\u003c/span\u003e\n\t\t\u003c/\u003e\n\t)\n}\n\nexport default App\n```\n\nThrough `state.numberOfTasks`, the display will now show the store's value held in the `numberOfTasks` property.\n\n## Making changes to the store\n\nWhen you want to modify your store, you use the second element returned by the `createStore` function.\nThis element allows you to make modifications to the store, letting you both add new properties and update existing ones.\nHowever, because properties within a store are created lazily, setting a property in the component function body without creating a tracking scope will **not** update the value.\nTo create the signal so it reactively updates, you have to access the property within a tracking scope, such as using a [`createEffect`](/reference/basic-reactivity/create-effect):\n\n```jsx\n// not reactive\nsetState(\"numberOfTasks\", state.tasks.length)\n\n// reactive\ncreateEffect(() =\u003e {\n\tsetState(\"numberOfTasks\", state.tasks.length)\n})\n```\n\n### Adding to an array\n\nTo add an element to an array, in this case the new task, you can append to the next index of the array through `state.tasks.length`.\nBy pinpointing the `tasks` key in combination with the upcoming position, the new task is added to the end of the array.\n\n```jsx\nconst addTask = (text) =\u003e {\n\tsetState(\"tasks\", state.tasks.length, {\n\t\tid: state.tasks.length,\n\t\ttext,\n\t\tcompleted: false,\n\t})\n}\n```\n\nThe setter in stores follow [path syntax](/concepts/stores#path-syntax-flexibility): `setStore(\"key\", value)`.\nIn the `addTask` function the `tasks` array is appended through `setState(\"tasks\", state.tasks.length, { id: state.tasks.length, text, completed: false })`, an example of this in action.\n\n#### Mutating state with `produce`\n\nIn situations where you need to make multiple `setState` calls and target multiple properties, you can simplify your code and improve readability by using Solid's [`produce`](/concepts/stores#store-updates-with-produce) utility function.\n\nSomething such as toggle function:\n\n```jsx\nconst toggleTask = (id) =\u003e {\n\tconst currentCompletedStatus = state.tasks[id].completed\n\tsetState(\n\t\t\"tasks\",\n\t\t(task) =\u003e task.id === id,\n\t\t\"completed\",\n\t\t!currentCompletedStatus\n\t)\n}\n```\n\nCan be simplified using `produce`:\n\n```jsx\nimport { produce } from \"solid-js/store\"\n\nconst toggleTask = (id) =\u003e {\n\tsetState(\n\t\t\"tasks\",\n\t\t(task) =\u003e task.id === id,\n\t\tproduce((task) =\u003e {\n\t\t\ttask.completed = !task.completed\n\t\t})\n\t)\n}\n\n// You can also rewrite the `addTask` function through produce\nconst addTask = (text) =\u003e {\n\tsetState(\n\t\t\"tasks\",\n\t\tproduce((task) =\u003e {\n\t\t\ttask.push({ id: state.tasks.length, text, completed: false })\n\t\t})\n\t)\n}\n```\n\nRead about some of the other [advantages to using `produce`](/concepts/stores#store-updates-with-produce).\n\n :::note\n Another benefit to working with `produce` is that it offers a way to modify a store without having to make multiple `setStore` calls.\n\n```jsx\n// without produce\nbatch(() =\u003e {\n\tsetState(0, \"text\", \"I'm updated text\")\n\tsetState(0, \"completed\", true)\n})\n\n// with produce\nsetState(\n\t0,\n\tproduce((task) =\u003e {\n\t\ttask.text = \"I'm updated text\";\n\t\ttask.completed = true;\n\t})\n)\n```\n\n :::\n\nThe updated example:\n\n```jsx\nimport { For, createEffect, Show } from \"solid-js\"\nimport { createStore, produce } from \"solid-js/store\"\n\nconst App = () =\u003e {\n\tlet input // lets you target the input value\n\tconst [state, setState] = createStore({\n\t\ttasks: [],\n\t\tnumberOfTasks: 0,\n\t})\n\n\tconst addTask = (text) =\u003e {\n\t\tsetState(\"tasks\", state.tasks.length, {\n\t\t\tid: state.tasks.length,\n\t\t\ttext,\n\t\t\tcompleted: false,\n\t\t})\n\t}\n\n\tconst toggleTask = (id) =\u003e {\n\t\tsetState(\n\t\t\t\"tasks\",\n\t\t\t(task) =\u003e task.id === id,\n\t\t\tproduce((task) =\u003e {\n\t\t\t\ttask.completed = !task.completed\n\t\t\t})\n\t\t)\n\t}\n\n\tcreateEffect(() =\u003e {\n\t\tsetState(\"numberOfTasks\", state.tasks.length)\n\t})\n\n\treturn (\n\t\t\u003c\u003e\n\t\t\t\u003cdiv\u003e\n\t\t\t\t\u003ch1\u003eMy Task List for Today\u003c/h1\u003e\n\t\t\t\t\u003cspan\u003eYou have {state.numberOfTasks} task(s) for today!\u003c/span\u003e\n\t\t\t\u003c/div\u003e\n\t\t\t\u003cinput ref={input} /\u003e\n\t\t\t\u003cbutton\n\t\t\t\tonClick={(e) =\u003e {\n\t\t\t\t\tif (!input.value.trim()) return\n\t\t\t\t\taddTask(input.value)\n\t\t\t\t\tinput.value = \"\"\n\t\t\t\t}}\n\t\t\t\u003e\n\t\t\t\tAdd Task\n\t\t\t\u003c/button\u003e\n\t\t\t\u003cFor each={state.tasks}\u003e\n\t\t\t\t{(task) =\u003e {\n\t\t\t\t\tconst { id, text } = task\n\t\t\t\t\treturn (\n\t\t\t\t\t\t\u003cdiv\u003e\n\t\t\t\t\t\t\t\u003cinput\n\t\t\t\t\t\t\t\ttype=\"checkbox\"\n\t\t\t\t\t\t\t\tchecked={task.completed}\n\t\t\t\t\t\t\t\tonChange={() =\u003e toggleTask(task.id)}\n\t\t\t\t\t\t\t/\u003e\n\t\t\t\t\t\t\t\u003cspan\u003e{text}\u003c/span\u003e\n\t\t\t\t\t\t\u003c/div\u003e\n\t\t\t\t\t)\n\t\t\t\t}}\n\t\t\t\u003c/For\u003e\n\t\t\u003c/\u003e\n\t)\n}\n\nexport default App\n```\n\n## State sharing\n\nAs applications grow and become more complex, sharing state between components can become a challenge.\nPassing state and functions from parent to child components, especially across multiple levels, is commonly referred to as \"prop drilling\".\nProp drilling can lead to verbose, hard-to-maintain code, and can make the data flow in an application more difficult to follow.\nTo solve this problem and allow for a more scalable and maintainable codebase, Solid provides [context](/concepts/context).\n\nTo use this, you need to create a context.\nThis context will have a default value and can be consumed by any _descendant_ component.\n\n```jsx\nimport { createContext } from \"solid-js\"\n\nconst TaskContext = createContext()\n```\n\nYour components will be wrapped with the `Provider` from the context, and passed with the values that you wish to share.\n\n```jsx\nimport { createStore } from \"solid-js/store\"\n\nconst TaskApp = () =\u003e {\n\tconst [state, setState] = createStore({\n\t\ttasks: [],\n\t\tnumberOfTasks: 0,\n\t})\n\n\treturn (\n\t\t\u003cTaskContext.Provider value={{ state, setState }}\u003e\n\t\t\t{/* Your components */}\n\t\t\u003c/TaskContext.Provider\u003e\n\t)\n}\n```\n\nIn any descendent component, you can consume the context values using `useContext`:\n\n```jsx\nimport { useContext } from \"solid-js\"\n\nconst TaskList = () =\u003e {\n\tconst { state, setState } = useContext(TaskContext)\n\n\t// Now you can use the shared state and functions\n}\n```\n\nFor a deeper dive, please refer to our dedicated [page on context](/concepts/context).",
"url": "https://github.com/solidjs/solid-docs/blob/HEAD/src/routes/guides/complex-state-management.mdx",
"metadata": {
"path": "src/routes/guides/complex-state-management.mdx",
"repo": "solidjs/solid-docs",
"repo_url": "https://github.com/solidjs/solid-docs.git",
"size": 10225,
"source_type": "github"
},
"hash": "ad6e2246b1766b52b960c1a985625d4c6cc918f35d6b12097cee89d3cd66a3fb",
"timestamp": "2026-02-23T11:43:00.187207785+01:00"
}