Stefanie Gauss
Stefanie Gauss

Reputation: 737

In SolidJS, how do Signals get repeatedly rendered into the DOM?

I think I understand Signals, Reactions, Derivations quite well in terms of Reactivity.

However, I can't understand how all of a sudden, putting them into an App() function and the Signals can be updated into the DOM repeated when the Signals are updated?

I think perhaps this doc explains it, but it seems too complicated to understand. Can it be explained in a couples of paragraphs?

For example, I'd imagine

<div>{value()}</div>

where value() comes from

const [value, setValue] = createSignal(123);

would have some code to have some Reaction that reacts to the Signal value's change and update the DOM, but how did it happen? Is this createSignal() the same as the regular createSignal() or is it a special edition that sets up a Reaction to update the DOM? Also related is, since App() is only run once, then how come the render() needs to take () => <App /> but cannot just take <App /> as its first argument? If App() runs once only, that means () => <App /> runs once only. So then why not just simply give a <App />?

Upvotes: 2

Views: 1600

Answers (1)

snnsnn
snnsnn

Reputation: 13698

Components are compiled into effects that gets re-evaluated when the signal's value is updated. Let me explain it briefly:

  1. When invoked createSignal creates a subscribers list and returns a pair of functions: a setter and a getter. Every signal keeps its own subscribers list.

The setter function sets the next value, while the getter function returns the stored value.

  1. When invoked, the getter function gets the stored value but before getting the value, the effect that wraps the getter invocation will be added to the signal's subscriber list.

So, reading a value, in other words invoking the getter as value(), automatically inserts an effect into the signal's subscribers list.

This effect can be created via createEffect maunally or automatically when JSX component gets compiled into a function call.

So, when the signal gets updated, it is these effects that gets executed.

  1. JSX components are compiled into effects, which creates and updates DOM nodes whenever signal value changes.

Lets see how components gets compiled into effects:

import { createSignal } from 'solid-js';

export const Counter = () => {
  const [count, setCount] = createSignal(0);

  return <div>{count()}</div>
};

The compiled output for the above component is as follows:

import { createSignal } from 'solid-js';

import { template as _$template } from "solid-js/web";
import { insert as _$insert } from "solid-js/web";

const _tmpl$ = /*#__PURE__*/_$template(`<div></div>`, 2);

export const Counter = () => {
  const [count, setCount] = createSignal(0);
  return (() => {
    const _el$ = _tmpl$.cloneNode(true);
    _$insert(_el$, count);
    return _el$;
  })();
};

See the line _$template(`<div></div>`, 2) and _tmpl$.cloneNode(true) and _$insert(_el$, count);.

Here:

  • _$template function creates a template reflecting the component's DOM structure.

  • cloneNode clones the template.

  • $insert function populates the values and inserts the returned DOM element into its parent DOM element.

So, it is not the getter's invocation but the re-running the whole effect that has the getter invocation updates the DOM.

How come the render() needs to take () => but cannot just take as its first argument?

The documentation answers your question. Solid's runtime keeps tracks of who owns the whom in order to prevent memory leaks. Passing a function is needed for settings things up.

It's important that the first argument is a function: do not pass JSX directly (as in render(, ...)), because this will call App before render can set up a root to track signal dependencies within App. https://www.solidjs.com/docs/latest#render

So, you can see <App /> as a function invocation, or more accurately as the invocation of the App function. So, you will be feeding a value to the render function.

If you don't like () => <App /> syntax, you can pass App function like so:

const dispose = render(App, document.getElementById("app"));

The important thing is you provide a function that returns a JSX element which will be used as the root element of your application.

Upvotes: 5

Related Questions