Tools for class-based reactivity powered by Solid.js, and for using classes as Solid components (f.e. in a JSX template).
npm install classy-solid --save
API and Usage
Note, these docs assume you have basic knowledge of Solid.js first.
@reactive
Mark a class with this decorator if it will have reactive properties (properties backed by Solid signals). See @signal below for an example.
@signal
Decorate a property of a class with @signal to make it reactive (backed by a Solid signal). Be sure to decorate a class that has reactive properties with the @reactive decorator as well.
import{reactive,signal}from'classy-solid'import{createEffect}from'solid-js'
@reactiveclassCar{
@signalengineOn=false
@signalsound='vroom'}constcar=newCar()// ...createEffect(()=>{// This re-runs when car.engineOn or car.sound change.if(car.engineOn)console.log(car.sound)})
@component
A decorator for using classes as Solid components.
Note, the @component decorator is still being refined, subject to change in the next few releases.
Examples:
Plain JS
, document.body)">
import{component,reactive,signal}from'classy-solid'
@component
@reactiveclassMyComp{
@signallast='none'onMount(){console.log('mounted')}template(props){// here we use `props` passed in, or the signal on `this` which is also// treated as a propreturn(<h1>
Hello, my name is {props.first}{this.last}</h1>)}}render(()=><MyCompfirst="Joe"last="Pea"/>,document.body)
Note: You only need the @reactive decorator if you will use @signal properties in your class. Make sure the @component decorator comes before the @reactive decorator, or else a runtime error will let you know. :)
Without decorators, for plain JS users who don't have decorator setups in their build yet (note, the new decorators proposal reached stage 3! So this will change soon!):
, document.body)">
import{component,reactive,signal}from'classy-solid'constMyComp=component(reactive(classMyComp{staticsignalProperties=['last']last='none'onMount(){console.log('mounted')}template(props){// here we use `props` passed in, or the signal on `this` which// is also a prop on the outside.return(<h1>
Hello, my name is {props.first}{this.last}</h1>)}},),)render(()=><MyCompfirst="Joe"last="Pea"/>,document.body)
TypeScript
, document.body)">
import{component,reactive,signal,Props}from'classy-solid'
@component
@reactiveclassMyComp{// This defines prop types for JSX. Note, this property does not actually// need to exist at runtime, hence the `!` to tell TS not to worry about it// being undefined.PropTypes!: Props<this,'last'|'count'|'first'>
@signallast='none'
@signalfirst='none'
@signalcount=123// This property will not appear in the JSX prop types, because we did not// list it in the PropTypes definition.foo='blah'onMount(){console.log('mounted')}template(props: this['PropTypes']){// Note, unlike the plain JS example, we had to define a `first`// property on the class, or else it would not have a type definition// here. Plain JS has no types, so no issue there.return(<h1>
Hello, my name is {props.first}{this.last}. The count is {this.count}</h1>)}}render(()=><MyCompfirst="Joe"last="Pea"count={456}/>,document.body)
If you're using TypeScript syntax, there's no reason not to use decorators because TS has support for it.
createSignalObject()
Returns a Solid signal in the form of an object with .get and .set methods, instead of an array tuple.
letcount=createSignalObject(0)// count starts at 0count.set(1)// set the value of count to 1count.set(count.get()+1)// add 1letcurrentValue=count.get()// read the current value
In cases where decorators are not yet supported or undesired, using Solid's createSignal directly as a class property is not so ideal:
classCounter{count=createSignal(0)increment(){// These are not so readable:this.count[1](this.count[0]()+1)// orthis.count[1](c=>c+1)}}
createSignalObject provides an alternative that is more usable as a class property:
classCounter{count=createSignalObject(0)increment(){// These are more readable:this.count.set(this.count.get()+1)// orthis.count.set(c=>c+1)}}
createSignalFunction()
Returns a Solid signal in the form of a single overloaded function for both getting and setting the signal, instead of an array tuple. Call the function with no arguments to get the signal value, and call it with an arg to set the signal value.
letcount=createSignalFunction(0)// count starts at 0count(1)// set the value of count to 1count(count()+1)// add 1letcurrentValue=count()// read the current value
In cases where decorators are not yet supported or undesired, using Solid's createSignal directly as a class property is not so ideal:
classCounter{count=createSignal(0)increment(){// These are not so readable:this.count[1](this.count[0]()+1)// orthis.count[1](c=>c+1)}}
createSignalFunction provides an alternative that is more usable as a class property:
classCounter{count=createSignalFunction(0)increment(){// These are more readable:this.count(this.count()+1)// orthis.count(c=>c+1)}}
signalify()
Use this to convert properties on an object into Solid signal-backed properties.
There are two ways to use this: either by defining which properties to convert to signal-backed properties by providing an array as property names in the second arg, which is useful on plain objects, or by passing in this and this.constructor within the constructor of a class that has reactive properties listed in a static signalProperties array (this is what the @reactive and @signal decorators end up doing behind the scenes).
This can be useful with plain objects, as well with classes in situations where decorators are unavailable or undesired.
In some cases, using signalify is more desirable than Solid's createMutable because the original object will be in use, rather than a Proxy. This can be useful, for example, for patching 3rd-party objects to make them reactive, whereas it would not be possible with createMutable.
Here are some examples. Make certain properties on an object reactive signal-backed properties:
import{signalify}from'class-solid'import{createEffect}from'solid-js'constobj={foo: 1,bar: 2,baz: 3,}// Make only the 'foo' and 'bar' properties reactive (backed by Solid signals).signalify(obj,['foo','bar'])// ...createEffect(()=>{console.log(obj.foo,obj.bar)})
Note, it returns the same object passed in, so you can write this:
constobj=signalify({foo: 1,bar: 2,baz: 3,},// Make only the 'foo' and 'bar' properties reactive (backed by Solid signals).['foo','bar'],)
If you want to make all properties signal-backed, then omitting the array will internally use Object.keys(obj) as a default:
// Make all properties reactive signalsconstobj=signalify({foo: 1,bar: 2,baz: 3,})
Note that the object passed in is the same object returned:
Signalify certain properties in a class (alternative to decorators):
import{signalify}from'class-solid'import{createEffect}from'solid-js'classCounter{count=0on=trueconstructor(){// Make only the 'count' variable reactive (signal-backed). The 'on'// variable remains a regular property.signalify(this,['count'])}}constc=newCounter()// ...createEffect(()=>{console.log(c.count)})
The downside of the previous example (namely, not using decorators) is that the code is less DRY, we had to repeat ourselves by writing the word "count" twice. But, if you're okay with it, you can make all properties reactive by omitting the second arg (sometimes you don't want all properties to be reactive):
classCounter{count=0on=trueconstructor(){// Both 'count' and 'on' will be signal-backed:signalify(this)}}
Another way to describe which properties are reactive is with a static signalProperties array and passing the constructor to signalify, which is exactly what the decorators are syntax sugar for:
classCounter{count=0on=truestaticsignalProperties=['count']constructor(){// Only 'count' will be signal-backed:signalify(this,this.constructor)}}
Note how with decorators, the code is more DRY and concise, because we don't have to repeat the count word twice, therefore reducing some surface area for human mistakes, and we don't have to write a constructor:
I'm unable to use classy while using the script-src directive in a Content Security Policy due to the use of the new Function() constructor. Is it possible to avoid using it?
I think many use Vite with Solid, but I noticed there aren't any instructions on using classy-solid with Vite. Using the example car class from the readme will cause errors with Vite if it's not configured to use decorators (Support for the experimental syntax 'decorators' isn't currently enabled).
To do so, one can install @babel/plugin-proposal-class-properties, and @babel/plugin-proposal-decorators. Then update their vite.config.ts file to include:
I have a solid-js project setup with their basic Vite template. I enabled experimental decorators with babel and was able to successfully use the @reactive decorator. However, whenever I try to use the @component decorator I get the following error:
A class descriptor's .kind property must be "class", but a decorator created a class descriptor with .kind "undefined"
I was able to solve the problem by exporting the class and wrapping it inside the component function like such:
export default component(Home)
Home being the class name
bug
opened by szammyboi 2
Releases(v0.2.0)
v0.2.0(Oct 7, 2022)
update to stage 3 decorators (57597e9cf693c9fbf076c2f35bec77e4cf9a3269) See README for updated usage instructions.
BREAKING:
Removed some use cases that involved using the previous @reactive and @component decorators as non-decorator function calls in plain JavaScript. Those decorators can only be used only as decorators now.
The static signalProperties feature was removed.
The signalify() signature changed: it no longer accepts classes (with static signalProperties properties) as a second argument.
Migration:
You will need to update your build to use Babel's new stage 3 decorator support, detailed in this blog post: https://babeljs.io/blog/2022/09/05/7.19.0#stage-3-decorators-14836httpsgithubcombabelbabelpull14836
TypeScript does not support stage 3 decorators yet, so if you use TypeScript, you will need to transpile code using babel instead of tsc (use tsc only for type checking with the --noEmit option, and/or emit only declaration files with the --emitDeclarationOnly option).
If you were using a static signalProperties property to define which properties should be signalified, you can either switch to using decorators, or switch to passing in property names to signalify().
If modifying your build is too difficult, you can use signalify() instead of decorators (see the README).
classy-solid provides features for using Solid reactivity with JavaScript classes.
Features:
@reactive class decorator for annotating classes that have reactive features powered by Solid
@signal decorator for properties that should be backed by a Solid signal
createSignalObject() - Returns a Solid signal in the form of an object with .get and .set methods, instead of an array tuple.
createSignalFunction() - Returns a Solid signal in the form of a single overloaded function for both getting and setting the signal, instead of an array tuple. Call the function with no arguments to get the signal value, and call it with an arg to set the signal value.
Use signalify() to convert properties on an object into Solid signal-backed properties.
More to come, including decorators for using classes as Solid components, making memo properties, effect methods, etc.
?? Papanasi is a UI library to use cross Frameworks. A set of components to use in Angular, React, Solid, Svelte, Vue and Web Components. Is based on the Mitosis library and documented using Storybook.