W↓
All docs
🔑
Sign Up/Sign In
docs.solidjs.com/concepts/ (+2)
Public Link
Apr 8, 2025, 10:09:43 AM - complete - 128.7 kB
Apr 8, 2025, 10:09:43 AM - complete - 128.7 kB
Apr 8, 2025, 10:08:41 AM - complete - 103.2 kB
Starting URLs:
https://docs.solidjs.com/concepts/intro-to-reactivity
Crawl Prefixes:
https://docs.solidjs.com/concepts/
https://docs.solidjs.com/advanced-concepts/
https://docs.solidjs.com/configuration/
## Page: https://docs.solidjs.com/concepts/intro-to-reactivity **Note**: While this guide is useful for understanding reactive systems, it does use some Solid-specific terminology. Reactivity powers the interactivity in Solid applications. This programming paradigm refers to a system's ability to respond to changes in data or state automatically. With Solid, reactivity is the basis of its design, ensuring application's stay up-to-date with its underlying data. * * * 1. Reactivity keeps the user interface (UI) and state in sync, which reduces the need for manual updates. 2. Real-time updates create a more responsive and interactive user experience. function Counter() { const [count, setCount] = createSignal(0); const increment = () => setCount((prev) => prev + 1); return ( <div> <span>Count: {count()}</span>{" "} {/* Only `count()` is updated when the button is clicked. */} <button type="button" onClick={increment}> Increment </button> </div> );} This `Counter` function sets up a button that, when clicked, calls the `increment` function to increase the `count` by one. This updates just the number displayed _without_ refreshing the entire component. * * * ### Signals Signals serve as core elements in reactive systems, playing an important role in data management and system responsiveness. They are responsible for storing and managing data, as well as triggering updates across the system. This is done through the use of getters and setters. const [count, setCount] = createSignal(0);// ^ getter ^ setter * **Getter**: A function responsible for accessing the current value of the signal. You call a getter to access the data stored in a signal within a component. * **Setter**: The function used to modify a signal's value. To trigger reactive updates across an application, you call a setter to update the value of a signal. console.log(count()); // `count()` is a getter that returns the current value of `count`, which is `0`.setCount(1); // the setter, `setCount`, updates the value of `count`.console.log(count()); // the updated value of `count` is now `1`. ### Subscribers Subscribers are the other core element in reactive systems. They are responsible for tracking changes in signals and updating the system accordingly. They are automated responders that keep the system up-to-date with the latest data changes. Subscribers work based on two main actions: * **Observation**: At their core, subscribers observe signals. This keeps the subscriber primed to pick up on any changes to the signal they are tracking. * **Response**: When a signal changes, the subscriber is notified. This triggers the subscriber to respond to the change in the signal. This can involve tasks like updating the UI or calling external functions. function Counter() { const [count, setCount] = createSignal(0); const increment = () => setCount((prev) => prev + 1); createEffect(() => { console.log(count()); }); // the `createEffect` will trigger the console log every time `count` changes.} * * * State management is the process of managing the state of an application. This involves storing and updating data, as well as responding to the changes in it. With Solid, state management is handled through signals and subscribers. Signals are used to store and update data, while subscribers are used to respond to changes in the data. ### Tracking changes Tracking changes involves monitoring any updates to the data and responding accordingly. This is done through the use of subscribers. When a signal is not accessed within a tracking scope, an update to the signal will not trigger an update. This happens because if a signal is not being tracked, it is not able to notify any subscribers of the change. const [count, setCount] = createSignal(0);console.log("Count:", count());setCount(1);// Output: Count: 0// `count` is not being tracked, so the console log will not update when `count` changes. Since initialization is a **one-time event**, if a signal is accessed _outside of a tracking scope_, it will not be tracked. To track a signal, it must be accessed within the scope of a subscriber. Reactive primitives, such as effects, can be used to create subscribers. const [count, setCount] = createSignal(0);createEffect(() => { console.log("Count:", count());});setCount(1);// Output: Count: 0// Count: 1 ### Updating the UI The UI of a Solid application is built using JSX. JSX creates a tracking scope behind the scenes, which allows signals to be tracked within the return statement of a component. function Counter() { const [count, setCount] = createSignal(0); const increment = () => setCount((prev) => prev + 1); return ( <div> <span>Count: {count()}</span>{" "} {/* ✅ will update when `count()` changes. */} <button type="button" onClick={increment}> Increment </button> </div> );} Components, much like other functions, will only run _once_. This means that if a signal is accessed outside of the return statement, it will run on initialization, but any updates to the signal will not trigger an update. function Counter() { const [count, setCount] = createSignal(0); const increment = () => setCount((prev) => prev + 1); console.log("Count:", count()); // ❌ not tracked - only runs once during initialization. createEffect(() => { console.log(count()); // ✅ will update whenever `count()` changes. }); return ( <div> <span>Count: {count()}</span>{/* ✅ will update whenever `count()` changes. */} <button type="button" onClick={increment}> Increment </button> </div> );} To learn more about managing state in Solid, visit the guide on state management. * * * Reactive systems are designed to respond to changes in data. These responses can be immediate or delayed, depending on the nature of the system. Often, the choice between these two depends on the requirements of the application and the nature of the tasks involved. ### Synchronous reactivity Synchronous reactivity is Solid's default reactivity mode, where a system responds to changes in a direct and linear fashion. When a signal changes, any corresponding subscribers are immediately updated in an ordered manner. With synchronous reactivity, the system is able to respond to changes in a predictable manner. This is useful in scenarios where the order of updates is important. For example, if a subscriber depends on another signal, it is important that the subscriber is updated after the signal it depends on. const [count, setCount] = createSignal(0);const [double, setDouble] = createSignal(0);createEffect(() => { setDouble(count() * 2);}); In this example, the `double` signal will always be updated after `count` due to synchronous reactivity. This ensures that `double` is always up-to-date with the latest value of `count`. ### Asynchronous reactivity Asynchronous reactivity is when a system responds to changes in a delayed or non-linear fashion. When a signal changes, the corresponding subscribers are not immediately updated. Instead, the system waits for a specific event or task to complete before updating the subscribers. This is important in scenarios where subscribers depend on multiple signals. In these cases, updating one signal before another could result in data inconsistency. For example, if a subscriber depends on two signals, it is important that the subscriber is updated after both signals have been updated. Rather, the system waits for both signals to be updated before updating the subscriber. **Note:** When asynchronous reactivity is present, it is important to ensure that the system is able to handle the delay in updates. `batch` can be used to delay an update so the subscriber runs after each signal has been updated. * * * * Signals are the core elements of a reactive system. They are responsible for storing and managing data. * Signals are both readable and writeable because of getters and setters. * Subscribers are automated responders that track changes in signals and update the system accordingly. * Signals and subscribers work together to ensure that the system is kept up-to-date with the latest data changes. * A reactive system is built on the principles of data-driven reactivity. This means that the system's reactivity is driven by the data it is built on. * Reactive systems can be synchronous or asynchronous. If you want to dive deeper, visit the guide on fine-grained reactivity. --- ## Page: https://docs.solidjs.com/concepts/understanding-jsx JSX is an extension for JavaScript. It allows you to write HTML-like code inside your JavaScript file which keeps your rendering logic and content in the same place. This provides a concise and readable way to create and represent components. * * * Solid was designed to align closely with HTML standards. const element = <h1>I'm JSX!!</h1> It offers a distinct advantage, however: to copy/paste solutions from resources like Stack Overflow; and to allow direct usage of templates from design tools. Solid sets itself apart by using JSX immediately as it returns DOM elements. This lets you use dynamic expressions within your HTML by allowing variables and functions to be references with the use of curly braces (`{ }`): const Component = () => { const animal = { breed: "cat", name: "Midnight" } return ( <p> I have a {animal.breed} named {animal.name}! </p> )} This means JavaScript content can be rendered on web pages based on an application's state or logic. Additionally, Solid's reactive system introduces fine-grained reactivity with JSX. This updates only the necessary parts of the DOM when changes occur in the underlying state. * * * ### Return a single root element Where HTML lets you have disconnected tags at the top level, JSX requires that a component to return a single root element. JSX maintains the familiar nested, tree-like structure found in HTML. As a result, parent-child relationships between elements become easier to follow. ### Close all tags Self-closing tags are a must in JSX. Unlike in HTML, where elements like `<input>`, `<img>`, or `<br>` don't require explicit closure, JSX requires consistent self-closing tags. This helps to avoid potential rendering issues. <img src="./image-here.png" /> ### Properties vs. attributes HTML attributes and JSX properties may seem similar, but they serve different purposes and behave differently. Both offer ways to specify configurations or pass information. However, HTML is used for standard web content and JSX creates Solid's component logic. #### HTML attributes HTML attributes are values set directly on HTML elements. They provide additional information about an element to guide its initial behavior and state. These attributes are often translated into properties on DOM objects once the browser parses the HTML. In JSX files, HTML attributes are used much like regular HTML, with a few key differences due to the blend of HTML and JavaScript: * Event listeners such as `onClick` can be in camelCase or lowercase. (**Note:** When using ESLint, you will get a warning if you use lowercase.) * In cases where you can dynamically specify a value, you can replace the `"` and `"` with curly braces (`{ }`): <button class="myClass" onClick={handleClick}> Click me!</button> ### JSX properties (props) JSX properties, commonly known as "props," help with the passing of data and configurations to components within an application. They connect the component with the data it requires, for seamless data flows and dynamic interactions. #### Core concepts * **Static props**: In Solid's JSX, static props are integrated directly into the HTML by cloning the template and using them as attributes. * **Dynamic props**: Dynamic props rely on state, allowing the content or properties to be dynamic. An example is changing the style of an element in response to interactions within an application. This can be expressed in the form of signals (`value={value()}`). * **Data transfer**: Props are also used to fill components with data that comes from resources, like `createResource` calls. This results in components that react in real-time to data changes. For how to use props effectively in Solid, explore the props page. --- ## Page: https://docs.solidjs.com/concepts/components/basics Components are the building blocks of Solid applications. These units are reusable and can be combined to create more complex applications. Components are functions that return JSX elements: function MyComponent() { return <div>Hello World</div>;} A component can be as simple as a single element or as complex as a full page. They can also be nested within each other to create more intricate applications: function App() { return ( <div> <MyComponent /> </div> );} * * * A web page is displayed by rendering a component tree, which is a hierarchical structure of components. At the top of the tree is the primary application component, which is the root of the tree. Child components are nested within the primary component, and those components can have their own child components. This nesting can continue as needed. A simple application may have a component tree that looks like this: App // primary application component└── MyComponent // child component When an application grows, the component tree can become more complex. For example, a more complex application may have a component tree that looks like this: App├── Header├── Sidebar├── Content│ ├── Post│ │ ├── PostHeader│ │ ├── PostContent│ │ └── PostFooter│ ├── Post│ │ ├── PostHeader│ │ ├── PostContent│ │ └── PostFooter│ └── Post│ ├── ...└── Footer In nesting components, you can create a hierarchy of components that can be reused throughout the application. This allows for a more modular approach to building applications, as components can be reused in different contexts. * * * Components have a lifecycle that defines how they are created, updated, and destroyed. A Solid component's lifecycle is different from other frameworks, as it is tied to the concept of reactivity. Where frameworks may re-run components on every state change, a Solid component's lifecycle is tied to its initial run. What this means is that a Solid component is only run once, when it is first rendered into the DOM. After that, the component is not re-run, even if the application's state changes. When the Solid component renders, it sets up a reactive system that monitors for state changes. When a state change occurs, the component will update the relevant areas without re-running the entire component. By bypassing the full component lifecycle on every state change, Solid has a more predictable behavior compared to frameworks that re-run functions on every update. Since the component's logic is not continuously visited, getting this setup right is important when working with Solid. ### Initialization & configuration When a component is first rendered into the DOM, the component function is executed. This is where you will set up the component's state and side-effects. This includes setting up signals, stores, effects, and other reactive elements. Since the logic in the component function is not continuously visited, it is important to set up the component correctly from the outset. Each component instance is independent of other instances, meaning that each component has its own state and side-effects. Through establishing proper dependencies, you can ensure that the component is set up correctly. This allows for components to be reused in different contexts without affecting each other. function MyComponent() { const [count, setCount] = createSignal(0); console.log(count()); return ( <div> <p>Count: {count()}</p> <button onClick={() => setCount((prev) => prev + 1)}>Increment</button> </div> );} When this component is rendered into the DOM, the function body is executed. This includes creating the `count` signal and executing the `console.log(count())` statement, which will log the current value of `count` to the console. In addition, the component's JSX is returned, which will be rendered into the DOM. After the component is rendered, the `console.log` statement will not be executed again, even if the component's state changes. However, because the component's JSX is reactive, each press of the button will update the DOM with the new value of `count`. In essence, Solid splits the concerns: 1. The initial setup logic, which is executed once when the component is rendered. 2. The reactive logic, which is executed when the component's state changes. ### Conditional rendering To display different content based on state or other criteria, you can use conditional rendering. Given that the component function is only executed once, conditional statements must be placed within the return statement. This design ensures that conditional paths are clear and immediately understood. function MyComponent() { const [count, setCount] = createSignal(0); return ( <div> {count() > 5 ? ( <div>Count limit reached</div> ) : ( <> <p>Count: {count()}</p> <button onClick={() => setCount((prev) => prev + 1)}> Increment </button> </> )} </div> );} This example uses a ternary operator to conditionally render different content based on the value of `count`. When `count` is greater than 5, the component will display `"Count limit reached"`. Otherwise, it will display the current count with an increment button. * * * For components to be reusable, they need to be exported from one module and imported into another. This allows for components to be shared and used where needed. ### Exporting components Once defined, a component can be exported to make it available for use in other parts of your application. There are two ways to export a component: named exports and default exports. **Named export:** Named exports allow for multiple components to be exported from a single file. To export a component, you must use the `export` keyword before the function definition or specify the name of the component to export in curly braces (`{}`). export function MyComponent() { return <div>Hello World</div>}// orfunction MyComponent() { return <div>Hello World</div>}export { MyComponent } **Default export:** Default exports specify a single component to export from a file. This is done by using the `default` keyword. // MyComponent.tsexport default function MyComponent() { return <div>Hello World</div>} ### Importing components To use a component in another file or component, it must be imported. To import a component, you must specify the path to the file containing the component and the name of the component to import. **Named import:** When importing a named export, you must specify the name of the component to import in curly braces (`{}`). // App.tsimport { MyComponent } from "./MyComponent";function App() { return ( <div> <MyComponent /> </div> );} This is the preferred way to import components, as it allows for better code readability and maintainability. Additionally, it allows for multiple components to be imported from the same file. // App.tsimport { MyComponent, MyOtherComponent } from "./MyComponent";function App() { return ( <div> <MyComponent /> <MyOtherComponent /> </div> );} **Default import:** To import a default export, you must specify the name of the component to import. // App.tsimport MyComponent from "./MyComponent";function App() { return ( <div> <MyComponent /> </div> );} ### Importing Solid and its utilities To use Solid, you must import the Solid library. The reactive primitives and utilities are exported from Solid's main module. import { createSignal } from "solid-js"; However, some of Solid's utilities are exported from their own modules. import { createStore } from "solid-js/store"; To see a full list of Solid's utilities, the Reference Tab in the sidebar provides the API Documentation. --- ## Page: https://docs.solidjs.com/concepts/components/class-style Similar to HTML, Solid uses `class` and `style` attributes to style elements via CSS (Cascading Style Sheets). * **Class attribute**: Enables styling one or more elements through CSS rules. * **Style attribute**: Inline styles that style single elements. * * * The `style` attribute allows you to style a single element and define CSS variables dynamically during runtime. To use it, you can pass either a string or an object. // String<div style="color: red;">This is a red div</div>// Object<div style={{ color: "red" }}>This is a red div</div> When using an object, the keys represent the CSS property names, and the values represent the CSS property values. The keys must be in dash-case, and the values must be strings. While inline styles are useful for rapid prototyping, they are not recommended for production use. This is because they are not reusable, and they can be difficult to maintain over time. * * * The `class` attribute allows you to style one or more elements through CSS rules. This provides a more structured approach to styling, as it allows you to reuse styles across multiple elements. Classes are defined in CSS files, which are then imported into the component files that use them. You can import these files using the `import` statement at the top of your component file. Once imported into a component, the classes are scoped to that component and any of its children. import "./Card.css";function Card() { // ...} ### Dynamic styling Dynamic styling provides a way to change the appearance of a component based on state or other factors like user inputs. This is useful for creating components that can adapt to different scenarios without having to create multiple versions of the same component: const [theme, setTheme] = createSignal("light");<div class={theme() === "light" ? "light-theme" : "dark-theme"}> This div's theme is determined dynamically!</div>; Props are another way to change styles. By passing props to components, you can adapt styles based on the component's usage or the data it receives: function ThemedButton(props) { return ( <button class={props.theme}> {props.theme === "light" ? "Light Button" : "Dark Button"} </button> );} ### `classList` When you want to apply multiple classes to an element, you can use the `classList` attribute. To use it, you can pass either a string or an object where the keys represent the class names and the values represent a boolean expression. When the value is `true`, the class is applied; when `false`, it is removed. const [current, setCurrent] = createSignal("foo");<button classList={{ "selected" : current() === "foo" }} onClick={() => setCurrent("foo")}> foo</button>; `classList` is often more efficient than `class` when handling multiple conditional classes. This is because `classList` selectively toggles only the classes that require alteration, while `class` will be re-evaluated each time. For a single conditional class, using `class` might be simpler but as the number of conditional classes increases, `classList` offers a more readable and declarative approach. For a guide on how to style your components, see Styling Your Components, where we cover the different ways to style your components using libraries such as Tailwind CSS. --- ## Page: https://docs.solidjs.com/concepts/components/event-handlers Event handlers are functions that are called in response to specific events occurring in the browser, such as when a user clicks or taps on an element. Solid provides two ways to add event listeners to the browser: * `on:__`: adds an event listener to the `element`. This is also known as a _native event_. * `on__`: adds an event listener to the `document` and dispatches it to the `element`. This can be referred to as a _delegated event_. Delegated events flow through the _component tree_, and save some resources by performing better on commonly used events. Native events, however, flow through the _DOM tree_, and provide more control over the behavior of the event. * * * To add an event handler, prefix the event name with either `on` or `on:`, and assign it to the function you wish to call when the event is dispatched. // delegated event<button onClick={handleClick}>Click me</button>// native event<div on:scroll={handleScroll}>... very long text ...</div> Delegated events are **not case sensitive**, therefore using delegated event handlers in Solid can be written using camelCase or all lowercase. Note that while delegated events can be written both ways, native events _are_ case sensitive. <button onclick={handleClick}>Click me</button> For any other events, such as custom events or events you wish _not_ to be delegated, the `on:` attribute will add an event listener as-is. This is what makes the event listener case sensitive. <button on:Custom-Event={handleClick}>Click me</button> For typing standard or custom events using `on:`, the TypeScript page has a section about event handlers. * * * To optimize event handlers, you can pass an array as the event handler, replacing the function. When doing this, the second item passed into the array is supplied as the handler's first argument: const handler = (data, event) => { console.log("Data:", data, "Event:", event);};<button onClick={[handler, "Hello!"]}>Click Me</button>; In this example, the `Hello!` string is passed as the `data` parameter in the `handler` function when the button is clicked. By binding events in this way, Solid avoids the overhead of using JavaScript's bind method and adding an additional closure. ### Dynamic handlers An event handler does not form part of the reactive system. If you were to pass the handler as a signal, it will not respond to the changes of that signal. In other words, events do not dynamically update, and the bindings are not reactive. This is because attaching and detaching listeners is a resource-intensive task. Since event handlers are called like a standard function, you can design them to call a reactive source, if needed. In the following example, `handleClick` represents a prop that has the flexibility to adopt any function. As a result, there is no requirement for these functions to be reactive. <div onClick={() => props.handleClick?.()} /> * * * Instead of attaching event listeners to every individual element, Solid uses _synthetic event delegation_, through the `on__` form . In this method, event listeners are attached to the `document` element and dispatch events to the relevant elements as they bubble up. By keeping the number of event listeners to a minimum, events can be captured more effectively. This is especially useful when working with a large number of elements, such as in a table or list. Supported events such as `click`, `input` and `keydown` are just a few examples that are optimized in this way. To view the full list see the references below. If you need to attach an event listener to an element that is not supported by Solid's event delegation, such as a custom event in a custom element, you can use the `on:__` form. <div on:customEvent={handleCustomEvent} /> ### Event delegation considerations While delegated events provide some performance enhancements, there are tradeoffs. Event delegation is designed for event propagation through the JSX Tree, rather than the DOM Tree. This can differ from the previous expectations of how events work and flow. Some things to keep in mind include: * Delegated event listeners are added _once_ per event type and handle all future events of that type. This means that delegated event listeners remain active even if the element that added them and its handler is removed. For example, if a `div` listens for `mousemove` and is later removed, the `mousemove` events will still be dispatched to the `document` in case a different element is also listening for mouse moves. <div onMouseMove={handleCustomEvent} /> * `event.stopPropagation()` does not work as expected since events are attached to the `document` rather than the `element`. With cases like this, a native event is recommended. As an example, using a native event would stop the following event from reaching the `div native` handler, which is _not_ the case for delegated events. You can view this example in the Solid Playground. onMount(() => { ref.addEventListener("click", () => { console.log("div native"); });});<div ref={ref}> <button onClick={(event) => { event.stopPropagation(); console.log("button"); }} > button </button></div>; // Button clickeddiv nativebutton You can solve this by switching the `button` event to using a native event: // ...<button on:click={(event) => { event.stopPropagation(); console.log("button"); }}> button</button>// ... // Button clickedbutton See how this solution differs in the Solid Playground. * Portals propagate events following the _component tree_ and not the _DOM tree_, making them easier to use. This means when a `Portal` gets attached to the `body`, any events will propagate up to the `container`. <div class="container" onInput={() => console.log("portal key press")}> <Portal mount={document.body}> <input onInput={() => console.log("input key press")} /> </Portal></div> ### List of delegated events You can also view this list in our source code (see `DelegatedEvents`). * `beforeinput` * `click` * `dblclick` * `contextmenu` * `focusin` * `focusout` * `input` * `keydown` * `keyup` * `mousedown` * `mousemove` * `mouseout` * `mouseover` * `mouseup` * `pointerdown` * `pointermove` * `pointerout` * `pointerover` * `pointerup` * `touchend` * `touchmove` * `touchstart` --- ## Page: https://docs.solidjs.com/concepts/components/props Props are a way to pass state from a parent component to a child component. These read-only properties are passed to components as attributes within JSX and are accessible within the component via the `props` object: function App() { // Passing a prop named "name" to the MyComponent component return ( <div> <MyComponent name="Ryan Carniato" /> </div> );} To access the props in the child component, you use the `props` object: function MyComponent(props) { return <div>Hello {props.name}</div>;} * * * `mergeProps` is a Solid utility function designed to merge multiple potentially reactive objects together. It behaves similar to `Object.assign` but will retain the reactivity of the properties being merged. This helps ensure that when individual properties within the merged object change, their reactivity is not lost. import { mergeProps } from "solid-js";function MyComponent(props) { // Using mergeProps to set default values for props const finalProps = mergeProps({ defaultName: "Ryan Carniato" }, props); return <div>Hello {finalProps.defaultName}</div>;}// Usage: <MyComponent defaultName="Ryan Carniato" /> When merging props, if there is no existing value for a property, the value from the first object will be used. However, if a value already exists, it will be used instead, all while retaining the reactivity of the property. * * * Props are read-only so that child components do not directly modify the data passed by the parent. This also encourages one-way data flow, a pattern often seen to promote more predictable data management. With Solid, destructuring props is not recommended as it can break reactivity. Instead, you should access props directly from the `props` object, or wrap them in a function to ensure they are always up-to-date: function MyComponent(props) { const { name } = props; // ❌: breaks reactivity and will not update when the prop value changes const name = props.name; // ❌: another example of breaking reactivity const name = () => props.name; // ✓: by wrapping `props.name` into a function, `name()` always retrieves its current value} ### `splitProps` `splitProps` is a utility function designed to help split a single props object into multiple sets of props, retaining the reactivity of the individual properties. It provides a way to destructure props without breaking reactivity. `splitProps` gives you the ability to define one or more arrays of keys that you wish to extract into separate props objects, all while retaining the reactivity of the individual properties. It will return an array of props objects related to each set of keys, plus an additional props object containing any remaining keys. When passing props to child components, you can use `splitProps` to split the props into multiple groups, and then pass each group to the appropriate child component: import { splitProps } from "solid-js";function ParentComponent(props) { // Splitting props into two groups: 'name' and 'age' const [greetingProps, personalInfoProps, restProps] = splitProps( props, ["name"], ["age"] ); // Using greetingProps and personalInfoProps in the current component return ( <div> <Greeting {...greetingProps} /> <PersonalInfo {...personalInfoProps} /> {/* restProps can be passed down or used as needed */} </div> );} * * * In most instances, simply using `props` within JSX will work without any issues. However, there are some cases where accessing `props.children` multiple times can introduce problems and unexpected behaviours, such as repeated creation of child components or elements. For instances like these, Solid provides a `children` helper that ensures you always get the right child components without anything unwanted happening. import { children } from "solid-js";function ColoredList(props) { const safeChildren = children(() => props.children); return <>{safeChildren()}</>;} * * * Prop drilling is a term used to describe the process of passing props through multiple layers of components. While it can be a useful pattern, it can also lead to problems. When components are nested deeply, passing props through each component can become difficult to manage. Additionally, this can lead to components receiving props that they do not need, unnecessary re-renders, and trouble refactoring. Since components in Solid do not own state, props are not needed to pass state between components, but may be used. Because of this, there may be times when you need to pass props through multiple layers of components. A common solution to this problem is to use Context to pass state to deeply nested components without having to pass props through each component in between. --- ## Page: https://docs.solidjs.com/concepts/signals Signals are the primary means of managing state in your Solid application. They provide a way to store and update values, and are the foundation of reactivity in Solid. Signals can be used to represent any kind of state in your application, such as the current user, the current page, or the current theme. This can be any value, including primitive values such as strings and numbers, or complex values such as objects and arrays. * * * You can create a signal by calling the `createSignal` function, which is imported from `solid-js`. This function takes an initial value as an argument, and returns a pair of functions: a **getter** function, and a **setter** function. import { createSignal } from "solid-js";const [count, setCount] = createSignal(0);// ^ getter ^ setter * * * The getter function returned by `createSignal` is used to access the value of the signal. You call this function with no arguments to get the current value of the signal: console.log(count()); // output: 0 * * * The setter function returned by `createSignal` is used to update the value of the signal. This function takes an argument that represents the new value of the signal: setCount(count() + 1);console.log(count()); // output: 1 The setter function can also take a function that passes the previous value. setCount((prevCount) => prevCount + 1);console.log(count()); // output: 1 * * * Signals are reactive, which means that they automatically update when their value changes. When a signal is called within a tracking scope, the signal adds the dependency to a list of subscribers. Once a signal's value changes, it notifies all of its dependencies of the change so they can re-evaluate their values and update accordingly. function Counter() { const [count, setCount] = createSignal(0); const increment = () => setCount((prev) => prev + 1); return ( <div> <span>Count: {count()}</span> {/* Updates when `count` changes */} <button type="button" onClick={increment}> Increment </button> </div> );} To learn more about how to use Signals in your application, visit our state management guide. --- ## Page: https://docs.solidjs.com/concepts/control-flow/conditional-rendering Conditional rendering is the process of displaying different UI elements based on certain conditions. This is a common pattern in UI development, and is often used to show or hide elements based on user input, data, or other conditions. Solid offers dedicated components to handle conditional rendering in a more straightforward and readable way. * * * `<Show>` renders its children when a condition is evaluated to be true. Similar to the ternary operator in JavaScript, it uses control logic flow within JSX to determine what to render. `<Show>` has a `when` property that is used to determine whether or not to render its children. When there is a change in the state or props it depends on, this property is re-evaluated. This property can be a boolean value, or a function that returns a boolean value. import { Show } from "solid-js"<Show when={data.loading}> <div>Loading...</div></Show> `<Show>` has the `fallback` property that can be used to specify the content to be rendered when the condition evaluates to false. This property can return a JSX element. import { Show } from "solid-js"<Show when={!data.loading} fallback={<div>Loading...</div>}> <h1>Hi, I am {data().name}.</h1></Show> If there are multiple conditions that need to be handled, `<Show>` can be nested to handle each condition. import { Show } from "solid-js"<Show when={data.loading}> <div>Loading...</div> <Show when={data.error}> <div>Error: {data.error}</div> </Show></Show> * * * When there are multiple conditions that need to be handled, it can be difficult to manage the logic flow with nested `<Show>` components. Solid has the `<Switch>` and `<Match>` components for this purpose. Similar to JavaScript's switch/case structure, `<Switch>` wraps multiple `<Match>` components so that each condition is evaluated _in sequence_. The first `<Match>` component that evaluates to true will have its children rendered, and the rest will be ignored. import { Switch, Match } from "solid-js"<Switch> <Match when={condition1}> <p>Outcome 1</p> </Match> <Match when={condition2}> <p>Outcome 2</p> </Match></Switch> Similar to `<Show>`, each `<Match>` component has a `when` property that is used to determine whether or not to render its children. An optional `fallback` property can also be passed to `<Switch>` to specify the content be rendered when none of the `<Match>` components evaluate to true. import { Switch, Match } from "solid-js"<Switch fallback={<p>Fallback content</p>}> <Match when={condition1}> <p>Outcome 1</p> </Match> <Match when={condition2}> <p>Outcome 2</p> </Match></Switch> --- ## Page: https://docs.solidjs.com/concepts/control-flow/dynamic `<Dynamic>` is a Solid component that allows you to render components dynamically based on data. By passing either a string representing a native HTML element or a component function to the `component` prop, you can render the chosen component with the remaining props you provide. import { createSignal, For } from "solid-js"import { Dynamic } from "solid-js/web"const RedDiv = () => <div style="color: red">Red</div>const GreenDiv = () => <div style="color: green">Green</div>const BlueDiv = () => <div style="color: blue">Blue</div>const options = { red: RedDiv, green: GreenDiv, blue: BlueDiv,}function App() { const [selected, setSelected] = createSignal("red") return ( <> <select value={selected()} onInput={(e) => setSelected(e.currentTarget.value)} > <For each={Object.keys(options)}> {(color) => <option value={color}>{color}</option>} </For> </select> <Dynamic component={options[selected()]} /> </> )} This example renders a `<select>` element that allows you to choose between three colors. Once a color is selected, the `<Dynamic>` component will render the chosen color's corresponding component or element. `<Dynamic>` creates more concise code than alternative conditional rendering options. For example, the following code renders the same result as the previous example: import { createSignal, Switch, Match, For } from "solid-js"const RedDiv = () => <div style="color: red">Red</div>const GreenDiv = () => <div style="color: green">Green</div>const BlueDiv = () => <div style="color: blue">Blue</div>const options = { red: RedDiv, green: GreenDiv, blue: BlueDiv,}function App() { const [selected, setSelected] = createSignal("red") return ( <> <select value={selected()} onInput={(e) => setSelected(e.currentTarget.value)} > <For each={Object.keys(options)}> {(color) => <option value={color}>{color}</option>} </For> </select> <Switch fallback={<BlueDiv />}> <Match when={selected() === "red"}> <RedDiv /> </Match> <Match when={selected() === "green"}> <GreenDiv /> </Match> </Switch> </> )} Instead of a more verbose `<Switch>` and `<Match>` statement, `<Dynamic>` offers a more concise way to render components dynamically. * * * When working with these components, you can pass props to the component you are rendering by passing them to the `<Dynamic>` component. This makes them available to the component you are rendering, similar to how you would pass props to components in JSX. import { Dynamic } from "solid-js/web"function App() { return ( <Dynamic component={someComponent} someProp="someValue" /> )} --- ## Page: https://docs.solidjs.com/concepts/control-flow/list-rendering List rendering allows you to generate multiple elements from a collection of data, such as an array or object, where each element corresponds to an item in the collection. When dealing with dynamic data, Solid offers two ways to render lists: the `<For>` and `<Index>` components. Both of these components help you loop over data collections to generate elements, however, they both cater to different scenarios. * * * `<For>` is a looping component that allows you to render elements based on the contents of an array or object. This component is designed to be used with **complex data structures**, such as arrays of objects, where the order and length of the list may change frequently. The sole property in `<For>` is `each` , through which you can specify the data collection to loop over. This property expects an array, however, it can also accept objects that have been converted to arrays using utilities such as `Object.entries` or `Object.values`. import { For } from "solid-js"<For each={data()}> {(item, index) => // rendering logic for each element }</For> Between the `<For>` tags, the component requires a callback function which will dictate how each item in the data collection should be rendered. This structure resembles the callback used within JavaScript's `map` method, providing a familiar pattern to follow. The function receives two arguments: * `item`: represents the current item in the data collection that is being rendered over. * `index`: the current item's index within the collection. You can access the current `item` and `index` to dynamically set attributes or content of the JSX elements. Index is a _signal_ and must be called as a function to retrieve its value. <For each={data()}> {(item, index) => ( <li style={{ color: index() % 2 === 0 ? "red" : "blue" }} > {item.name} </li> )}</For> * * * `<Index>`, similar to `<For>`, is a looping component that allows you to render elements based on the contents of an array or object. However, when the order and length of the list remain _stable_, but the content may change frequently, `<Index>` is a better option because it results in fewer re-renders. import { Index } from "solid-js"<Index each={data()}> {(item, index) => ( // rendering logic for each element )}</Index> Similar to the `<For>` component, `<Index>` accepts a single property named `each`, which is where you pass the structure you wish to loop over. Where the `index` is a signal with `<For>`, it remains fixed with `<Index>`. This is because `<Index>` is more concerned with the **index** of the elements in the array. Because of this, the `item` is a signal, allowing the _content_ at each index to change without a re-render while the index remains fixed. import { Index } from "solid-js"<Index each={data()}> {(item, index) => ( <li> {item().name} - {item().completed} </li> )}</Index> * * * `<For>` is designed to be used when the _order_ and _length_ of the list may change frequently. When the list value changes in `<For>`, the entire list is re-rendered. However, if the array undergoes a change, such as an element shifting position, `<For>` will manage this by simply **moving** the corresponding DOM node and **updating** the index. `<Index>`, however, is designed to be used when the **order** and **length** of the list remain _stable_, but the content may change frequently. When the list value changes in `<Index>`, only the content at the specified index is updated. ### When to use `<For>` In cases where signals, nested loops, or dynamic lists are not required, `<For>` is the best option. For example, when creating a list of static elements, such as a list of links, `<For>` is the best option to use. This is because it will only modify the indexes of the elements in the list, rather than re-rendering the entire list. import { createSignal, For } from "solid-js"function StringList() { const [items, setItems] = createSignal(["Item 1", "Item 2", "Item 3"]) return ( <ul> <input type="text" onInput={(e) => { // add the new item to the list }} /> <For each={items()}> {(item, index) => ( <li> {item} - {index()} </li> )} </For> </ul> )} If you are working with signals, JavaScript primitives like strings and numbers or input fields, `<Index>` is the better option to use. If you were using `<For>`, the entire list would be re-rendered when a value changes, even if the length of the list remains unchanged. `<Index>`, instead, will update the content at the specified index, while the rest of the list remains unchanged. import { createSignal, Index } from "solid-js"function FormList() { const [inputs, setInputs] = createSignal(['input1','input2','input3']) return( <form> <Index each={inputs()}> {(input, index) => ( <input type="text" value={input()} onInput={(e) => { // update the input value }} /> )} </Index> </form> )} --- ## Page: https://docs.solidjs.com/concepts/control-flow/portal When an element requires rendering outside of the usual document flow, challenges related to stacking contents and z-index can interfere with the desired intention or look of an application. `<Portal>` helps with this by putting elements in a different place in the document, bringing an element into the document flow so they can render as expected. import { Portal } from "solid-js/web"<Portal> <div class="popup">...</div></Portal> The content nested within `<Portal>` is rendered and positioned by default at the end of the document body. This can be changed by passing a `mount` prop to `<Portal>`. The `mount` prop accepts a DOM node, which will be used as the mount point for the portal content. import { Portal } from "solid-js/web"<Portal mount={document.querySelector("main")}> <div class="popup">...</div></Portal> Using `<Portal>` can be particularly useful in cases where elements, like information popups, might be clipped or obscured due to the overflow settings of their parent elements. By putting the element outside of the parent element, it is no longer bound by the overflow settings of its parent. This creates a more accessible experience for users, as the content is no longer obscured. info `<Portal>` will render wrapped unless specifically targeting `document.head`. This is so events propagate through the Portal according to the component hierarchy instead of the elements hierarchy. By default, children will wrap in a `<div>`. If you portal into an SVG, then the `isSVG` prop must be used to avoid wrapping the children in a `<div>` and wrap in a `<g>` instead. import { Portal } from "solid-js/web"function Rect() { return ( <Portal mount={document.querySelector("svg")} isSVG={true}> <rect fill="red" x="25" y="25" height="50" width="50" /> </Portal> );}function SVG() { return <svg xmlns="http://www.w3.org/2000/svg" />;} --- ## Page: https://docs.solidjs.com/concepts/control-flow/error-boundary By default, if part of an application throws an error during rendering, the entire application can crash, resulting in Solid removing its UI from the screen. Error boundaries provide a way to catch these errors and prevent the entire app from crashing. The `<ErrorBoundary>` component is used to create an error boundary. It catches any error that occurs during the rendering or updating of its children. However, an important note is that errors occurring outside the rendering process, such as in event handlers or after a `setTimeout`, are _not_ caught by error boundaries. The `fallback` prop can be used to display a user-friendly error message or notification when an error occurs. If a function is passed to `fallback`, it will receive the error object as well as a `reset` function. The `reset` function forces the `<ErrorBoundary>` to re-render its children and reset the error state, providing users with a way to recover from the error. import { ErrorBoundary } from "solid-js";import { Header, ErrorProne } from "./components";function App() { return ( <div> <Header /> <ErrorBoundary fallback={(error, reset) => ( <div> <p>Something went wrong: {error.message}</p> <button onClick={reset}>Try Again</button> </div> )} > <ErrorProne /> </ErrorBoundary> </div> );} In this example, when the `ErrorProne` component throws an error, the `<ErrorBoundary>` catches it, preventing it from affecting the rest of the application. Instead, it displays the error message passed to the fallback prop. --- ## Page: https://docs.solidjs.com/concepts/effects Effects are functions that are triggered when the signals they depend on change. They play a crucial role in managing side effects, which are actions that occur outside of the application's scope, such as DOM manipulations, data fetching, and subscriptions. * * * An effect is created using the `createEffect` function. This function takes a callback as its argument that runs when the effect is triggered. import { createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => { console.log(count());}); In this example, an effect is created that logs the current value of `count` to the console. When the value of `count` changes, the effect is triggered, causing it to run again and log the new value of `count`. * * * Effects can be set to observe any number of dependencies. Dependencies are what allow an effect to track changes and respond accordingly. These can include signals, variables, props, context, or any other reactive values. When any of these change, the effect is notified and will run again to update its state. Upon initialization, an effect will run _once_, regardless of whether it has any dependencies. This is useful for setting up the effect and initializing variables or subscribing to signals. After this run, the effect will only be triggered when any of its _dependencies_ change. createEffect(() => { console.log("hello"); // will run only once});createEffect(() => { console.log(count()); // will run every time count changes}); Solid automatically tracks the dependencies of an effect, so you do not need to manually specify them. This improves the tracking and minimizes the chances of overlooking or incorrectly identifying dependencies. * * * When an effect is set to observe a signal, it creates a subscription to it. This subscription allows the effect to track the changes in the signal's value, which causes it to observe any changes that may happen and to execute its callback accordingly. import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => { console.log(count()); // Logs current value of count whenever it changes}); ### Managing multiple signals Effects have the ability to observe multiple signals. A single effect can subscribe to multiple signals, and similarly, multiple effects can keep track of a single signal. This is useful when you need to update the UI based on multiple signals. When multiple signals are observed within a single effect, it will execute its callback whenever _any_ of the signals change. The effect will run even if only one of the signals changes, not necessarily all of them. This means that the effect will run with the latest values of all of the signals that it is observing. import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);const [message, setMessage] = createSignal("Hello");createEffect(() => { console.log(count(), message());});setCount(1); // Output: 1, "Hello"setMessage("World"); // Output: 1, "World" ### Nested effects When working with effects, it is possible to nest them within each other. This allows each effect to independently track its own dependencies, without affecting the effect that it is nested within. createEffect(() => { console.log("Outer effect starts"); createEffect(() => console.log("Inner effect")); console.log("Outer effect ends");}); The order of execution is important to note. An inner effect will _not_ affect the outer effect. Signals that are accessed within an inner effect, will _not_ be registered as dependencies for the outer effect. When the signal located within the inner effect changes, it will trigger only the _inner effect_ to re-run, not the outer one. import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => { console.log("Outer effect starts"); createEffect(() => console.log(count())); // when count changes, only this effect will run console.log("Outer effect ends");}); This forces each effect to be independent of each other, which helps to avoid unexpected behaviour. Additionally, it allows you to create effects that are only triggered when certain conditions are met. * * * Effects have a lifecycle that can be managed using certain functions. These functions allow you to control the initialization and disposal of effects to build the type of behaviour that you need. This can include running a side effect only once, or cleaning up a task when it is no longer needed. ### `onMount` In situations where you just want to run a side effect **once**, you can use the `onMount` function. This lifecycle function is similar to an effect, but it does not track any dependencies. Rather, once the component has been initialized, the `onMount` callback will be executed and will not run again. import { onMount } from "solid-js";function Component() { const [data, setData] = createSignal(null); createEffect(() => { data(); // will run every time data changes }); onMount(async () => { // will run only once, when the component is mounted const fetchedData = await fetch("https://example.com/data"); setData(fetchedData); }); return <div>...</div>;} `onMount` provides the assurance that the callback will only run once. If using an effect in this situation, there is no guarantee that it will only run once, which can lead to unexpected behaviour. This makes `onMount` useful for API calls and other side effects that only need to be run once per component instance. ### `onCleanup` While `onMount` is useful for running a side effect once, `onCleanup` is helpful for cleaning up a task when it is no longer needed. `onCleanup` will run whenever the component unmounts, removing any subscriptions that the effect has. import { onCleanup } from "solid-js";function App() { const [count, setCount] = createSignal(0); const timer = setInterval(() => { setCount((prev) => prev + 1); }, 1000); onCleanup(() => { clearInterval(timer); }); return <div>Count: {count()}</div>;} In this example, the `onCleanup` function is used to clear the interval that is set up in the effect. To avoid the interval from running indefinitely, the `onCleanup` function is used to clear the interval once the component unmounts. `onCleanup` can be used to avoid memory leaks. These occur when a component is unmounted, but references to it still exist and, as a result, could still be running in the background. Using `onCleanup` to remove any subscriptions or references to the component can help to avoid this issue. --- ## Page: https://docs.solidjs.com/concepts/derived-values/derived-signals Derived signals are functions that rely on one or more signals to produce a value. These functions are not executed immediately, but instead are only called when the values they rely on are changed. When the underlying signal is changed, the function will be called again to produce a new value. const double = () => count() * 2; In the above example, the `double` function relies on the `count` signal to produce a value. When the `count` signal is changed, the `double` function will be called again to produce a new value. Similarly you can create a derived signal that relies on a store value because stores use signals under the hood. To learn more about how stores work, you can visit the stores section. const fullName = () => store.firstName + ' ' + store.lastName; These dependent functions gain reactivity from the signal they access, ensuring that changes in the underlying data propagate throughout your application. It is important to note that these functions do not store a value themselves; instead, they can update any effects or components that depend on them. If included within a component's body, these derived signals will trigger an update when necessary. While you can create derived values in this manner, Solid created the `createMemo` primitive. To dive deeper into how memos work, check out the memos section. --- ## Page: https://docs.solidjs.com/concepts/derived-values/memos Memos are a type of reactive value that can be used to memoize derived state or expensive computations. They are similar to derived signals in that they are reactive values that automatically re-evaluate when their dependencies change. However, unlike derived signals, memos are optimized to execute only once for each change in their dependencies. Memos expose a _read-only_ reactive value (like a signal) and track changes in their dependencies (similar to an effect). This makes them useful for caching the results of expensive or frequently accessed computations. By doing this, memos minimize unnecessary work within an application by retaining the results of a computation until its dependencies change. * * * A memo is created using the `createMemo` function. Within this function, you can define the derived value or computations you wish to memoize. When called, `createMemo` will return a **getter** function that reads the current value of the memo: import { createMemo, createSignal } from "solid-js"const [count, setCount] = createSignal(0)const isEven = createMemo(() => count() % 2 === 0)console.log(isEven()) // truesetCount(3)console.log(isEven()) // false While memos look similar to effects, they are different in that they _return a value_. This value is the result of the computation or derived state that you wish to memoize. ### Advantages of using memos While you can use a derived signal to achieve similar results, memos offer distinct advantages: * Memos are optimized to execute only once for each change in their dependencies. * When working with expensive computations, memos can be used to cache the results so they are not recomputed unnecessarily. * A memo will only recompute when its dependencies change, and will not trigger subsequent updates (as determined by `===` or strict equality) if its dependencies change but its value remains the same. * Any signal or memo accessed within a memo's function is **tracked**. This means that the memo will re-evaluate automatically when these dependencies change. * * * Both memos and effects are important when managing reactive computations and side effects. They, however, serve different purposes and each has their own unique behaviors. | | Memos | Effects | | --- | --- | --- | | Return value | Yes - returns a getter for the result of the computation or derived state. | Does not return a value but executes a block of code in response to changes. | | Caches results | Yes | No | | Behavior | Function argument should be pure without reactive side effects. | Function argument can cause side effects like UI updates or data fetches. | | Dependency tracking. | Yes | Yes | | Example use cases | Transforming data structures, computing aggregated values, derived state, or other expensive computations. | UI updates, network requests, or external integrations. | * * * ### Pure functions When working with memos, it is recommended that you leave them "pure". import { createSignal, createMemo } from "solid-js"const [count, setCount] = createSignal(0)const isEven = createMemo(() => count() % 2 === 0) // example of a pure function A pure function is one that does not cause any side effects. This means that the function's output should solely depend on its inputs. When you introduce side effects into a memo, it can complicate the reactivity chain. This can lead to unexpected behavior, such as infinite loops, that lead your application to crash. import { createSignal, createMemo } from "solid-js"const [count, setCount] = createSignal(0)const [message, setMessage] = createSignal("")const badMemo = createMemo(() => { if (count() > 10) { setMessage("Count is too high!") // side effect } return count() % 2 === 0}) These infinite loops can be triggered when a memo has a side effect that causes its dependencies to change. This will cause the memo to re-evaluate, which will then trigger the side effect again, and so on until the application crashes. This can be avoided by using a `createEffect` to handle the side effects instead: import { createSignal, createMemo, createEffect } from "solid-js"const [count, setCount] = createSignal(0)const [message, setMessage] = createSignal("")const isEven = createMemo(() => count() % 2 === 0)createEffect(() => { if (count() > 10) { setMessage("Count is too high!") }}) Here, the `createEffect` will handle the side effects, while the `isEven` memo will remain pure. ### Keep logic in memos Memos are optimized to execute only once for each change in their dependencies. This means that you can remove unnecessary effects that are triggered by a memo's dependencies. When working with derived state, memos are the recommended approach over effects. Keeping the logic in a memo prevents unnecessary re-renders that can occur when using an effect. Similarly, effects are better suited to handle side effects, such as DOM updates, rather than derived state. This separation of concerns can help keep your code clean and easy to understand. // effect - runs whenever `count` changescreateEffect(() => { if (count() > 10) { setMessage("Count is too high!") } else { setMessage("") }})// memo - only runs when `count` changes to or from a value greater than 10const message = createMemo(() => { if (count() > 10) { return "Count is too high!" } else { return "" }}) --- ## Page: https://docs.solidjs.com/concepts/context Context provides a way to pass data through the component tree without having to pass props down manually at every level. * * * ## When to use context When you have a large component tree that requires state to be shared, context can be used. Context can be employed to avoid prop drilling, which is the practice of passing props through intermediate elements without using them directly. If you want to avoid passing some props through a few layers, when applicable, adjusting your component hierarchy may be an easier solution. Signals are often the simplest solution since they can be imported directly into the components that need them. Context, however, is designed to share data that is global to an application or for information that is regularly accessed by multiple components in an application's component tree. This offers a way to access state across an application without passing props through intermediate layers or importing them directly into components. * * * ## Creating context Context is created using the `createContext` function. This function has a `Provider` property that wraps the component tree you want to provide context to. import { createContext } from "solid-js";const MyContext = createContext(); import { MyContext } from "./create";export function Provider (props) { return ( <MyContext.Provider> {props.children} </MyContext.Provider> )}; * * * ## Providing context to children To pass a value to the `Provider`, you use the `value` prop which can take in any value, including signals. Once a value is passed to the `Provider`, it is available to all components that are descendants of the `Provider`. When passing a single value, it can be directly passed to the `value` prop: import { createContext, useContext } from "solid-js";import { MyContext } from "./create";const Provider = (props) => ( <MyContext.Provider value="new value">{props.children}</MyContext.Provider>); * * * ## Consuming context Once the values are available to all the components in the context's component tree, they can be accessed using the `useContext` utility. This utility takes in the context object and returns the value(s) passed to the `Provider`: import { createContext, useContext } from "solid-js";import { MyContext } from "./create";const Provider = (props) => ( <MyContext.Provider value="new value"> {props.children} </MyContext.Provider>);const Child = () => { const value = useContext(MyContext); return ( <span> {value} </span> );};export const App = () => ( <Provider> <Child /> </Provider>); * * * ## Customizing Context Utilities When an application contains multiple context objects, it can be difficult to keep track of which context object is being used. To solve this issue, you can create a custom utility to create a more readable way to access the context values. For example, when wrapping a component tree, you may want to create a custom `Provider` component that can be used to wrap the component tree. This also provides you with the option of re-using the `Provider` component in other parts of your application, if needed. import { createSignal, createContext, useContext } from "solid-js";import { CounterContext } from "~/context/counter";export function CounterProvider(props) { return ( <CounterContext.Provider value={props.count ?? 0}> {props.children} </CounterContext.Provider> );} Now if you had to access the Provider in different areas of your application, you can simply import the `CounterProvider` component and wrap the component tree: import { CounterProvider } from "./counterProvider";export function App() { return ( <CounterProvider count={1}> <h1>Welcome to Counter</h1> <NestedComponents /> </CounterProvider> );} Similarly, you can create a custom utility to access the context values. Instead of importing `useContext` and passing in the context object on each component that you're using it in, creating a customized utility can make it easier to access the values you need: export function useCounter() { return useContext(CounterContext);} The `useCounter()` utility in this example can now be imported into any component that needs to access the context values: import { useCounter } from "./counter";export function CounterProvider(props) { const count = useCounter(); return ( <> <div>{count()}</div> </> );} * * * ## Updating Context Values Signals offer a way to synchronize and manage data shared across your components using context. You can pass a signal directly to the `value` prop of the `Provider` component, and any changes to the signal will be reflected in all components that consume the context. import { CounterProvider } from "./Context";import { Child } from "./Child";export function App() { return ( <CounterProvider count={1}> <h1>Welcome to Counter App</h1> <Child /> </CounterProvider> )} import { createSignal, useContext } from "solid-js";export function CounterProvider(props) { const [count, setCount] = createSignal(props.initialCount || 0); const counter = [ count, { increment() { setCount(prev => prev + 1); }, decrement() { setCount(prev => prev - 1); } } ]; return ( <CounterContext.Provider value={counter}> {props.children} </CounterContext.Provider> );}export function useCounter() { return useContext(CounterContext); } // /context/counter-component.tsximport { useCounter } from "./Context";export function Child(props) { const [count, { increment, decrement }] = useCounter(); return ( <> <div>{count()}</div> <button onClick={increment}>+</button> <button onClick={decrement}>-</button> </> );}; This offers a way to manage state across your components without having to pass props through intermediate elements. * * * ## Debugging with context `createContext` takes in an _optional_ default value and it is possible it can return `undefined` if not provided. When working with TypeScript, this can introduce type issues that make it difficult to determine why your component is not rendering as expected. To solve this issue, a default value can be specified when creating a context object, or errors can be handled manually through the use of a custom `useMyContext` utility: import { useContext } from "solid-js";function useMyContext() { const value = useContext(MyContext); if (!value) { throw new Error("Missing context Provider"); } return value;}function Child() { const value = useMyContext(); return <div>{value}</div>;} * * * ## Common issues with `createContext` and `useContext` If no default value is passed to `createContext`, it is possible for `useContext` to return `undefined`. Because of this, if an initial value was not passed to `createContext`, the TS type signature of `useContext` will indicate that the value returned might be `undefined` (as mentioned above). This can be quite annoying when you want to use the context inside a component, and particularly when immediately destructuring the context. Additionally, if you use `useContext` and it returns `undefined` (which is often, but not always, the result of a bug), the error message thrown at runtime can be confusing. The most common solution for it is to wrap all uses of `useContext` in a function that will explicitly throw a helpful error if the context is `undefined`. This also serves to narrow the type returned, so TS doesn't complain. As an example: function useCounterContext() { const context = useContext(CounterContext) if (!context) { throw new Error("can't find CounterContext") } return context} --- ## Page: https://docs.solidjs.com/concepts/stores Similar to signals, stores are a state management primitive. However, while signals manage a single piece of state, stores create a centralized location to reduce code redundancy. Within Solid, these stores can spawn a collection of reactive signals, each corresponding to a particular property which can be useful when working with complex state. * * * Stores can manage many data types, including: objects, arrays, strings, and numbers. Using JavaScript's proxy mechanism, reactivity extends beyond just the top-level objects or arrays. With stores, you can now target nested properties and elements within these structures to create a dynamic tree of reactive data. import { createStore } from "solid-js/store"// Initialize storeconst [store, setStore] = createStore({ userCount: 3, users: [ { id: 0, username: "felix909", location: "England", loggedIn: false, }, { id: 1, username: "tracy634", location: "Canada", loggedIn: true, }, { id: 2, username: "johny123", location: "India", loggedIn: true, }, ],}) * * * Store properties can be accessed directly from the state proxy through directly referencing the targeted property: console.log(store.userCount) // Outputs: 3 Accessing stores within a tracking scope follows a similar pattern to signals. While signals are created using the `createSignal` function and require calling the signal function to access their values, store values can be directly accessed without a function call. This provides access to the store's value directly within a tracking scope: const App = () => { const [mySignal, setMySignal] = createSignal("This is a signal.") const [store, setStore] = createStore({ userCount: 3, users: [ { id: 0, username: "felix909", location: "England", loggedIn: false, }, { id: 1, username: "tracy634", location: "Canada", loggedIn: true, }, { id: 2, username: "johny123", location: "India", loggedIn: true, }, ], }) return ( <div> <h1>Hello, {store.users[0].username}</h1> {/* Accessing a store value */} <span>{mySignal()}</span> {/* Accessing a signal */} </div> )} When a store is created, it starts with the initial state but does _not_ immediately set up signals to track changes. These signals are created **lazily**, meaning they are only formed when accessed within a reactive context. Once data is used within a reactive context, such as within the return statement of a component function, computed property, or an effect, a signal is created and dependencies are established. For 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. const App = () => { const [store, setStore] = createStore({ userCount: 3, users: [ ... ], }) const addUser = () => { ... } console.log(store.users.at(-1)) // This won't work return ( <div> <h1>Hello, {store.users[0].username}</h1> <p>User count: {store.userCount}</p> <button onClick={addUser}>Add user</button> </div> )} Rather, this would need to be in a tracking scope, like inside a `createEffect`, so that a dependency is established. const App = () => { const [store, setStore] = createStore({ userCount: 3, users: [ ... ], }) const addUser = () => { ... } console.log(store.users.at(-1)) createEffect(() => { console.log(store.users.at(-1)) }) return ( <div> <h1>Hello, {store.users[0].username}</h1> <p>User count: {store.userCount}</p> <button onClick={addUser}>Add user</button> </div> )} * * * Updating values within a store is best accomplished using a setter provided by the `createStore` initialization. This setter allows for the modification of a specific key and its associated value, following the format `setStore(key, newValue)`: const [store, setStore] = createStore({ userCount: 3, users: [ ... ],})setStore("users", (currentUsers) => [ ...currentUsers, { id: 3, username: "michael584", location: "Nigeria", loggedIn: false, },]) The value of `userCount` could also be automatically updated whenever a new user is added to keep it synced with the users array: const App = () => { const [store, setStore] = createStore({ userCount: 3, users: [ ... ], }) const addUser = () => { ... } createEffect(() => { console.log(store.users.at(-1)) setStore("userCount", store.users.length) }) return ( <div> <h1>Hello, {store.users[0].username}</h1> <p>User count: {store.userCount}</p> <button onClick={addUser}>Add user</button> </div> )} * * * Modifying a store using this method is referred to as "path syntax." In 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. String keys are used to precisely target particular values with path syntax. By specifying these exact key names, you can directly retrieve the targeted information. However, path syntax goes beyond string keys and offers more versatility when accessing targeted values. Instead of employing the use of just string keys, there is the option of using an array of keys. This method grants you the ability to select multiple properties within the store, facilitating access to nested structures. Alternatively, you can use filtering functions to access keys based on dynamic conditions or specific rules. The 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. * * * Path syntax provides a convenient way to modify arrays, making it easier to access and update their elements. Instead of relying on discovering individual indices, path syntax introduces several powerful techniques for array manipulation. ### Appending new values To append a new element to an array within a store, you specify the target array and set the index to the desired position. For example, if you wanted to append the new element to the end of the array, you would set the index to `array.length`: setStore("users", (otherUsers) => [ ...otherUsers, { id: 3, username: "michael584", location: "Nigeria", loggedIn: false, },])// can becomesetStore("users", store.users.length, { id: 3, username: "michael584", location: "Nigeria", loggedIn: false,}) ### Modifying multiple elements With path syntax, you can target a subset of elements of an array, or properties of an object, by specifying an array or range of indices. The most general form is to specify an array of values. For example, if `store.users` is an array of objects, you can set the `loggedIn` property of several indices at once like so: setStore("users", [2, 7, 10], "loggedIn", false)// equivalent to (but more efficient than):setStore("users", 2, "loggedIn", false)setStore("users", 7, "loggedIn", false)setStore("users", 10, "loggedIn", false) This array syntax also works for object property names. For example, if `store.users` is an object mapping usernames to objects, you can set the `loggedIn` property of several users at once like so: setStore("users", ["me", "you"], "loggedIn", false)// equivalent to (but more efficient than):setStore("users", ["me"], "loggedIn", false)setStore("users", ["you"], "loggedIn", false) For arrays specifically, you can specify a range of indices via an object with `from` and `to` keys (both of which are inclusive). For example, assuming `store.users` is an array again, you can set the `loggedIn` state for all users except index 0 as follows: setStore("users", { from: 1, to: store.users.length - 1 }, "loggedIn", false)// equivalent to (but more efficient than):for (let i = 1; i <= store.users.length - 1; i++) { setStore("users", i, "loggedIn", false)} You can also include a `by` key in a range object to specify a step size, and thereby update a regular subset of elements. For example, you can set the `loggedIn` state for even-indexed users like so: setStore("users", { from: 0, to: store.users.length - 1, by: 2 }, "loggedIn", false)// equivalent to (but more efficient than):for (let i = 1; i <= store.users.length - 1; i += 2) { setStore("users", i, "loggedIn", false)} Multi-setter syntax differs from the "equivalent" code in one key way: a single store setter call automatically gets wrapped in a `batch`, so all the elements update at once before any downstream effects are triggered. ### Dynamic value assignment Path syntax also provides a way to set values within an array using functions instead of static values. These functions receive the old value as an argument, allowing you to compute the new value based on the existing one. This dynamic approach is particularly useful for complex transformations. setStore("users", 3, "loggedIn" , (loggedIn) => !loggedIn) ### Filtering values In scenarios where you want to update elements in an array based on a specific condition, you can pass a function as an argument. This function will act as a filter, allowing you to select elements that satisfy the condition. It receives the old value and index as arguments, providing the flexibility to make conditional updates. setStore("users", (user) => user.username.startsWith("t"), "loggedIn", false) In addition to `.startsWith`, you can use other array methods like `.find` to filter for the values that you need. * * * When using store setters to modify objects, if a new value is an object, it will be shallow merged with the existing value. What 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. What this means, is that you can directly make the change to the store _without_ spreading out properties of the existing user object. setStore("users", 0, { id: 109,})// is equivalent tosetStore("users", 0, (user) => ({ ...user, id: 109,})) * * * ### Store updates with `produce` Rather than directly modifying a store with setters, Solid has the `produce` utility. This utility provides a way to work with data as if it were a mutable JavaScript object. `produce` also provides a way to make changes to multiple properties at the same time which eliminates the need for multiple setter calls. import { produce } from "solid-js/store"// without producesetStore("users", 0, "username", "newUsername")setStore("users", 0, "location", "newLocation")// with producesetStore( "users", 0, produce((user) => { user.username = "newUsername" user.location = "newLocation" })) `produce` and `setStore` do have distinct functionalities. While both can be used to modify the state, the key distinction lies in how they handle data. `produce` allows you to work with a temporary draft of the state, apply the changes, then produce a new immutable version of the store. Comparatively, `setStore` provides a more straightforward way to update the store directly, without creating a new version. It's important to note, however, `produce` is specifically designed to work with **arrays** and **objects**. Other collection types, such as JavaScript Sets and Maps, are not compatible with this utility. ### Data integration with `reconcile` When new information needs to be merged into an existing store `reconcile` can be useful. `reconcile` will determine the differences between new and existing data and initiate updates only when there are _changed_ values, thereby avoiding unnecessary updates. const { createStore, reconcile } from "solid-js/stores"const [data, setData] = createStore({ animals: ['cat', 'dog', 'bird', 'gorilla']})const newData = getNewData() // eg. contains ['cat', 'dog', 'bird', 'gorilla', 'koala']setData('animals', reconcile(newData)) In this example, the store will look for the differences between the existing and incoming data sets. Consequently, only `'koala'` - the new edition - will cause an update. When there is a need for dealing with data outside of a reactive context, the `unwrap` utility offers a way to transform a store to a standard object. This conversion serves several important purposes. Firstly, it provides a snapshot of the current state without the processing overhead associated with reactivity. This can be useful in situations where an unaltered, non-reactive view of the data is needed. Additionally, `unwrap` provides a means to interface with third-party libraries or tools that anticipate regular JavaScript objects. This utility acts as a bridge to facilitate smooth integrations with external components and simplifies the incorporation of stores into various applications and workflows. import { createStore, unwrap } from "solid-js/store"const [data, setData] = createStore({ animals: ["cat", "dog", "bird", "gorilla"],})const rawData = unwrap(data) To learn more about how to use Stores in practice, visit the guide on complex state management. --- ## Page: https://docs.solidjs.com/concepts/refs Refs, or references, are a special attribute that can be attached to any element, and are used to reference a DOM element or a component instance. They are particularly useful when you need to access the DOM nodes directly or invoke methods on a component. * * * One way of accessing DOM elements is through element selectors such as `document.querySelector` or `document.getElementById`. Since elements in Solid can be added or removed from the DOM based on state, you need to wait until the element is attached to the DOM before accessing it. This can be done by using `onMount` to wait until the element is attached to the DOM before accessing it: Accessing DOM elements through element selectors is not recommended for this reason. As elements with the same selectors are added and removed from the DOM, the first element that matches the selector will be returned, which may not be the element you want. * * * JSX can be used as a value and assigned to a variable when looking to directly access DOM elements. function Component() { const myElement = <p>My Element</p> return <div>{myElement}</div>} This lets you create and access DOM elements similar to `document.createElement` but without having to wait until it is attached to the DOM. It can be used multiple times without having to worry about duplicate selectors. The downside of this approach is that it separates the element and any child elements from the rest of the JSX structure. This makes the component's JSX structure more difficult to read and understand. * * * Solid provides a ref system to access DOM elements directly inside the JSX template, which keeps the structure of the elements intact. To use `ref`, you declare a variable and use it as the `ref` attribute: function Component() { let myElement; return ( <div> <p ref={myElement}>My Element</p> </div> )} These assignments occur at _creation time_ prior to the element being added to the DOM. If access to an element is needed before it is added to the DOM, you can use the callback form of `ref`: <p ref={(el) => { myElement = el // el is created but not yet added to the DOM }}> My Element</p> ### Signals as refs Signals can also be used as refs. This is useful when you want to access the element directly, but the element may not exist when the component is first rendered, or may be removed from the DOM at some point. function App() { const [show, setShow] = createSignal(false) let element!: HTMLParagraphElement return ( <div> <button onClick={() => setShow((isShown) => !isShown)}>Toggle</button> <Show when={show()}> <p ref={element}>This is the ref element</p> </Show> </div> )} In this example, the paragraph element is only rendered when the `show` signal is `true`. When the component initializes, the paragraph element does not exist, so the `element` variable is not assigned. Once the `show` signal is set to `true`, the paragraph element is rendered, and the `element` variable is assigned to the paragraph element. You can see a detailed view of the ref update lifecycle in this Solid playground example. * * * Forwarding refs is a technique that allows you to pass a ref from a parent component to a child component. This is useful when you want to access the DOM element of a child component from the parent component. To forward a ref, you need to pass the ref to the child component, and then assign the ref to the child component's element. When a child component receives a `ref` attribute from its parent, the `ref` is passed as a callback function. This is regardless of whether the parent passed it as a simple assignment or a callback. Once the child component receives the `ref`, it can be assigned to the element that the child component wants to expose through the `ref` attribute. To access the `ref` in the child component, it is passed as a prop: // Parent componentimport { Canvas } from "./Canvas.jsx"function ParentComponent() { let canvasRef const animateCanvas = () => { // Manipulate the canvas using canvasRef... } return ( <div> <Canvas ref={canvasRef} /> <button onClick={animateCanvas}>Animate Canvas</button> </div> )}// Child componentfunction Canvas(props) { return ( <div className="canvas-container"> <canvas ref={props.ref} /> {/* Assign the ref to the canvas element */} </div> )} In this example, the `canvas` element is directly assigned the `ref` attribute through the `props.ref` variable. This forwards the reference to the parent component, giving it direct access to the `canvas` element. * * * Directives allow the attachment of reusable behaviours to DOM elements. The `use:` prefix is used to denote these custom directives. Unlike props or attributes, directives operate at a lower level through providing fine-grained control over the elements they are attached to. Directives are like callback refs but they enable two extra features: * Having multiple directives on an element. * Passing in reactive data to the callback. A directive is essentially a function with a specific signature: function directive(element: Element, accessor: () => any): void * `element`: The DOM element that the directive is applied to. * `accessor`: A function that gives access to the value(s) passed to the directive. The directive functions are called at render time, but are called before the element is added to the DOM. Due to this order, elements are fully primed with their attributes, properties, or event listeners, therefore minimizing unexpected behaviors or premature interactions. Within directives, you're able to perform a variety of tasks, including: * creating signals * initiating effects * adding event listeners * and more. To learn more about directives and how they work with TypeScript, refer to our TypeScript for Solid guide. --- ## Page: https://docs.solidjs.com/advanced-concepts/fine-grained-reactivity Reactivity ensures automatic responses to data changes, eliminating the need for manual updates to the user interface (UI). By connecting UI elements to the underlying data, updates become automated. In a fine-grained reactive system an application will now have the ability to make highly _targeted and specific_ updates. An example of this can be seen in the contrast between Solid and React. In Solid, updates are made to the targeted attribute that needs to be changed, avoiding broader and, sometimes unnecessary, updates. In contrast, React would re-execute an entire component for a change in the single attribute, which can be less efficient. Because of the fine-grained reactive system, unnecessary recalculations are avoided. Through targeting only the areas of an application that have changed the user experience becomes smoother and more optimized. **Note:** If you're new to the concept of reactivity and want to learn the basics, consider starting with our intro to reactivity guide. * * * In Solid's reactivity system, there are two key elements: signals and observers. These core elements serve as the foundation for more specialized reactive features: * Stores which are proxies that create, read, and write signals under the hood. * Memos resemble effects but are distinct in that they _return_ a signal and optimize computations through caching. They update based on the behavior of effects, but are more ideal for computational optimization. * Resources, building on the concept of memos, convert the asynchronicity of network requests into synchronicity, where the results are embedded within a signal. * Render effects are a tailored type of effect that initiate immediately, specifically designed for managing the rendering process. ### Understanding signals Signals are like mutable variables that can point to a value now and another in the future. They are made up of two primary functions: * **Getter**: how to read the current value of a signal. * **Setter**: a way to modify or update a signal's value. In Solid, the `createSignal` function can be used to create a signal. This function returns the getter and setter as a pair in a two-element array, called a tuple. import { createSignal } from "solid-js";const [count, setCount] = createSignal(1);console.log(count()); // prints "1"setCount(0); // changes count to 0console.log(count()); // prints "0" Here, `count` serves as the getter, and `setCount` functions as the setter. ### Effects Effects are functions that are triggered when the signals they depend on point to a different value. They can be thought of as automated responders where any changes in the signal's value will trigger the effect to run. import { createSignal, createEffect } from "solid-js";const [count, setCount] = createSignal(0);createEffect(() => { console.log(count());}); The effect takes a function that is called whenever _any_ of the signals it relies on changes, such as `count` in this example. * * * To grasp the concept of reactivity, it is often helpful to construct a reactive system from scratch. The following example will follow the observer pattern, where data entities (signals) will maintain a list of their subscribers (effects). This is a way to notify subscribers whenever a signal they observe changes. Here is a basic code outline to begin: function createSignal() {}function createEffect() {}const [count, setCount] = createSignal(0);createEffect(() => { console.log("The count is " + count());}); * * * ### `createSignal` The `createSignal` function performs two main tasks: 1. Initialize the value (in this case, `count` is set to `0`). 2. Return an array with two elements: the getter and setter function. function createSignal(initialValue) { let value = initialValue; function getter() { return value; } function setter(newValue) { value = newValue; } return [getter, setter];}// .. This allows you to retrieve the current value through the getter and make any changes via the setter. At this stage, reactivity is not present, however. ### `createEffect` `createEffect` defines a function that immediately calls the function that is passed into it: // ..function createEffect(fn) { fn();}// .. ### Making a system reactive Reactivity emerges when linking `createSignal` and `createEffect` and this happens through: 1. Maintaining a reference to the current subscriber's function. 2. Registering this function during the creation of an effect. 3. Adding the function to a subscriber list when accessing a signal. 4. Notifying all subscribers when the signal has updated. let currentSubscriber = null;function createSignal(initialValue) { let value = initialValue; const subscribers = new Set(); function getter() { if (currentSubscriber) { subscribers.add(currentSubscriber); } return value; } function setter(newValue) { if (value === newValue) return; // if the new value is not different, do not notify dependent effects and memos value = newValue; for (const subscriber of subscribers) { subscriber(); // } } return [getter, setter];}// creating an effectfunction createEffect(fn) { const previousSubscriber = currentSubscriber; // Step 1 currentSubscriber = fn; fn(); currentSubscriber = previousSubscriber;}//.. A variable is used to hold a reference to the current executing subscriber function. This is used to determine which effects are dependent on which signals. Inside `createSignal`, the initial value is stored and a Set is used to store any subscriber functions that are dependent on the signal. This function will then return two functions for the signal: * The `getter` function checks to see if the current subscriber function is being accessed and, if it is, adds it to the list of subscribers before returning the _current_ value of the signal. * The `setter` function evaluated the new value against the old value, notifying the dependent functions only when the signal has been updated. When creating the `createEffect` function, a reference to any previous subscribers is initialized to handle any possible nested effects present. The current subscriber is then passed to the given function, which is run immediately. During this run, if the effect accesses any signals it is then registered as a subscriber to those signals. The current subscriber, once the given function has been run, will be reset to its previous value so that, if there are any nested effects, they are operated correctly. ### Validating the reactive system To validate the system, increment the `count` value at one-second intervals: //..const [count, setCount] = createSignal(0);createEffect(() => { console.log("The count is " + count());});setInterval(() => { setCount(count() + 1);}, 1000); This will display the incremented count value on the console at one-second intervals to confirm the reactive system's functionality. * * * In reactive systems, various elements, often referred to as "nodes", are interconnected. These nodes can be signals, effects, or other reactive primitives. They serve as the individual units that collectively make up the reactive behavior of the system. When a node changes, the system will re-evaluate the parts connected to that node. This can result in updates, additions, or removals of these connections, which affect the overall behavior of the system. Now, consider a scenario where a condition influences the data used to calculate an output: // Temperature.jsxconsole.log("1. Initialize");const [temperature, setTemperature] = createSignal(72);const [unit, setUnit] = createSignal("Fahrenheit");const [displayTemp, setDisplayTemp] = createSignal(true);const displayTemperature = createMemo(() => { if (!displayTemp()) return "Temperature display is off"; return `${temperature()} degrees ${unit()}`;});createEffect(() => console.log("Current temperature is", displayTemperature()));console.log("2. Turn off displayTemp");setDisplayTemp(false);console.log("3. Change unit");setUnit("Celsius");console.log("4. Turn on displayTemp");setDisplayTemp(true); In this example, the `createMemo` primitive is used to cache the state of a computation. This means the computation doesn't have to be re-run if its dependencies remain unchanged. The `displayTemperature` memo has an early return condition based on the value of `displayTemp`. When `displayTemp` is false, the memo returns a message saying "Temperature display is off," and as a result, `temperature` and `unit` are not tracked. If the `unit` is changed while `displayTemp` is false, however, the effect won't trigger since none of the memo's current dependencies (`displayTemp` in this case) have changed. ### Synchronous nature of effect tracking The reactivity system described above operates synchronously. This operation has implications for how effects and their dependencies are tracked. Specifically, the system registers the subscriber, runs the effect function, and then unregisters the subscriber — all in a linear, synchronous sequence. Consider the following example: createEffect(() => { setTimeout(() => { console.log(count()); }, 1000);}); The `createEffect` function in this example, initiates a `setTimeout` to delay the console log. Because the system is synchronous, it doesn't wait for this operation to complete. By the time the `count` getter is triggered within the `setTimeout`, the global scope no longer has a registered subscriber. As a result, this `count` signal will not add the callback as a subscriber which leads to potential issues with tracking the changes to `count`. ### Handling asynchronous effects While the basic reactivity system is synchronous, frameworks like Solid offer more advanced features to handle asynchronous scenarios. For example, the `on` function provides a way to manually specify the dependencies of an effect. This is particularly useful for to make sure asynchronous operations are correctly tied into the reactive system. Solid also introduces the concept of resources for managing asynchronous operations. Resources are specialized reactive primitives that convert the asynchronicity of operations like network requests into synchronicity, embedding the results within a signal. The system can then track asynchronous operations and their state, keeping the UI up-to-date when the operation completes or its' state changes. Using resources in Solid can assist in complex scenarios when multiple asynchronous operations are involved and the completion may affect different parts of the reactive system. By integrating resources into the system, you can ensure that dependencies are correctly tracked and that the UI remains consistent with the underlying asynchronous data. --- ## Page: https://docs.solidjs.com/configuration/environment-variables Solid is built on top of Vite, which offers a convenient way to handle environment variables. * * * Public variables are considered safe to expose to the client-side code. These variables are prefixed with `VITE_` and are injected into the client-side code during compilation time. In the root directory of the project, create a file called `.env`. This file will store environment variables in the `key = value` format. If working with TypeScript it is possible to make such variables type-safe and enable your TypeScript Language Service Provider (LSP) to autocomplete them by creating a file called `env.d.ts` in the root directory of the project. interface ImportMetaEnv { readonly VITE_USER_ID: string; readonly VITE_PUBLIC_ENDPOINT: string;}interface ImportMeta { readonly env: ImportMetaEnv;} function MyComponent() { return ( <div> <h2> Component with environment variable used{" "} {import.meta.env.VITE_VARIABLE_NAME} the value will be replaced during compilation time. </h2> </div> );} * * * These variables should only be accessed in your backend code, so it's best not to use the `VITE_` prefix for them. Instead, use `process.env` to access them. Depending on the Nitro preset chosen, they'll be made available automatically or they will require an external dependency such as dotenv. DB_HOST="somedb://192.110.0"DB_PASSWORD = super_secret_password_hash To access them, within your backend code, use `process.env`. For an example, check the pseudo-code below. "use server" const client = new DB({ host: process.env.DB_URL, password: process.env.DB_PASSWORD });} It is also possible to make `process.env` type-safe via the same `env.d.ts` file. declare namespace NodeJS { interface ProcessEnv { readonly DB_URL: string readonly DB_PASSWORD: string }} --- ## Page: https://docs.solidjs.com/configuration/typescript TypeScript is a superset of JavaScript that enhances code reliability and predictability through the introduction of static types. While JavaScript code can be directly used in TypeScript, the added type annotations in TypeScript provide clearer code structure and documentation, making it more accessible for developers. By leveraging standard JSX, a syntax extension to JavaScript, Solid facilitates seamless TypeScript interpretation. Moreover, Solid has built-in types for the API that heighten accuracy. For developers eager to get started, we offer TypeScript templates on GitHub. * * * When integrating TypeScript with the Solid JSX compiler, there are some settings to make for a seamless interaction: 1. `"jsx": "preserve"` in the `tsconfig.json` retains the original JSX form. This is because Solid's JSX transformation is incompatible with TypeScript's JSX transformation. 2. `"jsxImportSource": "solid-js"` designates Solid as the source of JSX types. For a basic setup, your `tsconfig.json` should resemble: { "compilerOptions": { "jsx": "preserve", "jsxImportSource": "solid-js" }} For projects with diverse JSX sources, such as a blend of React and Solid, some flexibility exists. While it's possible to set a default `jsxImportSource` in the `tsconfig.json`, which will correspond with the majority of your files, TypeScript also allows file-level overrides. Using specific pragmas within `.tsx` files facilitates this: /** @jsxImportSource solid-js */ or, if using React: /** @jsxImportSource react */ Opting for the React JSX pragma means having React and its associated dependencies fully integrated into the project. Additionally, it makes sure the project's architecture is primed for React JSX file handling, which is vital. * * * Transitioning from JavaScript to TypeScript in a Solid project offers the benefits of static typing. To migrate to Typescript: 1. Install TypeScript into your project. npm i typescript -D pnpm i typescript -D yarn add typescript -D bun i typescript -d deno add npm:typescript -D 2. Run the following command to generate a `tsconfig.json` file. npx tsc --init pnpx tsc --init yarn dlx tsc --init bunx tsc --init dpx tsc --init 3. Update the contents of the `tsconfig.json` to match Solid's configuration: { "compilerOptions": { "strict": true, "target": "ESNext", "module": "ESNext", "moduleResolution": "node", "allowSyntheticDefaultImports": true, "esModuleInterop": true, "jsx": "preserve", "jsxImportSource": "solid-js", "types": ["vite/client"], "noEmit": true, "isolatedModules": true }} 4. Create a TypeScript or `.tsx` file to test the setup. import { type Component } from "solid-js";const MyTsComponent: Component = () => { return ( <div> <h1>This is a TypeScript component</h1> </div> );}export default MyTsComponent; If using an existing JavaScript component, import the TypeScript component: import MyTsComponent from "./MyTsComponent";function MyJsComponent() { return ( <> {/* ... */} <MyTsComponent /> </> );} * * * Solid is written in TypeScript, meaning everything is typed out of the box. The Reference Tab in the sidebar provides the API Documentation that details the types of API calls. In addition, there are several helpful definitions to make it easier for specifying explicit types. ### Signals Using `createSignal<T>`, a signal's type can be defined as `T`. const [count, setCount] = createSignal<number>(); Here, `createSignal` has the return type `Signal<number | undefined>`, which corresponds to the type passed into it, and `undefined` as it was uninitialized. This resolves to a getter-setter tuple, both of which are generically typed: import type { Signal, Accessor, Setter } from "solid-js";type Signal<T> = [get: Accessor<T>, set: Setter<T>]; In Solid, a signal's getter, like `count`, is essentially a function that returns a specific type. In this case, the type is `Accessor<number | undefined>`, which translates to a function `() => number | undefined`. Since the signal was not initialized, its initial state is `undefined`, therefore `undefined` is included in its type. The corresponding setter, `setCount`, has a more complex type: Setter<number | undefined>. Essentially, this type means that the function can accept either a direct number or another function as its input. If provided with a function, that function can take the signal's previous value as its parameter and return a new value. Both the initial and resulting values can be a number or `undefined`. Importantly, calling `setCount` without any arguments will reset the signal's value to `undefined`. When using the function form of the setter, the signal's current value will always be passed to the callback as the sole argument. Additionally, the return type of the setter will align with the type of value passed into it, echoing the behavior expected from a typical assignment operation. If a signal is intended to store functions, the setter won't directly accept new functions as values. This is because it can not distinguish whether the function should be executed to yield the actual value or to store it as-is. In these situations, using the callback form of the setter is recommended: setSignal(() => value); #### Default values By providing default values when `createSignal` is called, the need for explicit type specification is avoided and the possibility of the `| undefined` type is eliminated. This leverages type inference to determine the type automatically: const [count, setCount] = createSignal(0);const [name, setName] = createSignal(""); In this example, TypeScript understands the types as `number` and `string`. This means that `count` and `name` directly receive the types `Accessor<number>` and `Accessor<string>`, respectively, without the `| undefined` tag. ### Context Just as signals use `createSignal<T>`, context uses `createContext<T>`, which is parameterized by the type `T` of the context's value: type Data = { count: number; name: string }; When invoking `useContext(dataContext)`, the type contained within the context is returned. For example, if the context is `Context<Data | undefined>`, then with using `useContext` a result of type `Data | undefined` will return. The `| undefined` signifies that the context may not be defined in the component's ancestor hierarchy. `dataContext` will be understood as `Context<Data | undefined>` by Solid. Calling `useContext(dataContext)` mirrors this type, returning `Data | undefined`. The `| undefined` arises when the context's value will be used but cannot be determined. Much like default values in signals, `| undefined` can be avoided in the type by giving a default value that will be returned if no value is assigned by a context provider: const dataContext = createContext({ count: 0, name: "" }); By providing a default value, TypeScript determines that `dataContext` is `Context<{ count: number, name: string }>`. This is equivalent to `Context<Data>` but without `| undefined`. A common approach to this is forming a factory function to generate a context's value. By using TypeScript's `ReturnType`, you can use the return type of this function to type this context: export const makeCountNameContext = (initialCount = 0, initialName = "") => { const [count, setCount] = createSignal(initialCount); const [name, setName] = createSignal(initialName); return [ { count, name }, { setCount, setName }, ] as const;};type CountNameContextType = ReturnType<typeof makeCountNameContext>;export const CountNameContext = createContext<CountNameContextType>(); `CountNameContextType` will correspond to the result of `makeCountNameContext`: [ { count: Accessor<number>, name: Accessor<string> }, { setCount: Setter<number>, setName: Setter<string> },]; To retrieve the context, use `useCountNameContext`, which has a type signature of `() => CountNameContextType | undefined`. In scenarios where `undefined` needs to be avoided as a possible type, assert that the context will always be present. Additionally, throwing a readable error may be preferable to non-null asserting: export const useCountNameContext = () => { const countName = useContext(CountNameContext); if (!countName) { throw new Error( "useCountNameContext should be called inside its ContextProvider" ); } return countName;}; **Note:** While supplying a default value to `createContext` can make the context perpetually defined, this approach may not always be advisable. Depending on the use case, it could lead to silent failures, which may be less preferable. ### Components #### The basics By default, components in Solid use the generic `Component<P>` type, where `P` represents the props' type that is an object. import type { Component } from "solid-js";const MyComponent: Component<MyProps> = (props) => { ...} A `JSX.Element` denotes anything renderable by Solid, which could be a DOM node, array of JSX elements, or functions yielding `JSX.Element`. Trying to pass unnecessary props or children will result in type errors: // in counter.tsxconst Counter: Component = () => { const [count, setCount] = createSignal(0); return ( <button onClick={() => setCount((prev) => prev + 1)}>{count()}</button> );};// in app.tsx<Counter />; // ✔️<Counter initial={5} />; // ❌: No 'initial' prop defined<Counter>hi</Counter>; // ❌: Children aren't expected #### Components with props For components that require the use of props, they can be typed using generics: const InitCounter: Component<{ initial: number }> = (props) => { const [count, setCount] = createSignal(props.initial); return ( <button onClick={() => setCount((prev) => prev + 1)}>{count()}</button> );};<InitCounter initial={5} />; #### Components with children Often, components may need to accept child elements. For this, Solid provides `ParentComponent`, which includes `children?` as an optional prop. If defining a component with the `function` keyword, `ParentProps` can be used as a helper for the props: import { ParentComponent } from "solid-js";const CustomCounter: ParentComponent = (props) => { const [count, setCount] = createSignal(0); return ( <button onClick={() => setCount((prev) => prev + 1)}> {count()} {props.children} </button> );}; In this example, `props` is inferred to be of the type `{children?: JSX.Element }`, streamlining the process of defining components that can accept children. #### Special component types Solid offers subtypes for components dealing uniquely with children: * **VoidComponent:** When a component should not accept children. * **FlowComponent:** Designed for components like `<Show>` or `<For>`, typically requiring children and, occasionally, specific children types. These types make sure that the children fit the required type, maintaining consistent component behaviour. ##### Components without the `Component` types Using the `Component` types is a matter of preference over a strict requirement. Any function that takes props and returns a JSX.Element qualifies as a valid component: // arrow functionconst MyComponent = (props: MyProps): JSX.Element => { ... }// function declarationfunction MyComponent(props: MyProps): JSX.Element { ... }// component which takes no propsfunction MyComponent(): JSX.Element { ... } It is worth noting that the `Component` types **cannot be used to create generic components**. Instead, the generics will have to be typed explicitly: // For arrow functions, the syntax <T> by itself is invalid in TSX because it could be confused with JSX.const MyGenericComponent = <T extends unknown>( props: MyProps<T>): JSX.Element => { /* ... */};// Using a function declaration for a generic componentfunction MyGenericComponent<T>(props: MyProps<T>): JSX.Element { /* ... */} ### Event handling #### Basics In Solid, the type for event handlers is specified as `JSX.EventHandler<TElement, TEvent>`. Here, `TElement` refers to the type of the element the event is linked to. `TEvent` will indicate the type of the event itself which can serve as an alternative to `(event: TEvent) => void` in the code. This approach guarantees accurate typing for `currentTarget` and `target` within the event object while also eliminating the need for inline event handlers. import type { JSX } from "solid-js"// Defining an event handler using the `EventHandler` type:const onInput: JSX.EventHandler<HTMLInputElement, InputEvent> = (event) => { console.log("Input changed to", event.currentTarget.value)}// Then attach handler to an input element:<input onInput={onInput} /> #### Inline handlers Defining an event handler inline within a JSX attribute automatically provides type inference and checking, eliminating the need for additional typing efforts: <input onInput={(event) => { console.log("Input changed to", event.currentTarget.value); }}/> #### `currentTarget` and `target` In the context of event delegation, the difference between `currentTarget` and `target` is important: * `currentTarget`: Represents the DOM element to which the event handler is attached. * `target`: Any DOM element within the hierarchy of `currentTarget` that has initiated the event. In the type signature `JSX.EventHandler<T, E>`, `currentTarget` will consistently have the type `T`. However, the type of target could be more generic, potentially any DOM element. For specific events like `Input` and `Focus` that are directly associated with input elements, the target will have the type `HTMLInputElement`. ### `ref` attribute #### Basics In an environment without TypeScript, using the `ref` attribute in Solid ensures that the corresponding DOM element is assigned to the variable after it is rendered: let divRef;console.log(divRef); // Outputs: undefinedonMount(() => { console.log(divRef); // Outputs: <div> element});return <div ref={divRef} />; In a TypeScript environment, particularly with strict `null` checks enabled, typing these variables can be a challenge. A safe approach in TypeScript is to acknowledge that `divRef` may initially be `undefined` and to implement checks when accessing it: let divRef: HTMLDivElement | undefined// This would be flagged as an error during compilationdivRef.focus()onMount(() => { if (!divRef) return divRef.focus()})return <div ref={divRef}>...</div> Within the scope of the `onMount` function, which runs after rendering, you can use a non-`null` assertion (indicated by the exclamation mark `!`): onMount(() => { divRef!.focus();}); Another approach is to bypass `null` during the assignment phase and then apply a definite assignment assertion within the `ref` attribute: let divRef: HTMLDivElement// Compilation error as expecteddivRef.focus()onMount(() => { divRef.focus()})return <div ref={divRef!}>...</div> In this case, using `divRef!` within the `ref` attribute signals to TypeScript that `divRef` will receive an assignment after this stage, which is more in line with how Solid works. Finally, a riskier approach involves using the definite assignment assertion right at the point of variable initialization. While this method bypasses TypeScript's assignment checks for that particular variable, it offers a quick but less secure workaround that could lead to runtime errors. let divRef!: HTMLDivElement;// Permitted by TypeScript but will throw an error at runtime:// divRef.focus();onMount(() => { divRef.focus();}); * * * Control flow-based narrowing involves refining the type of a value by using control flow statements. Consider the following: const user: User | undefined = maybeUser();return <div>{user && user.name}</div>; In Solid, however, accessors cannot be narrowed in a similar way: const [user, setUser] = createSignal<User>();return <div>{user() && user().name}</div>;// ^ Object may be 'undefined'.// Using `<Show>`:return ( <div> <Show when={user()}> {user().name /* Object is possibly 'undefined' */} </Show> </div>); In this case, using optional chaining serves as an good alternative: return <div>{user()?.name}</div>;// Using `<Show>`:return ( <div> <Show when={user()}> {(nonNullishUser) => <>{nonNullishUser().name}</>} </Show> </div>); This approach is similar to using the keyed option, but offers an accessor to prevent the recreation of children each time the `when` value changes. return ( <div> <Show keyed when={user()}> {(nonNullishUser) => nonNullishUser.name} </Show> </div>); Note that optional chaining may not always be possible. For instance, when a `UserPanel` component exclusively requires a `User` object: return <UserPanel user={user()} />;// ^ Type 'undefined' is not assignable to type 'User'. If possible, consider refactoring `UserPanel` to accept `undefined`. This minimizes the changes required when `user` goes from `undefined` to `User`. Otherwise, using Show's callback form works: return ( <Show when={user()}> {(nonNullishUser) => <UserPanel user={nonNullishUser()} />} </Show>); Casting can also be a solution so long as the assumption is valid: return <div>{user() && (user() as User).name}</div>; It's worth noting that runtime type errors may arise from doing this. This may happen when passing a type-cast value to a component, which discards information that may be nullish followed by accessing it asynchronously, such as in an event handler or timeout, or in `onCleanup`. `<Show>` only excludes `null`, `undefined`, and `false` from `when` when using the callback form. If the types in a union need to be differentiated, a memo or computed signal can work as an alternative solution: type User = Admin | OtherUser;const admin = createMemo(() => { const u = user(); return u && u.type === "admin" ? u : undefined;});return <Show when={admin()}>{(a) => <AdminPanel user={a()} />}</Show>; The following alternative also works when using `Show`: <Show when={(() => { const u = user(); return u && u.type === "admin" ? u : undefined; })()}> {(admin) => <AdminPanel user={admin()} />}</Show> * * * ### Custom event handlers To handle custom events in Solid, you can use the attribute `on:___`. Typing these events requires an extension of Solid's JSX namespace. class NameEvent extends CustomEvent { type: "Name"; detail: { name: string }; constructor(name: string) { super("Name", { detail: { name } }); }}declare module "solid-js" { namespace JSX { interface CustomEvents { Name: NameEvent; // Matches `on:Name` } }}// Usage<div on:Name={(event) => console.log("name is", event.detail.name)} />; It is now possible to use the intersection `EventListenerObject & AddEventListenerOptions` to provide listener options as follows: import type { JSX } from "solid-js"const handler: JSX.EventHandlerWithOptions<HTMLDivElement, Event> = { once: true, handleEvent: (event) => { console.log("will fire only once"); },}// Usage<div on:click={handler} />; #### Forcing properties and custom attributes In Solid, the `prop:___` directive allows explicit property setting, which is useful for retaining the original data types like objects or arrays. `attr:___` directive allows custom attributes, on the other hand, and it is effective for handling string-based HTML attributes. declare module "solid-js" { namespace JSX { interface ExplicitProperties { count: number; name: string; } interface ExplicitAttributes { count: number; name: string; } interface ExplicitBoolAttributes { disabled: boolean; } }}// Usage<Input prop:name={name()} prop:count={count()}/><my-web-component attr:name={name()} attr:count={count()} bool:disabled={true}/> #### Custom directives In Solid, custom directives can be applied using the `use:___` attribute, which usually accepts a target element and a JSX attribute value. The traditional `Directives` interface types these values directly (i.e. the type of `value` in `<div use:foo={value} />`). However, the newer `DirectiveFunctions` interface takes a function type and derives the valid types for elements and values from it. There are additional considerations: * The directive function always receives a single accessor. For multiple arguments, the syntax `<div use:foo={[a, b]} />` is an option, and an accessor to a tuple should be accepted. * The same principle holds for boolean directives, as seen in `<div use:foo />`, and for directives with static values, like `<div use:foo={false} />`. * `DirectiveFunctions` can accept functions that do not strictly meet the type requirements; such cases will be ignored. function model( element: Element, // directives can be used on any HTML and SVG element value: Accessor<Signal<string>> // second param will always be an accessor in case value being reactive) { const [field, setField] = value(); createRenderEffect(() => (element.value = field())); element.addEventListener("input", (e) => { const value = (e.target as HTMLInputElement).value; setField(value); });}declare module "solid-js" { namespace JSX { interface Directives { model: Signal<string>; // Corresponds to `use:model` } }}// Usagelet [name, setName] = createSignal("");<input type="text" use:model={[name, setName]} />; In using `DirectiveFunctions`, there's the ability to check both arguments (if present) by detailing the entire function type: function model(element: HTMLInputElement, value: Accessor<Signal<string>>) { const [field, setField] = value(); createRenderEffect(() => (element.value = field())); element.addEventListener("input", (e) => setField(e.target.value));}function log(element: Element) { console.log(element);}let num = 0;function count() { num++;}function foo(comp: Element, args: Accessor<string[]>) { // function body}declare module "solid-js" { namespace JSX { interface DirectiveFunctions { model: typeof model; log: typeof log; count: typeof count; foo: typeof foo; } }} While the `Directives` interface can limit the value type passed via JSX attribute to the directive, the `DirectiveFunctions` interface ensures that both element and value align with the expected types, as shown below: {/* This is correct */}<input use:model={createSignal('')} />{/* These will result in a type error */}<input use:model /><input use:model={7} /><div use:model={createSignal('')} /> ##### Addressing import issues with directives If directives are imported from a separate file or module, TypeScript might mistakenly remove the import thinking it is a type. To prevent this: * Configure `onlyRemoveTypeImports: true` in `babel-preset-typescript`. * When using `vite-plugin-solid`, set `solidPlugin({ typescript: { onlyRemoveTypeImports: true } })` in `vite.config.ts`. Careful management of export type and import type is required. Including a statement in the importing module ensures TypeScript keeps the directive's import. Tree-shaking tools usually omit this code from the final bundle. import { directive } from "./directives.js"directive // prevents TypeScript's tree-shaking<div use:directive />