State management
Edit this pageState management is process of handling and manipulating data that affects the behavior and presentation of a web application. To build interactive and dynamic web applications, state management is a critical aspect of development. Within Solid, state management is facilitated through the use of reactive primitives.
These state management concepts will be shown using a basic counter example:
There are 3 elements to state management:
-
State (
count
): The data that is used to determine what content to display to the user. -
View (
<div>{count()}</div>
): The visual representation of the state to the user. -
Actions (
increment
): Any event that modifies the state.
These elements work together to create a "one way data flow". When actions modify the state, the view is updated to show the current state to the user. One way data flow simplifies the management of data and user interactions, which provides a more predictable and maintainable application.
Managing basic state
State is the source of truth for the application, and is used to determine what content to display to the user. State is represented by a signal, which is a reactive primitive that manages state and notifies the UI of any changes.
To create a piece of state, you use the createSignal
function and pass in the initial value of the state:
To access the current value of the state, you call the signal's getter function:
To update the state, you use the signal's setter function:
With signals, you can create and manage state in a simple and straightforward manner. This allows you to focus on the logic of your application, rather than the complexities of state management. Additionally, signals are reactive, which means as long as it is accessed within a tracking scope, it will always be up to date.
Rendering state in the UI
To achieve a dynamic user interface, the UI must be able to reflect the current state of the data. The UI is the visual representation of the state to the user, and is rendered using JSX. JSX provides a tracking scope, which keeps the view in sync with the state.
Revisiting the Counter
component presented earlier, rendering the current state of count
is done within the return body using JSX:
To render the current state of count
, the JSX expression {count()}
is used.
The curly braces indicate that the expression is a JavaScript expression, and the parentheses indicate that it is a function call.
This expression is representative of a getter function for count
and will retrieve the current state value.
When the state is updated, the UI will be re-rendered to reflect the new state value.
Components in Solid only run once upon their initialization. After this initial render, if any changes are made to the state, only the portion of the DOM that is directly associated with the signal change will be updated.
The ability to update only the relevant portions of the DOM is a key feature of Solid that allows for performant and efficient UI updates. This is known as fine-grained reactivity. Through reducing the re-rendering of entire components or larger DOM segments, UI will remain more efficient and responsive for the user.
Reacting to changes
When the state is updated, any updates are reflected in the UI. However, there may be times when you want to perform additional actions when the state changes.
For example, in the Counter
component, you may want to display the doubled value of count
to the user.
This can be achieved through the use of effects, which are reactive primitives that perform side effects when the state changes:
The createEffect
function sets up a function to perform side effects whenever the state is modified.
Here, a side-effect refers to operations or updates that affect state outside of the local environment - like modifying a global variable or updating the DOM - triggered by those state changes.
In the Counter
component, a createEffect
function can be used to update the doubleCount
state whenever the count
state changes.
This keeps the doubleCount
state in sync with the count
state, and allows the UI to display the doubled value of count
to the user.
View this example of doubleCount
in a createEffect
in the Solid Playground example.
Derived state
When you want to calculate new state values based on existing state values, you can use derived state. This is a useful pattern when you want to display a transformation of a state value to the user, but do not want to modify the original state value or create a new state value.
Derived values can be created using a signal within a function, which can be referred to as a derived signal.
This approach can be used to simplify the doubleCount
example above, where the additional signal and effect can be replaced with a derived signal:
While this approach works for simple use cases, if doubleCount
is used several times within a component or contains a computationally expensive calculation, it can lead to performance issues.
The derived signal would be re-evaluated not just each time count
is changed, but also for each use of doubleCount()
.
For cases like this, you can use Memos to store the value of doubleCount
, which are also referred to as a memoized or cached value.
When using a memo, the calculation will only run once when the value of count
changes and can be accessed multiple times without re-evaluating for each additional use.
Using the createMemo
function, you can create a memoized value:
While accessed multiple times, the doubleCountMemo
will only re-evaluate and log once.
This is different from the derived signal, doubleCount
, which is re-evaluated for each time it is accessed.
View a similar example comparing a derived signal and a memo in the Solid Playground.
Lifting state
When you want to share state between components, you can lift state up to a common ancestor component. While state is not tied to components, you may want to link multiple components together in order to access and manipulate the same piece of state. This can keep things synchronized across the component tree and allow for more predictable state management.
For example, in the Counter
component, you may want to display the doubled value of count
to the user through a separate component:
To share the count
state between the Counter
and DisplayCounts
components, you can lift the state up to the App
component.
This allows the Counter
and DisplayCounts
functions to access the same piece of state, but also allows the Counter
component to update the state through the setCount
setter function.
When sharing state between components, you can access the state through props
.
Props values that are passed down from the parent component are read-only, which means they cannot be directly modified by the child component.
However, you can pass down setter functions from the parent component to allow the child component to indirectly modify the parent's state.
To encourage one-way data flow, props are passed as read-only or immutable values from the parent to child components.
There are specific utility functions for props, however, that offer methods to modify props values.
Managing complex state
As applications grow in size and complexity, lifting state can become difficult to manage. To avoid the concept of prop drilling, which is the process of passing props through multiple components, Solid offers stores to manage state in a more scalable and maintainable manner.
To learn more about managing complex state, navigate to the complex state management page.