Solid is a Reactive library for creating user interfaces. Solid updates the DOM only where it is required, without using Virtual DOM. Solid is fast.
To understand Solid, first we have to understand its Reactive ideology.
Let’s take an example of a simple auto updating counter to show how the code looks like and explain how it works under the hood.
First a counter without a UI: Solid Counter - CodeSandbox
import { createState, createEffect } from 'solid-js';
const [state, setState] = createState({ count: 0 });
setInterval(() => {
setState({ count: state.count + 1 });
}, 1000);
createEffect(() => {
console.log(state.count);
});
This code looks too much similar to React hooks and it is actually inspired from React hooks. But there is a major difference, contrary to React, this code will not run again and again top to down like in a React component. Instead we have code blocks which are run whenever their dependencies are updated.
createEffect
Whenever the value of state.count
is updated the code block passed to createEffect
is called which in turn will call console.log
every 1 second.
0
1
2
3
4
...
How does createEffect
knows when to call this function whenever state.count
changes ?
The answer lies in Javascript Proxy.
The state returned by createState is actually a proxy object, it has setter and getter traps for all properties on the original object.
Whenever a value is updated on this object, the setter is called. And whenever a value is accessed, the getter is called.
Let’s try to have a feel with some rough pseudocode:
let activeBlock;
// not real code, rough estimation
state.count = {
_blocks: [],
getter() {
if (activeBlock) {
this._blocks.push(activeBlock);
}
...
}
setter() {
if (this._blocks.length) {
this._blocks.forEach(block => block());
}
...
}
}
function createEffect(f) {
activeBlock = f;
f();
activeBlock = undefined;
}
...
createEffect(() => {
console.log(state.count);
});
Let’s call the function passed to createEffect as f
.
When we call createEffect, first we save f
in activeBlock
variable and then run f
. Then whenever a getter is hit, we save activeBlock
with the current property so that the dependent blocks can be run, when there is a hit to its setter.
In our counter example, f
is the block with console.log(state.count)
in it. For the first time when createEffect
calls f
, it saves f
in activeBlock
variable. Then it runs f
.
Now when f
runs, it encounters state.count
and its getter is executed,. Then f
is pushed to this._blocks
.
If we also had some other block with state.count
, its reference would also be pushed in the this._blocks
array.
createEffect(() => {
console.log('2 x Counter = ', state.count * 2);
});
If in the future state.count
is updated, its setter will be triggered and it will execute all its saved blocks.
In this way we do not need to explicitly define what we have to track, and the blocks would be run whenever anything in the block changes.
setState instead of setter
Solid emphasises uni-directional data flow like React. So instead of directly updating the value of state, setState
must be used to trigger the setter.
Reactive html element with solid
Now that we know the basics of createEffect
and how its works, let's use createEffect
to update an HTML element.
Counter with a UI element: Solid Counter - CodeSandbox
const [state, setState] = createState({ count: 0 });
setInterval(() => {
setState({ count: state.count + 1 });
}, 1000);
const el = document.createElement('div');
document.body.appendChild(el);
createEffect(() => {
el.innerText = state.count;
});
So we are using createEffect to update the DOM text.
Similarly we can update className, style and other attributes reactively:
className
createEffect(() => {
el.className = state.count % 2 === 0 ? "even" : "odd";
});
style
we are using Object.assign because =
does not work with style attribute.
createEffect(() => {
Object.assign(
el.style,
{ color: state.count % 2 === 0 ? "black" : "white" }
);
});
data-*
createEffect(() => {
el.setAttribute(
"data-test",
state.count % 2 === 0 ? "even" : "odd"
);
});
These were the basic reactive ideas needed to understand how solid works under the hood. Next post will cover jsx
in solid.
This is the first blog post of series: Understanding Solid
. Next post: Understanding Solid: JSX
Top comments (1)
"this code will not run again and again top to down like in a React component“
This is a very important difference that could be very nice in practice. I'll have to try solid js