Solid Primitives
In this page we will be explaining what you need to know to get started with using Solid.js primitives and making use of Solid.js primitives inside and outside your components.
Solid Primitives
First let's start with Solid.js primitves. What are primitives? Primitives are the simplest elements, functions or data types available in any programming language, framework or library. In Solid.js, primitives are the building blocks that will be responsible for governing reactivity within your Solid.js app. Here's a list of Solid.js primitives:
createSignal
createEffect
createMemo
createResource
createRoot
createRenderEffect
createDeferred
createComputed
createContext
createMutable
createStore
Note: Solid primitives can be used in any scope, they are not required to be used within a component. Solid components do not own their state because they are just setup functions that have nothing to do with the reactivity system after set up.
We will be focusing on the most used primitives, createSignal
, createMemo
, createEffect
and createResource
. To have a more in depth understanding of all the primitives Solid.js has, check out the concepts page.
createSignal
createSignal
is the most used primitive in Solid.js. It is used to create a reactive state variable. It takes an optional initial value and returns a tuple with the getter function to get the current value and a setter function to set the value. The setter function is used to update the value of the state variable.
createSignal
can be used to store almost any type of data type, including objects and arrays. However, in Solid it is advisable to use the createStore
primitive to store objects and arrays. This is because createStore
is optimized to handle cases of nested reactivity. More on this in the reference section.
js
import { createSignal } from "solid-js";const [count, setCount] = createSignal(0);
js
import { createSignal } from "solid-js";const [count, setCount] = createSignal(0);
createSignal
doesn't need to be used within a component. It can be used anywhere in your app. Whether it's in a component, a function or even outside of a component or function.
js
import { createSignal } from "solid-js";const [count, setCount] = createSignal(0);function increment() {setCount(count() + 1);}function decrement() {setCount(count() - 1);}function Counter() {return (<div><button onClick={decrement}>-</button><span>{count()}</span><button onClick={increment}>+</button></div>);}
js
import { createSignal } from "solid-js";const [count, setCount] = createSignal(0);function increment() {setCount(count() + 1);}function decrement() {setCount(count() - 1);}function Counter() {return (<div><button onClick={decrement}>-</button><span>{count()}</span><button onClick={increment}>+</button></div>);}
In the example above we have a count
state variable that is used to keep track of the current count. We have two functions, increment
and decrement
that are used to update the count
state variable. We also have a component, Counter
that uses the count
state variable to display the current count.
As you've noticed in the example above, we are using createSignal
outside of a component. This is perfectly fine, it is even possible to import your signal from another file and have it still behave reactively. You can use createSignal
anywhere in your app since in Solid components do not own their state. In this way solid has state management built in and there is no need for Redux or other state management libraries unless you would like to make use of features specific to them.
Since you're coming from React
The React counterpart to this is the useState
hook. The
useState
hook is used to create a state variable that is local to
a component. This means that the state variable will be destroyed when the
component is unmounted. In Solid, components do not own their state. This
means that the state variable will not be destroyed when the component is
unmounted. This is because Solid components are just setup functions that have
nothing to do with the reactivity system after set up.
Since you're coming from Vue
The Vue counterpart to this is the ref
function. The
ref
function is used to create a state variable that is local to
a component. This means that the state variable will be destroyed when the
component is unmounted. In Solid, components do not own their state. This
means that the state variable will not be destroyed when the component is
unmounted. This is because Solid components are just setup functions that have
nothing to do with the reactivity system after set up.
Since you're coming from Svelte
The Svelte counterpart to this is the let
keyword. The
let
keyword is used to create a state variable that is local to a
component. This means that the state variable will be destroyed when the
component is unmounted. In Solid, components do not own their state. This
means that the state variable will not be destroyed when the component is
unmounted. This is because Solid components are just setup functions that have
nothing to do with the reactivity system after set up.
createMemo
createMemo
is used to create a reactive state variable that is derived from other reactive state variables. It takes a function that returns the value of the state variable and returns a getter function to get the current value.
js
import { createSignal, createMemo } from "solid-js";const [count, setCount] = createSignal(0);const double = createMemo(() => count() * 2);
js
import { createSignal, createMemo } from "solid-js";const [count, setCount] = createSignal(0);const double = createMemo(() => count() * 2);
In the example above we have a count
state variable that is used to keep track of the current count. We also have a double
state variable that is derived from the count
state variable. The double
state variable is a memoized version of the count
state variable. This means that the double
state variable will only be recomputed when the count
state variable changes.
Here's a quick example of how you can make use of createMemo
to create a memoized derived state and make use of it in a component.
js
import { createMemo } from "solid-js";const [count, setCount] = createSignal(0);const double = createMemo(() => count() * 2);function Counter() {return (<div><button onClick={() => setCount(count() - 1)}>-</button><span>This is the base value : {count()}</span><span>This is the doubled value : {double()}</span><button onClick={() => setCount(count() + 1)}>+</button></div>);}
js
import { createMemo } from "solid-js";const [count, setCount] = createSignal(0);const double = createMemo(() => count() * 2);function Counter() {return (<div><button onClick={() => setCount(count() - 1)}>-</button><span>This is the base value : {count()}</span><span>This is the doubled value : {double()}</span><button onClick={() => setCount(count() + 1)}>+</button></div>);}
In the example above we have a count
state variable that is used to keep track of the current count. We also have a double
state variable that is derived from the count
state variable. The double
state variable is a memoized version of the count
state variable. This means that the double
state variable will only be recomputed when the count
state variable changes.
Under the hood createMemo
uses createComputed
to create a computed value. More on the createComputed
primitive in the reference section.
Since you're coming from React
The React counterpart to this is the useMemo
hook. The
useMemo
hook is used to create a memoized state variable that is
derived from other state variables. This means that the memoized state
variable will only be recomputed when one of the state variables it is derived
from changes.
Since you're coming from Vue
The Vue counterpart to this is the computed
function. The
computed
function is used to create a memoized state variable
that is derived from other state variables. This means that the memoized state
variable will only be recomputed when one of the state variables it is derived
from changes.
createEffect
createEffect
is used to create a reactive effect. It takes a function that will be executed whenever the reactive state variables used in the function change.
js
import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => {console.log("Count has been updated : "count());});function Counter() {return (<div><button onClick={() => setCount(count() - 1)}>-</button><span>{count()}</span><button onClick={() => setCount(count() + 1)}>+</button></div>);}
js
import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => {console.log("Count has been updated : "count());});function Counter() {return (<div><button onClick={() => setCount(count() - 1)}>-</button><span>{count()}</span><button onClick={() => setCount(count() + 1)}>+</button></div>);}
Each time the state of count
changes, the effect will be executed. This is useful for logging, making API calls, etc. Any side effect that you would like to run whenever the state of a reactive state variable changes can be done using createEffect
.
Since you're coming from React
The React counterpart to this is the useEffect
hook. The
useEffect
hook is used to create a side effect that will be
executed whenever the state of a state variable changes.
Since you're coming from Vue
The Vue counterpart to this is the watch
function. The
watch
function is used to create a side effect that will be
executed whenever the state of a state variable changes.
Since you're coming from Svelte
The Svelte counterpart to this is the $:
syntax. The
$:
syntax is used to create a side effect that will be executed
whenever the state of a state variable changes.
createResource
createResource
is a simple but very useful primitive that creates a signal that reflects the value of an asynchronous function. It takes in an asynchronous function, optional options and an optional source signal that will be used to fill in the argument of the asynchronous function.
Here's a quick example of how you can use createResource
to fetch data from an API.
js
import { createResource, Show } from "solid-js";async function fetchUser(id) {const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);return await response.json();}function User({ id }) {const [id, setId] = createSignal(1);const [user] = createResource(id, fetchUser);return (<div><Show when={!user.loading} fallback={<LoadingComponent />}><div>Name : {user().name}</div><div>Username : {user().username}</div><div>Email : {user().email}</div><div>Phone : {user().phone}</div><div>Website : {user().website}</div></Show></div>);}
js
import { createResource, Show } from "solid-js";async function fetchUser(id) {const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);return await response.json();}function User({ id }) {const [id, setId] = createSignal(1);const [user] = createResource(id, fetchUser);return (<div><Show when={!user.loading} fallback={<LoadingComponent />}><div>Name : {user().name}</div><div>Username : {user().username}</div><div>Email : {user().email}</div><div>Phone : {user().phone}</div><div>Website : {user().website}</div></Show></div>);}
createResource
returns some pretty cool stuff that you can use to display the state of the resource reactively, as you can see it has a loading
property that is a boolean that is true when the resource is loading and false when it is not. It also has an error
property that is an error object if there was an error while fetching the resource. createResource
also has some options we can make use of to customize the behaviour. Here's a list of them and a short description on what they do.
initialValue
: The initial value of the resource. This is useful if you want to display something while the resource is loading.name
: The name of the resource. This is useful for debugging.deferStream
: A boolean that determines whether your app should wait for the resource to finish before rendering to a stream. Note that this is only useful if you're making use of therenderToStream
function.ssrLoadFrom
: This can be used to tell the resource if it should load it's initial data from theinitialValue
or from the server. This is useful if you have some server-side data that you would like to assign to this resource. This can be used by passing either'initial'
or'server'
to this option.storage
: This option serves as an declarative way for you to store the resource's data. This is useful if you would like to store the data in a cache or something like that. This option takes a function that returns a tuple with two functions, an Accessor and a Setter of types any. Here's an example of how you can use this option.
tsx
import { createResource } from "solid-js";async function fetcherFunc(id: number) {const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);return await response.json();}const [id, setId] = createSignal<number>(1);const [store, setStore] = createSignal<any>({});const [user] = createResource(id, fetcherFunc, {storage: (init: any) => [store, setStore],});
tsx
import { createResource } from "solid-js";async function fetcherFunc(id: number) {const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);return await response.json();}const [id, setId] = createSignal<number>(1);const [store, setStore] = createSignal<any>({});const [user] = createResource(id, fetcherFunc, {storage: (init: any) => [store, setStore],});
Once the resource's data is resolved in the above code it will be automatically stored in the store
signal.
If you want to use multiple signal values within a single resource, you can do so by creating a derived state with the createMemo
primitive. You may be wondering why I'm using the createMemo
primitive instead of just creating a standard derived state. That's because createMemo
will aid in keeping your derived state reactive, whereas if you create a normal derived state, the derived state will not be updated if the state value changes. Here's how it might look in action.
tsx
import { createResource, createMemo } from "solid-js";// the fetcher function changed to accept an objectasync function fetcherFunc(info: { id: number; name: string }) {const response = await fetch(`https://jsonplaceholder.typicode.com/users/${info.id}/posts?name=${info.name}`);return await response.json();}const [id, setId] = createSignal<number>(1);const [name, setName] = createSignal<string>("");// the derived state made using createMemoconst derivedState = createMemo(() => ({ id: id(), name: name() }));const [posts] = createResource(derivedState, fetcherFunc);
tsx
import { createResource, createMemo } from "solid-js";// the fetcher function changed to accept an objectasync function fetcherFunc(info: { id: number; name: string }) {const response = await fetch(`https://jsonplaceholder.typicode.com/users/${info.id}/posts?name=${info.name}`);return await response.json();}const [id, setId] = createSignal<number>(1);const [name, setName] = createSignal<string>("");// the derived state made using createMemoconst derivedState = createMemo(() => ({ id: id(), name: name() }));const [posts] = createResource(derivedState, fetcherFunc);
This is just a simple way in which you can make use of more than one signal value within a resource.
To know more about Solid primitives you can check out the solid deep dive section