Complex state management
Edit this pageAs 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 can become difficult to maintain.
Consider this example:
There are several challenges to managing state in this way:
-
Increased verbosity with the multiple
createSignal
calls fortasks
,numberOfTasks
, as well as acreateMemo
function forcompletedTasks
. Additionally, with each state update, there requires manual updates to other related states which risks the application becoming out of sync. -
While Solid is optimized, this components design leads to frequent recalculations, such as updating
completedTasks
with every toggle action, which can negatively impact performance. In addition, the dependence on the component's logic on the current state fornumberOfTasks
andcompletedTasks
can complicate code understanding.
As an application like this scales, managing state in this manner becomes even more complex. Introducing other dependent state variables would require updates across the entire component which would likely introduce more errors. This 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.
Introducing stores
Through recreating this list using Stores, you will see how stores can improve the readability and management of your code.
If you're new to the concept of stores, see the stores section.
Creating a store
To reduce the amount of signals that were used in the original example, you can do the following using a store:
Through using a store, you no longer need to keep track of separate signals for tasks
, numberOfTasks
, and completedTasks
.
Accessing state values
Once you have created your store, the values can be accessed directly through the first value returned by the createStore
function:
Through state.numberOfTasks
, the display will now show the store's value held in the numberOfTasks
property.
Making changes to the store
When you want to modify your store, you use the second element returned by the createStore
function.
This element allows you to make modifications to the store, letting you both add new properties and update existing ones.
However, because properties within a store are created lazily, setting a property in the component function body without creating a reactive scope will not update the value.
To create the signal so it reactively updates, you have to access the property within a tracking scope, such as using a createEffect
:
Adding to an array
To 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
.
By pinpointing the tasks
key in combination with the upcoming position, the new task is added to the end of the array.
The setter in stores follow path syntax: setStore("key", value)
.
In 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.
Mutating state with produce
In 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
utility function.
Something such as toggle function:
Can be simplified using produce
:
Read about some of the other advantages to using produce
.
Another benefit to working with produce
is that it offers a way to modify a store without having to make multiple setStore
calls.
The updated example:
State sharing
As applications grow and become more complex, sharing state between components can become a challenge. Passing state and functions from parent to child components, especially across multiple levels, is commonly referred to as "prop drilling". Prop drilling can lead to verbose, hard-to-maintain code, and can make the data flow in an application more difficult to follow. To solve this problem and allow for a more scalable and maintainable codebase, Solid provides context.
To use this, you need to create a context. This context will have a default value and can be consumed by any descendant component.
Your components will be wrapped with the Provider
from the context, and passed with the values that you wish to share.
In any descendent component, you can consume the context values using useContext
:
For a deeper dive, please refer to our dedicated page on context.