Solid.js reactivity patterns for classes, and class components.

Overview

classy-solid

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'

@reactive
class Car {
	@signal engineOn = false
	@signal sound = 'vroom'
}

const car = new Car()

// ...

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
@reactive
class MyComp {
	@signal last = 'none'

	onMount() {
		console.log('mounted')
	}

	template(props) {
		// here we use `props` passed in, or the signal on `this` which is also
		// treated as a prop
		return (
			<h1>
				Hello, my name is {props.first} {this.last}
			</h1>
		)
	}
}

render(() => <MyComp first="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'

const MyComp = component(
	reactive(
		class MyComp {
			static signalProperties = ['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(() => <MyComp first="Joe" last="Pea" />, document.body)

TypeScript

, document.body)">
import {component, reactive, signal, Props} from 'classy-solid'

@component
@reactive
class MyComp {
	// 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'>

	@signal last = 'none'
	@signal first = 'none'
	@signal count = 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(() => <MyComp first="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.

let count = createSignalObject(0) // count starts at 0
count.set(1) // set the value of count to 1
count.set(count.get() + 1) // add 1
let currentValue = 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:

class Counter {
	count = createSignal(0)

	increment() {
		// These are not so readable:
		this.count[1](this.count[0]() + 1)
		// or
		this.count[1](c => c + 1)
	}
}

createSignalObject provides an alternative that is more usable as a class property:

class Counter {
	count = createSignalObject(0)

	increment() {
		// These are more readable:
		this.count.set(this.count.get() + 1)
		// or
		this.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.

let count = createSignalFunction(0) // count starts at 0
count(1) // set the value of count to 1
count(count() + 1) // add 1
let currentValue = 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:

class Counter {
	count = createSignal(0)

	increment() {
		// These are not so readable:
		this.count[1](this.count[0]() + 1)
		// or
		this.count[1](c => c + 1)
	}
}

createSignalFunction provides an alternative that is more usable as a class property:

class Counter {
	count = createSignalFunction(0)

	increment() {
		// These are more readable:
		this.count(this.count() + 1)
		// or
		this.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'

const obj = {
	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:

const obj = 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 signals
const obj = signalify({
	foo: 1,
	bar: 2,
	baz: 3,
})

Note that the object passed in is the same object returned:

let test
const obj = signalify(test = {...})
console.log(obj === test) // true

Signalify certain properties in a class (alternative to decorators):

import {signalify} from 'class-solid'
import {createEffect} from 'solid-js'

class Counter {
	count = 0
	on = true

	constructor() {
		// Make only the 'count' variable reactive (signal-backed). The 'on'
		// variable remains a regular property.
		signalify(this, ['count'])
	}
}

const c = new Counter()

// ...

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):

class Counter {
	count = 0
	on = true

	constructor() {
		// 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:

class Counter {
	count = 0
	on = true

	static signalProperties = ['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:

@reactive
class Counter {
	@signal count = 0
	on = true
}
You might also like...
Use vitejs, storybook and typescript to build great react components and documents.

react-component-storybook Use vitejs, storybook and typescript to build great react components. Just click Use this template button or use gh cli gh r

Black Dashboard React is a beautiful Bootstrap 4, Reacstrap and React (create-react-app) Admin Dashboard with a huge number of components built to fit together and look amazing.
Black Dashboard React is a beautiful Bootstrap 4, Reacstrap and React (create-react-app) Admin Dashboard with a huge number of components built to fit together and look amazing.

Black Dashboard React Black Dashboard React is a beautiful Bootstrap 4, Reacstrap and React (create-react-app) Admin Dashboard with a huge number of c

Tail-kit is a free and open source components and templates kit fully coded with Tailwind css 2.0.
Tail-kit is a free and open source components and templates kit fully coded with Tailwind css 2.0.

Tail-Kit A beautiful and large components and templates kit for TailwindCSS 2.0 Tail-Kit is Free and Open Source. It does not change or add any CSS to

A small and incomplete clone of the latest version of Window. Built using react typescript, styled components and zustand for state management
A small and incomplete clone of the latest version of Window. Built using react typescript, styled components and zustand for state management

Windows 11 Browser Clone A small and incomplete clone of the latest version of Window. The project was created using React's typescript template, zust

React Template using TS, Best Practices of Context API, Testing Library and Jest, Axios, React-router-dom and Styled-Components

React Template using TS, Best Practices of Context API, Testing Library and Jest, Axios, React-router-dom and Styled-Components

React Blur Admin - Styles And Components

React Blur Admin - Styles And Components

Free React Admin Dashboard made with Material UI components and React.
Free React Admin Dashboard made with Material UI components and React.

Minimal UI (Free version) Free React Admin Dashboard made with Material UI components and React. Minimal Kit FREE Minimal Kit PRO 7 Demo Pages 40 demo

This is a very simple boilerplate made with CRA, React Router, Styled Components and Storybook.

This is a Create React App boilerplate with some configurations from React Avançado Course. What is inside? This project uses lot of stuff as: Create

React Dashboard made with Material UI’s components. Our pro template contains features like TypeScript version, authentication system with Firebase and Auth0 plus many other

Material Kit - React Free React Admin Dashboard made with Material UI's components, React and of course create-react-app to boost your app development

Comments
  • Classy violates Contnent Security Policy

    Classy violates Contnent Security Policy

    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?

    opened by GodBleak 0
  • Document steps needed to use decorators with vite

    Document steps needed to use decorators with vite

    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:

    // ...
    solidPlugin({
         babel: {
              plugins: [
                   ["@babel/plugin-proposal-decorators", { legacy: true }],
                   "@babel/plugin-proposal-class-properties",
              ]
         },
    }),
    // ...
    

    I think it would be beneficial to add this or similar instructions to the readme.

    opened by GodBleak 1
  • Error When Using Component Decorator

    Error When Using Component Decorator

    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).
    Source code(tar.gz)
    Source code(zip)
  • v0.1.2(Apr 9, 2022)

  • v0.1.1(Apr 8, 2022)

    Features:

    • @component decorator for using classes as Solid components, for example in JSX or html templates.

    Full Changelog: https://github.com/lume/classy-solid/compare/v0.1.0...v0.1.1

    Source code(tar.gz)
    Source code(zip)
  • v0.1.0(Apr 8, 2022)

    Solid is getting classy!

    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.

    Source code(tar.gz)
    Source code(zip)
Owner
LUME
A web 3D toolkit for making interactive computing experiences.
LUME
🥯Papanasi is the Frontend UI library to use cross Frameworks. A set of components to use in Angular, React, Solid, Svelte, Vue and Web Components

?? 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.

Quique Fdez Guerra 387 Jan 4, 2023
TypeScript basics, Compiler & Configuration, working with Next-gen JS Code, Classes & Interfaces;

TypeScript basics, Compiler & Configuration, working with Next-gen JS Code, Classes & Interfaces; Advanced Types & TypeScript Features, Generics, Decorators; Full Project culminating this learning adventure; Working with Namespaces & Modules, Webpack & TypeScript, Third-Party Libraries & TypeScript.; React + TypeScript, NodeJS + TypeScript

Debora Schuch da Rosa 0 Dec 27, 2021
Create reusable components with Alpine JS reactivity 🦑

Apline JS Component Alpine JS plugin x-component allows you to create reusable components, sprinkled with Alpine JS reactive data ?? Example ?? Page W

Mark Mead 66 Jan 5, 2023
Solidjs-meteor-data - Helpers for combining SolidJS and Meteor reactivity

solidjs-meteor-data This package provides helper functions for combining the rea

Erik Demaine 13 Nov 11, 2022
A library of high-quality primitives that extend SolidJS reactivity.

Solid Primitives A project that strives to develop high-quality, community contributed Solid primitives. All utilities are well tested and continuousl

SolidJS Community 571 Jan 1, 2023
Vite-solid-electron - A simple Vite, SolidJS, Electron integration template

vite-solid-electron Overview Very simple Vite, SolidJS, Electron integration tem

Christian Hansen 25 Dec 18, 2022
react redux for CMS/Enterprise class App/ERP/Admin with ant-design

Feature List hot reloading/browser-sync/redux devtools on dev build minify/chunkhash/trackJS on production build eslint both of terminal and pre-commi

Justin-lu 410 Oct 18, 2022
Atividades da Short Class Web React do Let's Code Pass

Short_Class_Web_React Atividades da Short Class Web React do Let's Code Pass Iniciar repostiorio git git init Adicionar os arquivos para stagin git ad

Otto 1 Jan 10, 2022
Starter template for Vite with React (TypeScript). Supports Tailwind with CSS-Modules. Jest and @react/testing-library configured and ready to go. Also ESLint, Prettier, Husky, Commit-lint and Atomic Design for components.

Mocking up web app with Vital(speed) Live Demo Features ⚡️ React 17 ?? TypeScript, of course ?? Jest - unitary testing made easy ?? Tailwind with JIT

Josep Vidal 141 Dec 29, 2022
A monorepo boilerplate for react web and react native. Uses react-native-web to reduce time invested in making the same components for mobile and web

Monorepo template Get Started Run the following command to install all the dependencies in the right location yarn lerna bootstrap yarn workspace @mar

Amaan Kulshreshtha 5 Dec 12, 2022