Rachel Cole

Rachel Cole

1677556374

Signals for Solid, Qwik and React

Signals for Solid, Qwik And React

Signals are the new hotness on Tech Twitter, but are they for real or just hype. Let's find out as we figure out what signals are, and how to us them in Solid, Qwik and React.

0:00 Introduction
1:31 Example Application
2:13 Solid Implementation
7:46 Computed State
10:16 Qwik Implementation
14:28 Qwik take on Qwik
15:04 What is Fine Grained Updating?
17:52 Hacking "Fine Grained" Into React
20:47 Preact Signals For React
24:30 Jotai Signals
27:34 Outroduction

Code: https://github.com/jherr/signals-video-code

#react #solid #qwik #signals 

Signals for Solid, Qwik and React
Felix Kling

Felix Kling

1676623546

The Complete Guide to SOLID Design Principles

In this tutorial, I’ll show you what the SOLID principles entail, what each part of the SOLID acronym means, and how to implement them in your code. What are SOLID Design Principles? SOLID is a set of five design principles: The Single Responsibility Principle (SRP), The Open-Closed Principle (OCP), The Liskov Substitution Principle (LSP), The Interface Segregation Principle (ISP), The Dependency Inversion Principle (DIP). 

SOLID is a set of five design principles. These principles help software developers design robust, testable, extensible, and maintainable object-oriented software systems.

Each of these five design principles solves a particular problem that might arise while developing the software systems.

In this article, I’ll show you what the SOLID principles entail, what each part of the SOLID acronym means, and how to implement them in your code.

What We'll Cover

  • What are SOLID Design Principles?
    • The Single Responsibility Principle (SRP)
    • The Open-Closed Principle (OCP)
    • The Liskov Substitution Principle (LSP)
    • The Interface Segregation Principle (ISP)
    • The Dependency Inversion Principle (DIP)
  • Conclusion

What are SOLID Design Principles?

SOLID is an acronym that stands for:

  • Single Responsibility Principle (SRP)
  • Open-Closed Principle (OCP)
  • Liskov Substitution Principle (LSP)
  • Interface Segregation Principle (ISP)
  • Dependency Inversion Principle (DIP)

In the coming sections, we’ll look at what each of those principles means in detail.

The SOLID design principle is a subcategory of many principles introduced by the American scientist and instructor, Robert C. Martin (A.K.A Uncle Bob) in a 2000 paper.

Following these principles can result in a very large codebase for a software system. But in the long run, the main aim of the principles is never defeated. That is, helping software developers make changes to their code without causing any major issues.

The Single Responsibility Principle (SRP)

The single responsibility principle states that a class, module, or function should have only one reason to change, meaning it should do one thing.

For example, a class that shows the name of an animal should not be the same class that displays the kind of sound it makes and how it feeds.

Here’s an example in JavaScript:

class Animal {
  constructor(name, feedingType, soundMade) {
    this.name = name;
    this.feedingType = feedingType;
    this.soundMade = soundMade;
  }

  nomenclature() {
    console.log(`The name of the animal is ${this.name}`);
  }

  sound() {
    console.log(`${this.name} ${this.soundMade}s`);
  }

  feeding() {
    console.log(`${this.name} is a ${this.feedingType}`);
  }
}

let elephant = new Animal('Elephant', 'herbivore', 'trumpet');
elephant.nomenclature(); // The name of the animal is Elephant
elephant.sound(); // Elephant trumpets
elephant.feeding(); // Elephant is a herbivore

The code above violates the single responsibility principle because the class that's responsible for printing the name of the animal also shows the sound it makes and its type of feeding.

To fix this, you have to create a separate class for the sound and feeding methods like this:

class Animal {
  constructor(name) {
    this.name = name;
  }

  nomenclature() {
    console.log(`The name of the animal is ${this.name}`);
  }
}

let animal1 = new Animal('Elephant');
animal1.nomenclature(); // The name of the animal is Elephant

// Sound class
class Sound {
  constructor(name, soundMade) {
    this.name = name;
    this.soundMade = soundMade;
  }

  sound() {
    console.log(`${this.name} ${this.soundMade}s`);
  }
}

let animalSound1 = new Sound('Elephant', 'trumpet');
animalSound1.sound(); //Elephant trumpets

// Feeding class
class Feeding {
  constructor(name, feedingType) {
    this.name = name;
    this.feedingType = feedingType;
  }

  feeding() {
    console.log(`${this.name} is a/an ${this.feedingType}`);
  }
}

let animalFeeding1 = new Feeding('Elephant', 'herbivore');
animalFeeding1.feeding(); // Elephant is a/an herbivore

This way, each of the classes is doing only one thing:

  • the first one prints the name of the animal
  • the second prints the kind of sound it makes
  • and the third one prints its kind of feeding.

That’s more code, but better readability and maintainability. A developer who didn’t write the code can come to it and understand what’s going on quicker than having it all in one class.

The Open-Closed Principle (OCP)

The open-closed principle states that classes, modules, and functions should be open for extension but closed for modification.

This principle might seem to contradict itself, but you can still make sense of it in code. It means you should be able to extend the functionality of a class, module, or function by adding more code without modifying the existing code.

Here’s some code that violates the open-closed principle in JavaScript:

class Animal {
  constructor(name, age, type) {
    this.name = name;
    this.age = age;
    this.type = type;
  }

  getSpeed() {
    switch (this.type) {
      case 'cheetah':
        console.log('Cheetah runs up to 130mph ');
        break;
      case 'lion':
        console.log('Lion runs up to 80mph');
        break;
      case 'elephant':
        console.log('Elephant runs up to 40mph');
        break;
      default:
        throw new Error(`Unsupported animal type: ${this.type}`);
    }
  }
}

const animal1 = new Animal('Lion', 4, 'lion');
animal1.getSpeed(); // Lion runs up to 80mph

The code above violates the open-closed principle because if you want to add a new animal type, you have to modify the existing code by adding another case to the switch statement.

Normally, if you’re using a switch statement, then it’s very likely you will violate the open-closed principle.

Here’s how I refactored the code to fix the problem:

class Animal {
  constructor(name, age, speedRate) {
    this.name = name;
    this.age = age;
    this.speedRate = speedRate;
  }

  getSpeed() {
    return this.speedRate.getSpeed();
  }
}

class SpeedRate {
  getSpeed() {}
}

class CheetahSpeedRate extends SpeedRate {
  getSpeed() {
    return 130;
  }
}

class LionSpeedRate extends SpeedRate {
  getSpeed() {
    return 80;
  }
}

class ElephantSpeedRate extends SpeedRate {
  getSpeed() {
    return 40;
  }
}

const cheetah = new Animal('Cheetah', 4, new CheetahSpeedRate());
console.log(`${cheetah.name} runs up to ${cheetah.getSpeed()} mph`); // Cheetah runs up to 130 mph

const lion = new Animal('Lion', 5, new LionSpeedRate());
console.log(`${lion.name} runs up to ${lion.getSpeed()} mph`); // Lion runs up to 80 mph

const elephant = new Animal('Elephant', 10, new ElephantSpeedRate());
console.log(`${elephant.name} runs up to ${elephant.getSpeed()} mph`); // Elephant runs up to 40 mph

This way, if you want to add a new animal type, you can create a new class that extends SpeedRate and pass it to the Animal constructor without modifying the existing code.

For example, I added a new GoatSpeedRate class like this:

class GoatSpeedRate extends SpeedRate {
  getSpeed() {
    return 35;
  }
}

// Goat
const goat = new Animal('Goat', 5, new GoatSpeedRate());
console.log(`${goat.name} runs up to ${goat.getSpeed()} mph`); // Goat runs up to 354 mph

This conforms to the open-closed principle.

The Liskov Substitution Principle (LSP)

The Liskov substitution principle is one of the most important principles to adhere to in object-oriented programming (OOP). It was introduced by the computer scientist Barbara Liskov in 1987 in a paper she co-authored with Jeannette Wing.

The principle states that child classes or subclasses must be substitutable for their parent classes or super classes. In other words, the child class must be able to replace the parent class. This has the advantage of letting you know what to expect from your code.

Here’s an example of a code that does not violate the Liskov substitution principle:

class Animal {
  constructor(name) {
    this.name = name;
  }

  makeSound() {
    console.log(`${this.name} makes a sound`);
  }
}

class Dog extends Animal {
  makeSound() {
    console.log(`${this.name} barks`);
  }
}

class Cat extends Animal {
  makeSound() {
    console.log(`${this.name} meows`);
  }
}

function makeAnimalSound(animal) {
  animal.makeSound();
}

const cheetah = new Animal('Cheetah');
makeAnimalSound(cheetah); // Cheetah makes a sound

const dog = new Dog('Jack');
makeAnimalSound(dog); // Jack barks

const cat = new Cat('Khloe');
makeAnimalSound(cat); // Khloe meows

The Dog and Cat classes can successfully replace the parent Animal class.

On the other hand, let’s look at how the code below does violate the Liskov substitution principle:

class Bird extends Animal {
  fly() {
    console.log(`${this.name} flaps wings`);
  }
}

const parrot = new Bird('Titi the Parrot');
makeAnimalSound(parrot); // Titi the Parrot makes a sound
parrot.fly(); // Titi the Parrot flaps wings

The Bird class violates the Liskov substitution principle because it’s not implementing its own makeSound from the parent Animal class. Instead, it’s inheriting the generic sound.

To fix this, you have to make it use the makeSound method too:

class Bird extends Animal {
  makeSound() {
    console.log(`${this.name} chirps`);
  }

  fly() {
    console.log(`${this.name} flaps wings`);
  }
}

const parrot = new Bird('Titi the Parrot');
makeAnimalSound(parrot); // Titi the Parrot chirps
parrot.fly(); // Titi the Parrot flaps wings

The Interface Segregation Principle (ISP)

The interface segregation principle states that clients should not be forced to implement interfaces or methods they do not use.

More specifically, the ISP suggests that software developers should break down large interfaces into smaller, more specific ones, so that clients only need to depend on the interfaces that are relevant to them. This can make the codebase easier to maintain.

This principle is fairly similar to the single responsibility principle (SRP). But it’s not just about a single interface doing only one thing – it’s about breaking the whole codebase into multiple interfaces or components.

Think about this as the same thing you do while working with frontend frameworks and libraries like React, Svelte, and Vue. You usually break down the codebase into components you only bring in when needed.

This means you create individual components that have functionality specific to them. The component responsible for implementing scroll to the top, for example, will not be the one to switch between light and dark, and so on.

Here’s an example of code that violates the interface segregation principle:

class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} is eating`);
  }

  swim() {
    console.log(`${this.name} is swimming`);
  }

  fly() {
    console.log(`${this.name} is flying`);
  }
}

class Fish extends Animal {
  fly() {
    console.error("ERROR! Fishes can't fly");
  }
}

class Bird extends Animal {
  swim() {
    console.error("ERROR! Birds can't swim");
  }
}

const bird = new Bird('Titi the Parrot');
bird.swim(); // ERROR! Birds can't swim

const fish = new Fish('Neo the Dolphin');
fish.fly(); // ERROR! Fishes can't fly

The code above violates the interface segregation principle because the Fish class doesn’t need the fly method. A fish cannot fly. Birds can’t swim too, so the Bird class doesn’t need the swim method.

This is how I fixed the code to conform to the interface segregation principle:

class Animal {
  constructor(name) {
    this.name = name;
  }

  eat() {
    console.log(`${this.name} is eating`);
  }

  swim() {
    console.log(`${this.name} is swimming`);
  }

  fly() {
    console.log(`${this.name} is flying`);
  }
}

class Fish extends Animal {
  // This class needs the swim() method
}

class Bird extends Animal {
  //   THis class needs the fly() method
}

// Making them implement the methods they need
const bird = new Bird('Titi the Parrot');
bird.swim(); // Titi the Parrot is swimming

const fish = new Fish('Neo the Dolphin');
fish.fly(); // Neo the Dolphin is flying

console.log('\n');

// Both can also implement eat() method of the Super class because they both eat
bird.eat(); // Titi the Parrot is eating
fish.eat(); // Neo the Dolphin is eating

The Dependency Inversion Principle (DIP)

The dependency inversion principle is about decoupling software modules. That is, making them as separate from one another as possible.

The principle states that high-level modules should not depend on low-level modules. Instead, they should both depend on abstractions. Additionally, abstractions should not depend on details, but details should depend on abstractions.

In simpler terms, this means instead of writing code that relies on specific details of how lower-level code works, you should write code that depends on more general abstractions that can be implemented in different ways.

This makes it easier to change the lower-level code without having to change the higher-level code.

Here’s a code that violates the dependency inversion principle:

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  bark() {
    console.log('woof! woof!! woof!!');
  }
}

class Cat extends Animal {
  meow() {
    console.log('meooow!');
  }
}

function printAnimalNames(animals) {
  for (let i = 0; i < animals.length; i++) {
    const animal = animals[i];
    console.log(animal.name);
  }
}

const dog = new Dog('Jack');
const cat = new Cat('Zoey');

const animals = [dog, cat];

printAnimalNames(animals);

The code above violates dependency inversion principle because the printAnimalNames function depends on the concrete implementations of Dog and Cat.

If you wanted to add another animal like an ape, you’d have to modify the printAnimalNames function to handle it.

Here’s how to fix it:

class Animal {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class Dog extends Animal {
  bark() {
    console.log('woof! woof!! woof!!!');
  }
}

class Cat extends Animal {
  meow() {
    console.log('meooow!');
  }
}

function printAnimalNames(animals) {
  for (let i = 0; i < animals.length; i++) {
    const animal = animals[i];
    console.log(animal.getName());
  }
}

const dog = new Dog('Jack');
const cat = new Cat('Zoey');

const animals = [dog, cat, ape];

printAnimalNames(animals);

In the code above, I created a getName method inside the Animal class. This provides an abstraction that the printAnimalNames function can depend on. Now, the printAnimalNames function only depends on the Animal class, not the concrete implementations of Dog and Cat.

If you wan to add an Ape class, you can do so without modifying the printAnimalNames function:

class Animal {
  constructor(name) {
    this.name = name;
  }

  getName() {
    return this.name;
  }
}

class Dog extends Animal {
  bark() {
    console.log('woof! woof!! woof!!!');
  }
}

class Cat extends Animal {
  meow() {
    console.log('meooow!');
  }
}

// Add Ape class
class Ape extends Animal {
  meow() {
    console.log('woo! woo! woo!');
  }
}

function printAnimalNames(animals) {
  for (let i = 0; i < animals.length; i++) {
    const animal = animals[i];
    console.log(animal.getName());
  }
}

const dog = new Dog('Jack'); // Jack
const cat = new Cat('Zoey'); // Zoey

// Use the Ape class
const ape = new Ape('King Kong'); // King Kong

const animals = [dog, cat, ape];

printAnimalNames(animals);

Conclusion

In this article, you learned what the SOLID design priniples are. We discussed each principle with an example, and went through ways to implement them in JavaScript.

I hope the article gives you a solid grasp of the SOLID principles. You can see that the SOLID design principles can help you create a software system that is free of bugs, maintainable, flexible, scalable, and reusable.

Thanks for reading.

Original article source at https://www.freecodecamp.org

#solid 

The Complete Guide to SOLID Design Principles
Bongani  Ngema

Bongani Ngema

1675516620

Android Components Architecture in A Modular Word

Android Components Architecture in a Modular Word

Android Components Architecture in a Modular Word is a sample project that presents modern, 2020 approach to Android application development using Kotlin and latest tech-stack.

The goal of the project is to demonstrate best practices, provide a set of guidelines, and present modern Android application architecture that is modular, scalable, maintainable and testable. This application may look simple, but it has all of these small details that will set the rock-solid foundation of the larger app suitable for bigger teams and long application lifecycle management.

demo.gif

Mentions

The project received different mentions/reference from Android Developer Community:

  • Android Weekly Newsletter that helps you to stay cutting-edge with your Android Development.
  • AndroidSweets Fresh news from Droid zone.
  • Android Awesome A curated list of awesome Android libraries and resources.
  • Droidcon Blog posts from leading authorities and video archive from droidcons around the world.
  • DroidconKE The official DroidconKE 2020 conference App.
  • Github trending See what the GitHub community is most excited about today.
  • KotlinBy A curated list of awesome Kotlin related stuff.
  • Reddit: Discussion about Kotlin, a statically typed programming language for the JVM, Android, JavaScript, and native.

Development

Environment setup

First off, you require the latest Android Studio 4.1.0 (or newer) to be able to build the app.

You need to supply keys for Marvel API. You can find information about how to gain access by using the link.

When you obtain the keys, you can provide them to the app by putting the following in the local.properties project root file:

#Marvel API KEYS
marvel.key.public = <insert>
marvel.key.private = <insert>

Code style

To maintain the style and quality of the code, are used the bellow static analysis tools. All of them use properly configuration and you find them in the project root directory .{toolName}.

ToolsConfig fileCheck commandFix command
detekt/.detekt./gradlew detekt-
ktlint-./gradlew ktlint./gradlew ktlintFormat
spotless/.spotless./gradlew spotlessCheck./gradlew spotlessApply
lint/.lint./gradlew lint-

All these tools are integrated in pre-commit git hook, in order ensure that all static analysis and tests passes before you can commit your changes. To skip them for specific commit add this option at your git command:

git commit --no-verify

The pre-commit git hooks have exactly the same checks as CircleCI and are defined in this script. This step ensures that all commits comply with the established rules. However the continuous integration will ultimately be validated that the changes are correct.

Design

App support different screen sizes and the content has been adapted to fit for mobile devices and tablets. To do that, it has been created a flexible layout using one or more of the following concepts:

In terms of design has been followed recommendations android material design comprehensive guide for visual, motion, and interaction design across platforms and devices. Granting the project in this way a great user experience (UX) and user interface (UI). For more info about UX best practices visit link.

Moreover, has been implemented support for dark theme with the following benefits:

  • Can reduce power usage by a significant amount (depending on the device’s screen technology).
  • Improves visibility for users with low vision and those who are sensitive to bright light.
  • Makes it easier for anyone to use a device in a low-light environment.
ModeCharacters listCharacters favoriteCharacter detail
Light
Dark

Architecture

The architecture of the application is based, apply and strictly complies with each of the following 5 points:

Modules

Modules are collection of source files and build settings that allow you to divide a project into discrete units of functionality. In this case apart from dividing by functionality/responsibility, existing the following dependence between them:

The above graph shows the app modularisation:

  • :app depends on :core and indirectly depends on :features by dynamic-features.
  • :features modules depends on :commons, :core, :libraries and :app.
  • :core and :commons only depends for possible utils on :libraries.
  • :libraries don’t have any dependency.

App module

The :app module is an com.android.application, which is needed to create the app bundle. It is also responsible for initiating the dependency graph, play core and another project global libraries, differentiating especially between different app environments.

Core module

The :core module is an com.android.library for serving network requests or accessing to the database. Providing the data source for the many features that require it.

Features modules

The :features module are an com.android.dynamic-feature is essentially a gradle module which can be downloaded independently from the base application module. It can hold code and resources and include dependencies, just like any other gradle module.

features

Commons modules

The :commons modules are an com.android.library only contains code and resources which are shared between feature modules. Reusing this way resources, layouts, views, and components in the different features modules, without the need to duplicate code.

uiviews

Libraries modules

The :libraries modules are an com.android.library, basically contains different utilities that can be used by the different modules.

Architecture components

Ideally, ViewModels shouldn’t know anything about Android. This improves testability, leak safety and modularity. ViewModels have different scopes than activities or fragments. While a ViewModel is alive and running, an activity can be in any of its lifecycle states. Activities and fragments can be destroyed and created again while the ViewModel is unaware.

Passing a reference of the View (activity or fragment) to the ViewModel is a serious risk. Lets assume the ViewModel requests data from the network and the data comes back some time later. At that moment, the View reference might be destroyed or might be an old activity that is no longer visible, generating a memory leak and, possibly, a crash.

The communication between the different layers follow the above diagram using the reactive paradigm, observing changes on components without need of callbacks avoiding leaks and edge cases related with them.

Build variants

The application has different product flavours: Dev, QA, Prod. Each variant has a specific target environment and to make easier to distinguish them the app uses a specific icon colour for debug and release build variant with descriptive app name. In this case and given that it's a sample, all variants have the same Marvel API endpoint. But the idea is to have different environments target for Development and QA respectively, what doesn't affect the production environment. This is applicable to any tool, platform, service what is being used. For more information about build variant, check this link.

TypesDEVQAPROD
Debug
MarvelDEV

MarvelQA

Marvel
Release
MarvelDEV

MarvelQA

Marvel

Documentation

The documentation is generated following KDoc language (the equivalent of Java's JavaDoc) via documentation engine for Kotlin Dokka.

To consult it check this link or open the project /docs directory.

Tech-stack

This project takes advantage of many popular libraries, plugins and tools of the Android ecosystem. Most of the libraries are in the stable version, unless there is a good reason to use non-stable dependency.

Dependencies

  • Jetpack:
    • Android KTX - provide concise, idiomatic Kotlin to Jetpack and Android platform APIs.
    • AndroidX - major improvement to the original Android Support Library, which is no longer maintained.
    • Benchmark - handles warmup, measures your code performance, and outputs benchmarking results to the Android Studio console.
    • Data Binding - allows you to bind UI components in your layouts to data sources in your app using a declarative format rather than programmatically.
    • Lifecycle - perform actions in response to a change in the lifecycle status of another component, such as activities and fragments.
    • LiveData - lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services.
    • Navigation - helps you implement navigation, from simple button clicks to more complex patterns, such as app bars and the navigation drawer.
    • Paging - helps you load and display small chunks of data at a time. Loading partial data on demand reduces usage of network bandwidth and system resources.
    • Room - persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.
    • ViewModel - designed to store and manage UI-related data in a lifecycle conscious way. The ViewModel class allows data to survive configuration changes such as screen rotations.
  • Coroutines - managing background threads with simplified code and reducing needs for callbacks.
  • Dagger2 - dependency injector for replacement all FactoryFactory classes.
  • Retrofit - type-safe HTTP client.
  • Coil - image loading library for Android backed by Kotlin Coroutines.
  • Moshi - makes it easy to parse JSON into Kotlin objects.
  • Timber - a logger with a small, extensible API which provides utility on top of Android's normal Log class.
  • Stetho - debug bridge for applications via Chrome Developer Tools.
  • and more...

Test dependencies

  • UIAutomator - a UI testing framework suitable for cross-app functional UI testing across system and installed apps.
  • Espresso - to write concise, beautiful, and reliable Android UI tests
  • Robolectric - industry-standard unit testing framework for Android.
  • JUnit - a simple framework to write repeatable tests. It is an instance of the xUnit architecture for unit testing frameworks.
  • Mockk - provides DSL to mock behavior. Built from zero to fit Kotlin language.
  • AndroidX - the androidx test library provides an extensive framework for testing Android apps.
  • and more...

Plugins

  • Ktlint - an anti-bikeshedding Kotlin linter with built-in formatter.
  • Detekt - a static code analysis tool for the Kotlin programming language.
  • Spotless - a code formatter can do more than just find formatting errors.
  • Versions - make easy to determine which dependencies have updates.
  • SafeArgs - generates simple object and builder classes for type-safe navigation and access to any associated arguments.
  • Jacoco - code coverage library
  • and more...

Resources

Projects

This is project is a sample, to inspire you and should handle most of the common cases, but obviously not all. If you need to take a look at additional resources to find solutions for your project, visit these interesting projects:

Articles

A collection of very interesting articles related last android community tendencies and recommendations for start to take in consideration for your current/next project:

Libraries

The open-source community create and maintains tons of awesome libraries making your job more easy, giving the opportunity to use them in your developments. Here are a very important collection of them:

Best practices

Avoid reinventing the wheel by following these guidelines:

Codelabs

Google Developers Codelabs provide a guided, tutorial, hands-on coding experience. Most codelabs will step you through the process of building a small application, or adding a new feature to an existing application. They cover a wide range of android concepts to learn and practice:

Contributions

All contributions are welcome! Please feel free to post questions, recommendations, ideas, bugs by create new issue following the template or if you want create directly new pull request.

Download Details:

Author: vmadalin
Source Code: https://github.com/vmadalin/android-modular-architecture 
License: Apache-2.0 license

#kotlin #testing #solid #android 

Android Components Architecture in A Modular Word
Lawrence  Lesch

Lawrence Lesch

1672988400

Architecture: .NET 7, Angular 15, Clean Architecture, Clean Code

ARCHITECTURE

This project is an example of architecture using new technologies and best practices.

The goal is to learn and share knowledge and use it as reference for new projects.

PRINCIPLES and PATTERNS

  • Clean Architecture
  • Clean Code
  • SOLID Principles
  • KISS Principle
  • DRY Principle
  • Fail Fast Principle
  • Common Closure Principle
  • Common Reuse Principle
  • Acyclic Dependencies Principle
  • Mediator Pattern
  • Result Pattern
  • Folder-By-Feature Structure
  • Separation of Concerns

BENEFITS

  • Simple and evolutionary architecture.
  • Standardized and centralized flow for validation, log, security, return, etc.
  • Avoid cyclical references.
  • Avoid unnecessary dependency injection.
  • Segregation by feature instead of technical type.
  • Single responsibility for each request and response.
  • Simplicity of unit testing.

TECHNOLOGIES

RUN

Command Line

Prerequisites

Steps

  1. Open directory source\Web\Frontend in command line and execute npm i.
  2. Open directory source\Web in command line and execute dotnet run.
  3. Open https://localhost:8090.

Visual Studio Code

Prerequisites

Steps

  1. Open directory source\Web\Frontend in command line and execute npm i.
  2. Open source directory in Visual Studio Code.
  3. Press F5.

Visual Studio

Prerequisites

Steps

  1. Open directory source\Web\Frontend in command line and execute npm i.
  2. Open source\Architecture.sln in Visual Studio.
  3. Set Architecture.Web as startup project.
  4. Press F5.

Docker

Prerequisites

Steps

  1. Execute docker compose up --build -d in root directory.
  2. Open http://localhost:8090.

PACKAGES

Source: https://github.com/rafaelfgx/DotNetCore

Published: https://www.nuget.org/profiles/rafaelfgx

LAYERS

Web: Frontend and Backend.

Application: Flow control.

Domain: Business rules and domain logic.

Model: Data transfer objects.

Database: Data persistence.

WEB

FRONTEND

Service

It is the interface between frontend and backend and has logic that does not belong in components.

export class AppCustomerService { }

Guard

It validates if a route can be activated.

export class AppGuard implements CanActivate { }

ErrorHandler

It provides a hook for centralized exception handling.

export class AppErrorHandler implements ErrorHandler { }

HttpInterceptor

It intercepts and handles an HttpRequest or HttpResponse.

export class AppHttpInterceptor implements HttpInterceptor { }

BACKEND

Controller

It has no any logic, business rules or dependencies other than mediator.

[ApiController]
[Route("customers")]
public sealed class CustomerController : BaseController
{
    [HttpPost]
    public IActionResult Add(AddCustomerRequest request) => Mediator.HandleAsync<AddCustomerRequest, AddCustomerResponse>(request).ApiResult();

    [HttpDelete("{id:long}")]
    public IActionResult Delete(long id) => Mediator.HandleAsync(new DeleteCustomerRequest(id)).ApiResult();

    [HttpGet("{id:long}")]
    public IActionResult Get(long id) => Mediator.HandleAsync<GetCustomerRequest, GetCustomerResponse>(new GetCustomerRequest(id)).ApiResult();

    [HttpGet("grid")]
    public IActionResult Grid([FromQuery] GridCustomerRequest request) => Mediator.HandleAsync<GridCustomerRequest, GridCustomerResponse>(request).ApiResult();

    [HttpGet]
    public IActionResult List() => Mediator.HandleAsync<ListCustomerRequest, ListCustomerResponse>(new ListCustomerRequest()).ApiResult();

    [HttpPut("{id:long}")]
    public IActionResult Update(UpdateCustomerRequest request) => Mediator.HandleAsync(request).ApiResult();
}

APPLICATION

It has only business flow, not business rules.

Request

It has properties representing the request.

public sealed record AddCustomerRequest;

Request Validator

It has rules for validating the request.

public sealed class AddCustomerRequestValidator : AbstractValidator<AddCustomerRequest> { }

Response

It has properties representing the response.

public sealed record AddCustomerResponse;

Handler

It is responsible for the business flow and processing a request to return a response.

It call factories, repositories, unit of work, services or mediator, but it has no business rules.

public sealed record AddCustomerHandler : IHandler<AddCustomerRequest, AddCustomerResponse>
{
    public async Task<Result<AddCustomerResponse>> HandleAsync(AddCustomerRequest request)
    {
        return Result<AddCustomerResponse>.Success(new AddCustomerResponse());
    }
}

Factory

It creates a complex object.

Any change to object affects compile time rather than runtime.

public interface ICustomerFactory { }
public sealed class CustomerFactory : ICustomerFactory { }

DOMAIN

It has no any references to any layer.

It has aggregates, entities, value objects and services.

Aggregate

It defines a consistency boundary around one or more entities.

The purpose is to model transactional invariants.

One entity in an aggregate is the root, any other entities in the aggregate are children of the root.

public sealed class CustomerAggregate { }

Entity

It has unique identity. Identity may span multiple bounded contexts and may endure beyond the lifetime.

Changing properties is only allowed through internal business methods in the entity, not through direct access to the properties.

public sealed class Customer : Entity { }

Value Object

It has no identity and it is immutable.

It is defined only by the values ​​of its properties.

To update a value object, you must create a new instance to replace the old one.

It can have methods that encapsulate domain logic, but these methods must have no side effects on the state.

public sealed record ValueObject;

Services

It performs domain operations and business rules.

It is stateless and has no operations that are not a part of an entity or value object.

public interface ICustomerService { }
public sealed class CustomerService : ICustomerService { }

MODEL

It has properties to transport and return data.

public sealed record CustomerModel;

DATABASE

It encapsulates data persistence.

Context

It configures the connection and represents the database.

public sealed class Context : DbContext
{
    public Context(DbContextOptions options) : base(options) { }
}

Entity Configuration

It configures the entity and its properties in the database.

public sealed class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
    public void Configure(EntityTypeBuilder<Customer> builder) { }
}

Repository

It inherits from the generic repository and only implements specific methods.

public interface ICustomerRepository : IRepository<Customer> { }
public sealed class CustomerRepository : Repository<Customer>, ICustomerRepository
{
    public CustomerRepository(Context context) : base(context) { }
}

Download Details:

Author: rafaelfgx
Source Code: https://github.com/rafaelfgx/Architecture 
License: MIT license

#typescript #angular #csharp #solid #dotnet 

Architecture: .NET 7, Angular 15, Clean Architecture, Clean Code
Billy Chandler

Billy Chandler

1668414766

Understanding SOLID Principles in JavaScript with Examples

SOLID is that stands for the five design principles of Object-Oriented class design. In this tutorial, you'll learn what these principles stand for and how they work using JavaScript examples.

Robert C. Martin – also known as Uncle Bob – is a well-known American software engineer. He's the author of the bestselling books Clean Code and Clean Architecture.

In 2000, he wrote a paper titled Design Principles and Design Patterns where he introduced some principles to help developers write good code in object-oriented programming.

These principles are meant to make object-oriented designs more understandable, flexible, and maintainable. They make it easy to create readable and testable code that many developers can collaboratively work with anywhere and anytime. And they make you aware of the right way to write code 💪.

All the principles in the paper together form the SOLID acronym, which was later introduced by Michael Feathers.

SOLID is a mnemonic acronym that stands for the five design principles of Object-Oriented class design. These principles are:

  • S - Single-responsibility Principle
  • O - Open-closed Principle
  • L - Liskov Substitution Principle
  • I - Interface Segregation Principle
  • D - Dependency Inversion Principle

In this article, you will learn what these principles stand for and how they work using JavaScript examples. The examples should be fine even if you are not fully conversant with JavaScript, because they apply to other programming languages as well.

What is the Single-Responsibility Principle (SRP)?

The Single-responsibility Principle, or SRP, states that a class should only have one reason to change. This means that a class should have only one job and do one thing.

Let’s take a look at a proper example. You’ll always be tempted to put similar classes together – but unfortunately, this goes against the Single-responsibility principle. Why?

The ValidatePerson object below has three methods: two validation methods, (ValidateName() and ValidateAge()), and a Display() method.

class ValidatePerson {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    ValidateName(name) {
        if (name.length > 3) {
            return true;
        } else {
            return false;
        }
    }

    ValidateAge(age) {
        if (age > 18) {
            return true;
        } else {
            return false;
        }
    }

    Display() {
        if (this.ValidateName(this.name) && this.ValidateAge(this.age)) {
            console.log(`Name: ${this.name} and Age: ${this.age}`);
        } else {
            console.log('Invalid');
        }
    }
}

The Display() method goes against the SRP because the goal is that a class should have only one job and do one thing. The ValidatePerson class does two jobs – it validates the person’s name and age and then displays some information.

The way to avoid this problem is to separate code that supports different actions and jobs so that each class only performs one job and has one reason to change.

This means that the ValidatePerson class will only be responsible for validating a user, as seen below:

class ValidatePerson {
    constructor(name, age) {
        this.name = name;
        this.age = age;
    }

    ValidateName(name) {
        if (name.length > 3) {
            return true;
        } else {
            return false;
        }
    }

    ValidateAge(age) {
        if (age > 18) {
            return true;
        } else {
            return false;
        }
    }
}

While the new class DisplayPerson will now be responsible for displaying a person, as you can see in the code block below:

class DisplayPerson {
    constructor(name, age) {
        this.name = name;
        this.age = age;
        this.validate = new ValidatePerson(this.name, this.age);
    }

    Display() {
        if (
            this.validate.ValidateName(this.name) &&
            this.validate.ValidateAge(this.age)
        ) {
            console.log(`Name: ${this.name} and Age: ${this.age}`);
        } else {
            console.log('Invalid');
        }
    }
}

With this, you will have fulfilled the single-responsibility principle, meaning our classes now have just one reason to change. If you want to change the DisplayPerson class, it won’t affect the ValidatePerson class.

What is the Open-Closed Principle?

The open-closed principle can be confusing because it's a two-direction principle. According to Bertrand Meyer's definition on Wikipedia, the open-closed principle (OCP) states that software entities (classes, modules, functions, etc.) should be open for extension, but closed for modification.

This definition can be confusing, but an example and further clarification will help you understand.

There are two primary attributes in the OCP:

  • It is open for extension — This means you can extend what the module can do.
  • It is closed for modification — This means you cannot change the source code, even though you can extend the behavior of a module or entity.

OCP means that a class, module, function, and other entities can extend their behavior without modifying their source code. In other words, an entity should be extendable without modifying the entity itself. How?

For example, suppose you have an array of iceCreamFlavours, which contains a list of possible flavors. In the makeIceCream class, a make() method will check if a particular flavor exists and logs a message.

const iceCreamFlavors = ['chocolate', 'vanilla'];

class makeIceCream {
    constructor(flavor) {
        this.flavor = flavor;
    }

    make() {
        if (iceCreamFlavors.indexOf(this.flavor) > -1) {
            console.log('Great success. You now have ice cream.');
        } else {
            console.log('Epic fail. No ice cream for you.');
        }
    }
}

The code above fails the OCP principle. Why? Well, because the code above is not open to an extension, meaning for you to add new flavors, you would need to directly edit the iceCreamFlavors array. This means that the code is no longer closed to modification. Haha (that's a lot).

To fix this, you would need an extra class or entity to handle addition, so you no longer need to modify the code directly to make any extension.

const iceCreamFlavors = ['chocolate', 'vanilla'];

class makeIceCream {
    constructor(flavor) {
        this.flavor = flavor;
    }
    make() {
        if (iceCreamFlavors.indexOf(this.flavor) > -1) {
            console.log('Great success. You now have ice cream.');
        } else {
            console.log('Epic fail. No ice cream for you.');
        }
    }
}

class addIceCream {
    constructor(flavor) {
        this.flavor = flavor;
    }
    add() {
        iceCreamFlavors.push(this.flavor);
    }
}

Here, we've added a new class — addIceCream – to handle addition to the iceCreamFlavors array using the add() method. This means your code is closed to modification but open to an extension because you can add new flavors without directly affecting the array.

let addStrawberryFlavor = new addIceCream('strawberry');
addStrawberryFlavor.add();
makeStrawberryIceCream.make();

Also, notice that SRP is in place because you created a new class. 😊

What is the Liskov Substitution Principle?

In 1987, the Liskov Substitution Principle (LSP) was introduced by Barbara Liskov in her conference keynote “Data abstraction”. A few years later, she defined the principle like this:

“Let Φ(x) be a property provable about objects x of type T. Then Φ(y) should be true for objects y of type S where S is a subtype of T”.

To be honest, that definition is not what many software developers want to see 😂 — so let me break it down into an OOP-related definition.

The principle defines that in an inheritance, objects of a superclass (or parent class) should be substitutable with objects of its subclasses (or child class) without breaking the application or causing any error.

In very plain terms, you want the objects of your subclasses to behave the same way as the objects of your superclass.

A very common example is the rectangle, square scenario. It’s clear that all squares are rectangles because they are quadrilaterals with all four angles being right angles. But not every rectangle is a square. To be a square, its sides must have the same length.

Bearing this in mind, suppose you have a rectangle class to calculate the area of a rectangle and perform other operations like set color:

class Rectangle {
    setWidth(width) {
        this.width = width;
    }

    setHeight(height) {
        this.height = height;
    }

    setColor(color) {
        // ...
    }

    getArea() {
        return this.width * this.height;
    }
}

Knowing fully well that all squares are rectangles, you can inherit the properties of the rectangle. Since the width and height has to be the same, then you can adjust it:

class Square extends Rectangle {
    setWidth(width) {
        this.width = width;
        this.height = width;
    }
    setHeight(height) {
        this.width = height;
        this.height = height;
    }
}

Taking a look at the example, it should work properly:

let rectangle = new Rectangle();
rectangle.setWidth(10);
rectangle.setHeight(5);
console.log(rectangle.getArea()); // 50

In the above, you will notice that a rectangle is created, and the width and height are set. Then you can calculate the correct area.

But according to the LSP, you want the objects of your subclasses to behave the same way as the objects of your superclass. Meaning if you replace the Rectangle with Square, everything should still work well:

let square = new Square();
square.setWidth(10);
square.setHeight(5);

You should get 100, because the setWidth(10) is supposed to set both the width and height to 10. But because of the setHeight(5), this will return 25.

let square = new Square();
square.setWidth(10);
square.setHeight(5);
console.log(square.getArea()); // 25

This breaks the LSP. To fix this, there should be a general class for all shapes that will hold all generic methods that you want the objects of your subclasses to have access to. Then for individual methods, you create an individual class for rectangle and square.

class Shape {
    setColor(color) {
        this.color = color;
    }
    getColor() {
        return this.color;
    }
}

class Rectangle extends Shape {
    setWidth(width) {
        this.width = width;
    }
    setHeight(height) {
        this.height = height;
    }
    getArea() {
        return this.width * this.height;
    }
}

class Square extends Shape {
    setSide(side) {
        this.side = side;
    }
    getArea() {
        return this.side * this.side;
    }
}

This way, you can set the color and get the color using either the super or subclasses:

// superclass
let shape = new Shape();
shape.setColor('red');
console.log(shape.getColor()); // red

// subclass
let rectangle = new Rectangle();
rectangle.setColor('red');
console.log(rectangle.getColor()); // red

// subclass
let square = new Square();
square.setColor('red');
console.log(square.getColor()); // red

What is the Interface Segregation Principle?

The Interface Segregation Principle (ISP) states that “a client should never be forced to implement an interface that it doesn’t use, or clients shouldn’t be forced to depend on methods they do not use”. What does this mean?

Just as the term segregation means — this is all about keeping things separated, meaning separating the interfaces.

Note: By default, JavaScript does not have interfaces, but this principle still applies. So let’s explore this as if the interface exists, so you will know how it works for other programming languages like Java.

A typical interface will contain methods and properties. When you implement this interface into any class, then the class needs to define all its methods. For example, suppose you have an interface that defines methods to draw specific shapes.

interface ShapeInterface {
    calculateArea();
    calculateVolume();
}

When any class implements this interface, all the methods must be defined even if you won't use them or if they don’t apply to that class.

class Square implements ShapeInterface {
    calculateArea(){
        //...
    }
    calculateVolume(){
        //...
    }  
}

class Cuboid implements ShapeInterface {
    calculateArea(){
        //...
    }
    calculateVolume(){
        //...
    }    
}

class Rectangle implements ShapeInterface {
    calculateArea(){
        //...
    }
    calculateVolume(){
        //...
    }   
}

Looking at the example above, you will notice that you cannot calculate the volume of a square or rectangle. Because the class implements the interface, you need to define all methods, even the ones you won’t use or need.

To fix this, you would need to segregate the interface.

interface ShapeInterface {
    calculateArea();
    calculateVolume();
}

interface ThreeDimensionalShapeInterface {
    calculateArea();
    calculateVolume();
}

You can now implement the specific interface that works with each class.

class Square implements ShapeInterface {
    calculateArea(){
        //...
    } 
}

class Cuboid implements ThreeDimensionalShapeInterface {
    calculateArea(){
        //...
    }
    calculateVolume(){
        //...
    }    
}

class Rectangle implements ShapeInterface {
    calculateArea(){
        //...
    }  
}

What is the Dependency Inversion Principle?

This principle is targeted towards loosely coupling software modules so that high-level modules (which provide complex logic) are easily reusable and unaffected by changes in low-level modules (which provide utility features).

According to Wikipedia, this principle states that:

  1. High-level modules should not import anything from low-level modules. Both should depend on abstractions (for example, interfaces).
  2. Abstractions should be independent of details. Details (concrete implementations) should depend on abstractions.

In plain terms, this principle states that your classes should depend upon interfaces or abstract classes instead of concrete classes and functions. This makes your classes open to extension, following the open-closed principle.

Let's look at an example. When building a store, you would want your store to make use of a payment gateway like stripe or any other preferred payment method. You might write your code tightly coupled to that API without thinking of the future.

But then what if you discover another payment gateway that offers far better service, let’s say PayPal? Then it becomes a struggle to switch from Stripe to Paypal, which should not be an issue in programming and software design.

class Store {
    constructor(user) {
        this.stripe = new Stripe(user);
    }

    purchaseBook(quantity, price) {
        this.stripe.makePayment(price * quantity);
    }

    purchaseCourse(quantity, price) {
        this.stripe.makePayment(price * quantity);
    }
}

class Stripe {
    constructor(user) {
        this.user = user;
    }

    makePayment(amountInDollars) {
        console.log(`${this.user} made payment of ${amountInDollars}`);
    }
}

Considering the example above, you'll notice that if you change the payment gateway, you won't just need to add the class – you'll also need to make changes to the Store class. This does not just go against the Dependency Inversion Principle but also against the open-closed principle.

To fix this, you must ensure that your classes depend upon interfaces or abstract classes instead of concrete classes and functions. For this example, this interface will contain all the behavior you want your API to have and doesn't depend on anything. It serves as an intermediary between the high-level and low-level modules.

class Store {
    constructor(paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    purchaseBook(quantity, price) {
        this.paymentProcessor.pay(quantity * price);
    }

    purchaseCourse(quantity, price) {
        this.paymentProcessor.pay(quantity * price);
    }
}

class StripePaymentProcessor {
    constructor(user) {
        this.stripe = new Stripe(user);
    }

    pay(amountInDollars) {
        this.stripe.makePayment(amountInDollars);
    }
}

class Stripe {
    constructor(user) {
        this.user = user;
    }

    makePayment(amountInDollars) {
        console.log(`${this.user} made payment of ${amountInDollars}`);
    }
}

let store = new Store(new StripePaymentProcessor('John Doe'));
store.purchaseBook(2, 10);
store.purchaseCourse(1, 15);

In the code above, you will notice that the StripePaymentProcessor class is an interface between the Store class and the Stripe class. In a situation where you need to make use of PayPal, all you have to do is create a PayPalPaymentProcessor which would work with the PayPal class, and everything will work without affecting the Store class.

class Store {
    constructor(paymentProcessor) {
        this.paymentProcessor = paymentProcessor;
    }

    purchaseBook(quantity, price) {
        this.paymentProcessor.pay(quantity * price);
    }

    purchaseCourse(quantity, price) {
        this.paymentProcessor.pay(quantity * price);
    }
}

class PayPalPaymentProcessor {
    constructor(user) {
        this.user = user;
        this.paypal = new PayPal();
    }

    pay(amountInDollars) {
        this.paypal.makePayment(this.user, amountInDollars);
    }
}

class PayPal {
    makePayment(user, amountInDollars) {
        console.log(`${user} made payment of ${amountInDollars}`);
    }
}

let store = new Store(new PayPalPaymentProcessor('John Doe'));
store.purchaseBook(2, 10);
store.purchaseCourse(1, 15);

You will also notice that this follows the Liskov Substitution Principle because you can replace it with other implementations of the same interface without breaking your application.

Ta-Da 😇

It's been an adventure🙃. I hope you noticed that each of these principles are related to the others in some way.

In an attempt to correct one principle, say the dependency inversion principle, you indirectly ensure that your classes are open to extension but closed to modification, for example.

You should keep these principles in mind when writing code, because they make it easier for many people to collaborate on your project. They simplify the process of extending, modifying, testing, and refactoring your code. So make sure you understand their definitions, what they do, and why you need them beyond OOP.

For more understanding, you can watch this video by Beau Carnes on the freeCodeCamp YouTube channel or read this article by Yiğit Kemal Erinç.

Have fun coding!

Original article source at https://www.freecodecamp.org

#solid #oop #javascript

Understanding SOLID Principles in JavaScript with Examples
Brooke  Giles

Brooke Giles

1665818058

SOLID Principles for Object-Oriented Programming (OOP)

In this tutorial, you'll learn about the SOLID principles for Object-Oriented Programming (OOP). 

After learning object-oriented programming I have seen people making very subtle mistakes because learning oop only teaches you about the concepts and not how to use it correctly. That's why this course brings you SOLID Principles which gives you the correct way to design your classes and isolate your code well.

What you’ll learn:

  •        SOLID Principles

Are there any course requirements or prerequisites?

  •        Basic Object Oriented Programming

Who this course is for:

  •        Beginners in software engineering.

#solid #oop #programming

SOLID Principles for Object-Oriented Programming (OOP)
Maitri Sharma

Maitri Sharma

1663321001

What is Interconversion of Matter Class 9 Notes by Physics Wallah

The phenomenon of change of matter form one state to another state and back to original state, by altering the conditions of temperature and pressure, is called the interconversion of matter.
https://www.pw.live/chapter-matter-is-our-surrounding-class-9/interconversion-of-states-of-matter


 #physics  #newton  #solid  #science  #class   #scientist #study #education 

What is Interconversion of Matter Class 9 Notes by Physics Wallah
Hong  Nhung

Hong Nhung

1661601629

6 Khung Công Tác Giao Diện Người Dùng Tốt Nhất để Sử Dụng Vào Năm 2022

Bài đăng này tiết lộ sáu thư viện giao diện người dùng hàng đầu để sử dụng vào năm 2022. Danh sách này mới và rất khác so với những năm trước.

Trạng thái của JS

Danh sách này là từ báo cáo State of JS , một cuộc khảo sát tổng hợp hàng năm về các nhà phát triển trong cộng đồng JavaScript để chia sẻ suy nghĩ của họ về việc sử dụng và sự hài lòng đối với các thư viện, khái niệm và ý kiến ​​xung quanh các công cụ và trong cộng đồng. Báo cáo năm 2021, được phát hành cách đây vài tuần, được hình dung từ việc khảo sát hơn 16.000 nhà phát triển từ khắp nơi trên thế giới.

Những người trả lời cho State of JS 2021 cho biết số năm kinh nghiệm của họ: <1 năm: 1,6%;  1 - 2 năm: 6/1%;  2-5 năm: 19,9%;  5 - 10 năm: 23,2%;  10 - 20 năm: 19%;  20 năm: 7%;  Không trả lời: 23,2%

Đây là những khuôn khổ JS hàng đầu cần chú ý trong năm nay được đánh giá bởi mức độ hài lòng khi sử dụng của nhà phát triển.

SolidJS

Tin nhắn vững chắc - Hiệu suất tập trung vào cả máy khách và máy chủ

SolidJS được cho là framework JS nhanh nhất và có kích thước nhỏ nhất tồn tại cho đến ngày nay. Nó quen thuộc nên bạn đã có ý tưởng về cách sử dụng nó nếu bạn đến từ nền tảng Knockout.js hoặc React Hooks. Nó cung cấp cho bạn quyền kiểm soát hoàn toàn đối với những gì được cập nhật và khi nào, ngay cả ở cấp liên kết DOM mà không có DOM ảo hoặc khác biệt.

Với hỗ trợ JSX và TypeScript, nó biên dịch một lần và sau đó cập nhật khi cần thiết theo cách nhanh nhất có thể. Nó cũng thực sự giàu tính năng với các tính năng tuyệt vời như Fragment, Portals, Context, Suspense, Error Boundaries, Lazy Components, Async & Concurrent Rendering, Implicit Trust, SSR & Hydration, Directives and Streaming.

Svelte

tiêu đề svelte - Ứng dụng web được tăng cường từ tính

Svelte.js là một khung JavaScript mã nguồn mở cũng siêu nhanh. Nó thực hiện biên dịch và phục vụ mã tại thời điểm xây dựng chứ không phải thời gian chạy như một số khung công tác khác. Điều này làm cho nó trở nên siêu nhanh, được cho là một trong những khuôn khổ nhanh nhất. Mã được biên dịch ở dạng bit nhỏ hơn và mô-đun JS, và điều này làm cho trình duyệt làm ít hơn và do đó tải nội dung nhanh hơn. Svelte được biết là không có DOM ảo (các thư viện rất nhanh áp dụng cách tiếp cận này), thực sự phản ứng và cho phép bạn viết ít mã hơn với tư cách là một nhà phát triển sử dụng nó.

Phản ứng

Tiêu đề React - Một thư viện JavaScript để xây dựng giao diện người dùng

React là một khuôn khổ JS được nhóm tại Facebook sử dụng để xây dựng giao diện người dùng web, đặc biệt là cho các ứng dụng trang đơn. React rất phản ứng như tên cho thấy — bạn có thể xây dựng các ứng dụng nhỏ đến rất lớn cập nhật DOM mà không cần tải lại trang. Nó được xây dựng để rất nhanh, dễ khai báo và dễ học.

React sử dụng DOM ảo, một cách hay để tạo các ứng dụng web nhanh hơn. Các thành phần cũng có thể dễ dàng được sử dụng lại và một thành phần có thể chứa nhiều thành phần nhỏ hơn trong đó. Việc tạo các ứng dụng động trên web bằng React thay vì Vanilla JS cũng dễ dàng hơn. React có một bộ công cụ giúp bạn thoải mái sử dụng, từ một cộng đồng mạnh mẽ đến các công cụ gỡ lỗi chuyên dụng và tiện ích mở rộng trong các IDE yêu thích của bạn.

View.js

Tiêu đề Vue - Khung JavaScript Tiến bộ

Vue.js là một khuôn khổ JS mã nguồn mở được xây dựng bởi Evan You, để xây dựng các giao diện người dùng tương tác. Nó sử dụng kiến ​​trúc model-view-viewmodel, về cơ bản đảm bảo rằng logic độc lập với chế độ xem để mọi thứ rõ ràng và dễ đọc hơn. Vue chú ý tốt đến lớp xem của ứng dụng, sau đó làm việc với các thư viện hỗ trợ cho các chức năng bổ sung khác để lại kích thước ánh sáng lõi.

Công cụ phát triển giao diện người dùng hàng đầu cho người mới bắt đầu vào năm 2022

Như bạn đã biết, Vue làm cho việc thử nghiệm đơn vị trở nên khá dễ dàng. Nó rất linh hoạt và tài liệu cũng là tài liệu dễ đọc nhất, theo ý kiến ​​của tôi, trong số tất cả các khung JS hiện có. Nó cũng rất dễ dàng để bắt đầu. Vue là một trong số ít framework rất phổ biến nhưng không được quản lý bởi bất kỳ tập đoàn lớn nào.

Alpine.js

Tiêu đề Alpine.js - khung JavaScript mới, nhẹ, của bạn

Alpine.js là một công cụ tối thiểu, chắc chắn để soạn thảo hành vi trực tiếp trong phần đánh dấu của bạn. Hãy nghĩ về nó giống như jQuery cho web hiện đại. Nhập một thẻ script và bắt đầu. nó thực sự tối thiểu — nó chỉ có một bộ sưu tập gồm 15 thuộc tính, 6 thuộc tính và 2 phương thức.

“Alpine.js cung cấp cho bạn bản chất phản ứng và khai báo của các khuôn khổ lớn như Vue hoặc React với chi phí thấp hơn nhiều. Bạn có thể giữ DOM của mình và thực hiện hành vi khi bạn thấy phù hợp. " - Caleb Porzio, tác giả dự án

Bạn nên dùng thử. Nó cho phép bạn viết JS bên trong HTML, nội tuyến, mà không cần bất kỳ bản dựng nào hoặc không cần cài đặt nhiều.

Lit

Tiêu đề Lit - Đơn giản.  Nhanh.  Thành phần Web.

Lit là một khuôn khổ JS được xây dựng dựa trên các tiêu chuẩn của các thành phần web, rất tương lai và phản ứng, đồng thời nó chứa các mẫu khai báo và một số tính năng chu đáo để giảm bớt phần soạn sẵn và giúp việc xây dựng các ứng dụng web dễ dàng hơn. Kích thước của tệp nén là 5kb, vì vậy bạn biết thời gian tải sẽ rất ngắn và cũng nhanh chóng. Lit không tải lại các trang mà tự động cập nhật các phần đã thay đổi của DOM — không có cây ảo nào được tạo lại, không có sự khác biệt.

Nó có sân chơi tương tác thực sự thú vị này để thử nghiệm các ý tưởng của bạn. Bạn có thể kiểm tra nó ngay hôm nay.

Kết thúc

Trong bài đăng hôm nay, chúng ta đã thấy sáu framework JavaScript hàng đầu để sử dụng trong năm nay, xếp hạng cao trong chỉ số hài lòng của hàng nghìn nhà phát triển JS khác. Các khuôn khổ đáng chú ý khác bao gồm Stimulus, Angular và Ember. Bạn mới nghe nói về cái nào trong số này? Hãy cho tôi biết ở phần bình luận.

Liên kết: https://www.telerik.com/blogs/top-6-frontend-frameworks-2022

#framework #lit #solid #alpine #react #vue

6 Khung Công Tác Giao Diện Người Dùng Tốt Nhất để Sử Dụng Vào Năm 2022
Thierry  Perret

Thierry Perret

1661594400

6 Meilleurs Frameworks Frontaux à Utiliser En 2022

Cet article révèle les six principales bibliothèques frontales à utiliser en 2022. La liste est fraîche et très différente des années précédentes.

État de JS

Cette liste est tirée du rapport State of JS , une enquête annuelle bien organisée auprès des développeurs de la communauté JavaScript pour partager leurs réflexions sur l'utilisation et la satisfaction des bibliothèques, des concepts et des opinions sur les outils et au sein de la communauté. Le rapport 2021, qui a été publié il y a quelques semaines, a été visualisé à partir d'une enquête auprès de plus de 16 000 développeurs du monde entier.

Les répondants au State of JS 2021 donnent leurs années d'expérience : <1 an : 1,6% ;  1-2 ans : 6/1 % ;  2-5 ans : 19,9 % ;  5-10 ans : 23,2 % ;  10-20 ans : 19 % ;  20 ans : 7 % ;  Pas de réponse : 23,2 %

Ce sont les meilleurs frameworks JS à surveiller cette année, classés par la satisfaction de l'utilisation des développeurs.

SolidJS

Messagerie solide - Performance axée à la fois sur le client et le serveur

SolidJS est sans doute le framework JS le plus rapide et le plus petit qui existe aujourd'hui. Il est familier, vous avez donc déjà une idée de la façon de l'utiliser si vous venez d'un arrière-plan Knockout.js ou React Hooks. Il vous offre un contrôle complet sur ce qui est mis à jour et quand, même au niveau de la liaison DOM sans DOM virtuel ni différence.

Avec la prise en charge de JSX et TypeScript, il compile une fois, puis effectue la mise à jour nécessaire de la manière la plus rapide possible. Il est également très riche en fonctionnalités avec des fonctionnalités telles que les fragments, les portails, le contexte, le suspense, les limites d'erreur, les composants paresseux, le rendu asynchrone et simultané, la délégation implicite, le SSR et l'hydratation, les directives et le streaming.

Svelte

en-tête svelte - Applications Web améliorées cybernétiquement

Svelte.js est un framework JavaScript open-source qui est également très rapide. Il effectue la compilation et sert le code au moment de la construction, par opposition à l'exécution comme certains autres frameworks. Cela le rend super rapide, sans doute l'un des frameworks les plus rapides. Le code compilé est en petits bits et modules JS, ce qui fait que le navigateur en fait moins et donc charge le contenu plus rapidement. Svelte est connu pour ne pas avoir de DOM virtuel (les bibliothèques très rapides adoptent cette approche), être vraiment réactif et vous permettre d'écrire moins de code en tant que développeur l'utilisant.

Réagir

React header - Une bibliothèque JavaScript pour créer des interfaces utilisateur

React est un framework JS de l'équipe de Facebook utilisé pour créer des interfaces utilisateur Web, en particulier pour les applications à page unique. React est très réactif comme son nom l'indique - vous pouvez créer des applications petites à très grandes qui mettent à jour le DOM sans rechargement de page. Il a été conçu pour être très rapide, déclaratif et facile à apprendre.

React utilise le DOM virtuel, un bon moyen de créer des applications Web plus rapides. Les composants peuvent également être facilement réutilisés et un composant peut contenir plusieurs composants plus petits. Il est également plus facile de créer des applications dynamiques sur le Web en utilisant React au lieu de Vanilla JS. React dispose d'une suite d'outils qui vous permettent de l'utiliser confortablement, d'une communauté robuste à des outils de débogage dédiés et des extensions dans vos IDE préférés.

Afficher.js

En-tête Vue - Le framework JavaScript progressif

Vue.js est un framework JS open source construit par Evan You, pour créer des interfaces utilisateur interactives. Il utilise l'architecture modèle-vue-vuemodèle, garantissant essentiellement que la logique est indépendante de la vue afin que les choses soient plus claires et lisibles. Vue accorde une grande attention à la couche d'affichage de l'application, puis travaille avec les bibliothèques de support pour d'autres fonctionnalités supplémentaires, laissant le noyau léger en taille.

Les meilleurs outils de développement frontend pour les débutants en 2022

Comme vous pouvez déjà le constater, Vue rend les tests unitaires assez faciles. Il est très flexible et les documents sont également les plus faciles à lire, à mon avis, parmi tous les frameworks JS. Il est également très facile de démarrer. Vue est l'un des rares frameworks qui est très populaire mais qui n'est géré par aucune grande entreprise.

Alpine.js

En-tête Alpine.js - votre nouveau framework JavaScript léger

Alpine.js est un outil robuste et minimal pour composer un comportement directement dans votre balisage. Pensez-y comme jQuery pour le Web moderne. Insérez une balise de script et lancez-vous. c'est vraiment minime - il n'a qu'une collection de 15 attributs, 6 propriétés et 2 méthodes.

« Alpine.js vous offre la nature réactive et déclarative des grands frameworks comme Vue ou React à un coût bien moindre. Vous pouvez garder votre DOM et saupoudrer de comportement comme bon vous semble. – Caleb Porzio, auteur du projet

Tu devrais l'essayer. Il vous permet d'écrire JS à l'intérieur du HTML, en ligne, sans aucune construction ni besoin de beaucoup d'installation.

Allumé

En-tête éclairé - Simple.  Vite.  Composants Web.

Lit est un framework JS construit sur des standards de composants Web, très futuriste et réactif, et il contient des modèles déclaratifs et une poignée de fonctionnalités réfléchies pour réduire le passe-partout et faciliter la création d'applications Web. La taille est de 5 Ko pour le fichier compressé, vous savez donc que le temps de chargement sera très court et qu'il est également rapide. Lit ne recharge pas les pages mais met à jour dynamiquement les parties modifiées du DOM - pas de reconstruction d'arborescence virtuelle, pas de différence.

Il a ce terrain de jeu interactif vraiment cool pour tester vos idées. Vous pouvez le vérifier aujourd'hui.

Emballer

Dans l'article d'aujourd'hui, nous avons vu les six meilleurs frameworks JavaScript à utiliser cette année qui se classent en tête de l'indice de satisfaction de milliers d'autres développeurs JS. D'autres frameworks notables incluent Stimulus, Angular et Ember. Laquelle de celles-ci entendez-vous parler pour la première fois ? Faites-moi savoir dans les commentaires.

Lien : https://www.telerik.com/blogs/top-6-frontend-frameworks-2022

#framework #lit #solid #alpine #react #vue

6 Meilleurs Frameworks Frontaux à Utiliser En 2022
Hans  Marvin

Hans Marvin

1661587140

6 Best Frontend Frameworks To Use in 2022

A front end framework is essentially a bundle of JavaScript code that someone else has written which you can include in your application to help you build it faster. It’s somewhat analogous to the framework for a house—which might include instructions on how to build windows, doors, or certain rooms. The house’s developer can then decide how to take the rules the framework gave them and build the house in the manner they desire.

Front end frameworks declare rules on how developers should structure their HTML, CSS, and JavaScript, to make the application easier to develop. In this manner, frameworks are opinionated—they prefer you to make certain decisions. Sure, you can choose to not listen to the framework and write the JavaScript however you want (unopinionated), but then you’re likely to get certain details wrong and run into issues that the frameworks were specifically developed to solve.

In today’s post, we have seen the top six JavaScript frameworks to use this year that rank high in the satisfaction index by thousands of other JS developers. Other notable frameworks include Stimulus, Angular and Ember. Which of these are you just hearing about for the first time? 

See more at: https://www.telerik.com/blogs/top-6-frontend-frameworks-2022

#framework #lit #solid #alpine #react #vue

6 Best Frontend Frameworks To Use in 2022
加藤  七夏

加藤 七夏

1661579775

2022 年使用的 6 個最佳前端框架

這篇文章揭示了 2022 年使用的前六個前端庫。這個列表是新鮮的,與往年有很大不同。

JS 狀態

此列表來自JS 狀態報告,該報告是對 JavaScript 社區開發人員的年度綜合調查,旨在分享他們對庫的使用和滿意度的看法,以及圍繞工具和社區內的概念和意見。幾週前發布的 2021 年報告是通過對來自世界各地的 16,000 多名開發人員的調查得出的。

JS 2021 年的受訪者給出了他們多年的經驗:<1 年:1.6%; 1-2年:6/1%; 2-5年:19.9%; 5-10年:23.2%; 10-20年:19%; 20年:7%; 沒有回答:23.2%

這些是今年開發人員使用滿意度最值得關注的 JS 框架。

SolidJS

可靠的消息傳遞 - 性能集中在客戶端和服務器上

SolidJS可以說是當今存在的最快和最小的 JS 框架。它很熟悉,因此如果您來自 Knockout.js 或 React Hooks 背景,您已經知道如何使用它。它使您可以完全控制更新的內容和時間,即使在沒有虛擬 DOM 或差異的 DOM 綁定級別也是如此。

借助 JSX 和 TypeScript 支持,它編譯一次,然後根據需要以最快的方式進行更新。它還具有非常豐富的功能,例如片段、門戶、上下文、懸念、錯誤邊界、惰性組件、異步和並發渲染、隱式委託、SSR 和水化、指令和流式傳輸。

苗條

svelte header - 控制論增強的網絡應用程序

Svelte.js是一個開源的 JavaScript 框架,速度也非常快。它在構建時進行編譯並提供代碼,而不是像其他一些框架那樣運行時。這使它超級快,可以說是最快的框架之一。編譯後的代碼是更小的位和 JS 模塊,這使得瀏覽器做的更少,因此加載內容更快。眾所周知,Svelte 沒有虛擬 DOM(非常快的庫採用這種方法),具有真正的反應性,並且允許您作為開發人員使用它編寫更少的代碼。

反應

React header - 一個用於構建用戶界面的 JavaScript 庫

React是 Facebook 團隊的一個 JS 框架,用於構建 Web 用戶界面,尤其是用於單頁應用程序。顧名思義,React 非常具有反應性——您可以構建從小型到大型的應用程序來更新 DOM 而無需重新加載頁面。它被構建為非常快速、聲明性和易於學習。

React 使用虛擬 DOM,這是創建更快的 Web 應用程序的好方法。組件也可以很容易地重複使用,一個組件中可以包含多個較小的組件。使用 React 而不是 Vanilla JS 在 Web 上創建動態應用程序也更容易。React 有一套工具讓你使用起來很舒服,從一個強大的社區到你最喜歡的 IDE 中的專用調試工具和擴展。

查看.js

Vue 標頭 - 漸進式 JavaScript 框架

Vue.js是由 Evan You 構建的開源 JS 框架,用於構建交互式用戶界面。它採用了model-view-viewmodel的架構,基本保證了邏輯獨立於視圖,讓事情更加清晰易讀。Vue 非常關注應用程序的視圖層,然後與支持庫一起工作以提供其他附加功能,從而保持核心的大小。

2022 年面向初學者的頂級前端開發人員工具

正如您已經知道的那樣,Vue 使單元測試變得非常容易。它非常靈活,而且在我看來,在所有 JS 框架中,文檔也是最容易閱讀的。它也非常容易上手。Vue 是少數幾個非常流行但不受任何大公司管理的框架之一。

Alpine.js

Alpine.js 標頭 - 您的新的輕量級 JavaScript 框架

Alpine.js是一個堅固的、最小的工具,用於直接在您的標記中編寫行為。把它想像成現代網絡的 jQuery。插入一個腳本標籤,然後開始吧。它真的很小——它只有 15 個屬性、6 個屬性和 2 個方法的集合。

“Alpine.js 以低得多的成本為您提供了 Vue 或 React 等大型框架的反應性和聲明性。你可以保留你的 DOM,並在你認為合適的時候加入一些行為。” – Caleb Porzio,項目作者

你應該試試看。它允許您在 HTML 中內聯編寫 JS,無需任何構建或大量安裝。

點亮

點亮標頭 - 簡單。 快速地。 網絡組件。

Lit是一個基於 Web 組件標準構建的 JS 框架,非常具有未來感和反應性,它包含聲明性模板和一些經過深思熟慮的功能,以減少樣板文件並使構建 Web 應用程序更容易。壓縮文件的大小為 5kb,因此您知道加載時間會很短,而且速度也很快。Lit 不會重新加載頁面,而是動態更新 DOM 的更改部分——沒有虛擬樹重建,沒有差異。

它有一個非常酷的交互式遊樂場來測試你的想法。你今天可以檢查一下。

包起來

在今天的帖子中,我們看到了今年使用的排名前六的 JavaScript 框架,它們在成千上萬的其他 JS 開發人員的滿意度指數中排名靠前。其他值得注意的框架包括 Stimulus、Angular 和 Ember。您是第一次聽說以下哪些?在評論中告訴我。

鏈接:https ://www.telerik.com/blogs/top-6-frontend-frameworks-2022

#framework #lit #solid #alpine #react #vue

2022 年使用的 6 個最佳前端框架

6 лучших интерфейсных фреймворков для использования в 2022 году

This post reveals the top six frontend libraries to use in 2022. The list is fresh and very different from the previous years.

State of JS

This list is from the State of JS report, an annual well-put-together survey of developers in the JavaScript community to share their thoughts around usage and satisfaction of libraries, concepts and opinions around tools and within the community. The 2021 report, which was released some weeks ago, was visualized from surveying over 16,000 developers from across the world.

Respondents to the State of JS 2021 give their years of experience: <1 year: 1.6%; 1-2 years: 6/1%; 2-5 years: 19.9%; 5-10 years: 23.2%; 10-20 years: 19%; 20 years: 7%; No answer: 23.2%

These are the top JS frameworks to look out for this year rated by developer usage satisfaction.

SolidJS

Solid messaging - Performance focused on both client and server

SolidJS is arguably the fastest and smallest sized JS framework that exists today. It is familiar so you already have an idea of how to use it if you are coming from a Knockout.js or a React Hooks background. It provides you complete control over what gets updated and when, even at the DOM binding level without Virtual DOMs or diffing.

With JSX and TypeScript support, it compiles once and then does updating as needed in the fastest possible way. It is also really feature-rich with great features like Fragments, Portals, Context, Suspense, Error Boundaries, Lazy Components, Async & Concurrent Rendering, Implicit Delegation, SSR & Hydration, Directives and Streaming.

Svelte

svelte header - Cybernetically enhanced web apps

Svelte.js is an open-source JavaScript framework that is also super fast. It does compilation and serves the code at build time as opposed to runtime like some other frameworks. This makes it super fast, arguably one of the fastest frameworks. The compiled code is in smaller bits and JS modules, and this makes the browser do less and so load content faster. Svelte is known to have no virtual DOM (very fast libraries adopt this approach), be really reactive and allow you to write less code as a developer using it.

React

React header - A JavaScript library for building user interfaces

React is a JS framework by the team at Facebook used to build web user interfaces, especially for single page applications. React is very reactive as the name suggests—you can build small to very large applications that updates the DOM without page reloads. It was built to be very fast, declarative and easy to learn.

React uses virtual DOM, a good way to create faster web apps. The components can also easily be reused, and a component can contain multiple smaller components in it. It is also easier to create dynamic applications on the web using React instead of Vanilla JS. React has a suite of tools that make you comfortable using it, from a robust community to dedicated debugging tools and extensions in your favorite IDEs.

Vue.js

Vue header - The Progressive JavaScript Framework

Vue.js is an open-source JS framework built by Evan You, for building interactive user interfaces. It uses the model-view-viewmodel architecture, basically ensuring that logic is independent of the view so that things are clearer and legible. Vue pays good attention to the view layer of the application, and then works with supporting libraries for other additional functionality leaving the core light in size.

Top Frontend Developer Tools for Beginners in 2022

As you can already tell, Vue makes unit testing pretty easy. It is very flexible, and the docs also are the easiest to read, in my opinion, among all JS frameworks out there. It is also super easy to get started with. Vue is one of the few frameworks that is very popular but not managed by any big corporations.

Alpine.js

Alpine.js header - your new, lightweight, JavaScript framework

Alpine.js is a rugged, minimal tool for composing behavior directly in your markup. Think of it like jQuery for the modern web. Plop in a script tag and get going. it is really minimal—it only has a collection of 15 attributes, 6 properties and 2 methods.

“Alpine.js offers you the reactive and declarative nature of big frameworks like Vue or React at a much lower cost. You get to keep your DOM, and sprinkle in behavior as you see fit.” – Caleb Porzio, project author

You should try it out. It lets you write JS inside the HTML, inline, without any builds or need for a lot of installation.

Lit

Lit header - Simple. Fast. Web Components.

Lit is a JS framework built on web components standards, very futuristic and reactive, and it contains declarative templates and a handful of thoughtful features to reduce boilerplate and make building web apps easier. The size is 5kb for the compressed file, so you know the load time will be very short, and it is also fast. Lit does not reload pages but dynamically updates the changed parts of the DOM—no virtual tree rebuilds, no diffing.

It has this really cool interactive playground to test out your ideas. You can check it out today.

Wrapping Up

In today’s post, we have seen the top six JavaScript frameworks to use this year that rank high in the satisfaction index by thousands of other JS developers. Other notable frameworks include Stimulus, Angular and Ember. Which of these are you just hearing about for the first time? Let me know in the comments.

Link: https://www.telerik.com/blogs/top-6-frontend-frameworks-2022

#framework #lit #solid #alpine #react #vue

6 лучших интерфейсных фреймворков для использования в 2022 году

React-like Solid.js Browser Router with Hassle-free Nesting / Dynamic

solid-js-router

React-like solid.js browser router with hassle-free nesting / dynamic routes

Motivation

Solid.js default solid-app-router package does not support convenient work with route nesting / dynamic routes. Docs says, it only supports <Outlet/> rendering, i.e.:

<Route path='/' element={<div>Onion starts here <Outlet /></div>}>
  <Route path='layer1' element={<div>Another layer <Outlet /></div>}>
    <Route path='layer2' element={<div>Innermost layer</div>}></Route>
  </Route>
</Route>

So, as you can see, it's not really convenient and component/reactive way. And I've decided to write a better lib, i.e.:

<Routes>
  <Route path={'/'}>
    <div>Onion starts here <Onion /></div>
  </Route>  
</Routes>

// for /*
const Onion = () => (
  <Routes>
    <Route path={'layer1'}>
      <div>Another layer <Onion2 /></div>
    </Route>  
  </Routes>
)
  
// for /layer1/*
const Onion2 = () => (
  <Routes>
    <Route path={'layer2'}>
      <div>Innermost layer</div>
    </Route>  
  </Routes>
)

For this example the original way looks better, but if we wanna be honest, the real life case is:

You kinda coding with solid-app-router:

const App = () => (
  <Router>
    <Route path={'/home'} element={<HomePage/>}/>
    <Route path={'/personal-account'} element={<PersonalAccountPage/>}/>
    <Route path={'/'} element={<Navigate href={'/home'}/>}/>
  </Router>
)

const HomePage = () => (
  <>
    <div>static HomePage info</div>
    <Link href={'/personal-account'}>Go to personal account</Link>
  </>
)

const PersonalAccountPage = () => {
  
  const [clicks, setClicks] = createSignal(0)
  
  const pages = createMemo(() => [
    {
      href: 'products',
      name: 'Products',
      component: ProductsComponent,
      props: {clicks: clicks()}
    },
    {
      href: 'billing',
      name: 'Billing',
      component: BillingComponent,
      props: {clicks: clicks()},
    },
  ])
  
  return (
    <>
      <div class={'nav left-part'}>
        <For each={pages()}>
          {page => (
            <Link href={page.href} class={'w-full'}>{page.name}</Link>
          )}
        </For>
        <button onClick={() => setClicks(clicks() + 1)}>Click me</button>
      </div>
      <div class={'container right-part'}>
        <For each={pages()}>
          {page => (
            <Route path={page.href} element={(<page.component {...page.props}/>)}/>
          )}
        </For>
      </div>
    </>
  )
}

And then the problems begin, "container right-part" routing just won't work, and you will read the docs and come to a conclusion:

You MUST define routes only in a static way.

And then rewrite everything with extremely shitty <Outlet/> strategy and lots of cross-app storages, if at all possible.

Conclusion

So, the only purpose of this library is to make routing workable and convenient.

Installation

npm i @gh0st-work/solid-js-router

Usage

On the same example:

import {Routes, Route, Link, Router, DefaultRoute} from '@gh0st-work/solid-js-router';

const App = () => (
  <Router>
    <Routes>
      <Route path={'/home'} fallback={true}>
        <HomePage/>
      </Route>
      <Route path={'/personal-account'}>
        <PersonalAccountPage/>  
      </Route>
      <DefaultRoute to={'/home'}/>
    </Routes>
  </Router>
)

const HomePage = () => (
  <>
    <div>static HomePage info</div>
    <Link href={'/personal-account'}>Go to personal account</Link>
  </>
)

const PersonalAccountPage = () => {
  
  const [clicks, setClicks] = createSignal(0)
  
  const pages = createMemo(() => [
    {
      href: 'products',
      name: 'Products',
      component: ProductsComponent,
      props: {clicks: clicks()}
    },
    {
      href: 'billing',
      name: 'Billing',
      component: BillingComponent,
      props: {clicks: clicks()},
    },
  ])
  
  return (
    <>
      <div class={'nav left-part'}>
        <For each={pages()}>
          {page => (
            <Link href={page.href} class={'w-full'}>{page.name}</Link>
          )}
        </For>
        <button onClick={() => setClicks(clicks() + 1)}>Click me</button>
      </div>
      <div class={'container right-part'}>
        <Routes>
          <For each={pages()}>
            {page => (
              <Route path={page.href}>
                <page.component {...page.props}/>
              </Route>
            )}
          </For>
          <DefaultRoute to={'products'}/>
        </Routes>
      </div>
    </>
  )
}

And it just works perfectly.

API

<Router>

Component for global routing management, use in only once, wrap your app in it.

Props:

  • history - history package createBrowserHistory() instance
  • children - default hidden prop, your elements

<Routes>

Component for defining your routes, just wrap them in it.

Props:

  • fallback - JSX element if no available route found.
    Not redirecting anywhere.
  • onRoute - function ({route, parentRoute}) => {} that will be called on every route change
  • children - default hidden prop, non-<Route> components will be ignored

<Route>

Just route component.

Props:

  • path - relative path of your route.
    Parsed via regexparam, so you can use matching. 
    Recommended starting from /, i.e. /personal-account -> /products.
  • fallback - boolean (true/false).
    If no available route found the first fallback={true} route will be used.
    Not redirecting anywhere.
  • children - default hidden prop, your elements

Matching supported, child must be function:

import {Routes, Route, Link, Router, DefaultRoute, Navigate} from '@gh0st-work/solid-js-router';

const App = () => (
  <Router>
    <Routes>
      <Route path={'/car/:id'}>
        {({id}) => <Car id={id}/>}
      </Route>
      <Route path={'/home'}>
        <Navigate to={'/car/1'}/>  
      </Route>
      <DefaultRoute to={'/home'}/>
    </Routes>
  </Router>
)


const Car = ({id}) => {
  
  return (<span>Car #{id}</span>)
}

<Navigate>

Component that will redirect on mount.

Props:

  • to - link/href to redirect

<DefaultRoute>

Just shortcut to:

<Route path={'*'}>
  <Navigate to={to}/>
</Route>

Insert it in the end of your routes and get rid of fallbacks.

Props:

  • to - link/href to redirect, if no route found (* regex matching)

<Link>

<a> tag with e.preventDefault(), to use this routing.

Props:

  • href - link/href to redirect
  • beforeRedirect - func that will be called onClick and before redirect
    ({href, e}) => {}
  • afterRedirect - func that will be called onClick and after redirect
    ({href, e}) => {}
  • children - default hidden prop, your elements
  • ...otherProps - will be inserted in <a> declaration.

Ex:

import {Link} from "@gh0st-work/solid-js-router";

const PersonalAccount = () => {
  return (
    <>
      <Link 
        href={'/home'} 
        class={'font-medium text-amber-500 hover:text-amber-400'} 
        beforeRedirect={({href, e}) => console.log(href, e)}
        afterRedirect={({href, e}) => console.log(href, e)}
      >
        Go home
      </Link>
      <Link 
        href={'/home'} 
        class={'text-white font-medium text-lg bg-amber-500 hover:bg-amber-400 flex items-center justify-center space-x-2 rounded-md px-4 py-2'} 
      >
        <i class={'w-4 h-4 fa-solid fa-house'}/>
        <span>Go home button</span>
      </Link>
    </>
  )
}

useHistory()

History navigation, all apis from history package.

And history.pathname() (signal) used in routing.

Ex:

import {useHistory} from "@gh0st-work/solid-js-router";

const Home = () => {
  const history = useHistory()
  
  return (
    <>
      <span>Current pathname: {history.pathname()}</span>
      <button onClick={() => history.push('/home')}>Go home</button>
      <button onClick={() => history.back()}>Go back</button>
    </>
  )
}

Development

TODO

  •  second-level nesting test
  •  match params forwarding
  •  more levels nesting test
  •  useRoutes hook
  •  types (?)

Github


Author: gh0st-work
Source code: https://github.com/gh0st-work/solid-js-router

#react  #javascript #solid 
 

React-like Solid.js Browser Router with Hassle-free Nesting / Dynamic
Joseph  Murray

Joseph Murray

1660763160

Felte: A form Library For Svelte, Solid and React

Felte

Felte is a simple to use form library for Svelte, Solid and React. No Field or Form components are needed, just plain stores and actions to build your form however you like. You can see it in action in this CodeSandbox demo!

Features

  • Single action to make your form reactive.
  • Use HTML5 native elements to create your form. (Only the name attribute is necessary).
  • Provides stores and helper functions to handle more complex use cases.
  • No assumptions on your validation strategy. Use any validation library you want or write your own strategy.
  • Handles addition and removal of form controls during runtime.
  • Official solutions for error reporting using reporter packages.
  • Well tested. Currently at 99% code coverage and constantly working on improving test quality.
  • Supports validation with yup, zod, superstruct and vest.
  • Easily extend its functionality.

Simple usage example

Svelte

<script>
  import { createForm } from 'felte';

  const { form } = createForm({
    onSubmit: async (values) => {
      /* call to an api */
    },
  });
</script>

<form use:form>
  <input type="text" name="email" />
  <input type="password" name="password" />
  <button type="submit">Sign In</button>
</form>

Solid

import { createForm } from '@felte/solid';

function Form() {
  const { form } = createForm({
    onSubmit: async (values) => {
      /* call to an api */
    },
  });

  return (
    <form use:form>
      <input type="text" name="email" />
      <input type="password" name="password" />
      <button type="submit">Sign In</button>
    </form>
  );
}

React/Preact

import { useForm } from '@felte/react';
// if using preact, use `@felte/preact`

function Form() {
  const { form } = useForm({
    onSubmit: async (values) => {
      /* call to an api */
    },
  });

  return (
    <form ref={form}>
      <input type="text" name="email" />
      <input type="password" name="password" />
      <button type="submit">Sign In</button>
    </form>
  );
}

VanillaJS with Web Components

<script type="module">
  import 'https://unpkg.com/@felte/element@0.4.0/dist/min/felte-form.js';
  const felteForm = document.querySelector('felte-form');

  felteForm.configuration = {
    onSubmit: async (values) => {
      console.log(values);
    },
  };
</script>

<felte-form>
  <form>
    <input type="text" name="email" />
    <input type="password" name="password" />
    <button type="submit">Sign In</button>
  </form>
</felte-form>

This example works without a bundler! Copy its contents to an HTML file and open it on your browser. A more complete example like this, with validation and error reporting, can be found here.

More examples

You can find fully functional examples on the /examples directory of this repository. You should be able to open them on CodeSandbox by replacing github's url to githubbox. E.g. Replace https://github.com/pablo-abc/felte/tree/main/examples/svelte/basic with https://githubbox.com/pablo-abc/felte/tree/main/examples/svelte/basic.

Packages

This repository is a mono-repo containing multiple packages located in the packages directory. Maintained using pnpm and Changesets.

Svelte

We provide two packages that are specific to Svelte:

felte

This is the core package that contains all the basic functionality you need to handle your forms in Svelte. Felte optionally allows you to use error reporters (see them as plugins) to prevent you from needing to find a way to display your errors on your form manually. For this we provide already some reporter packages contained in this same repo.

@felte/reporter-svelte

A reporter package that uses a Svelte component to pass the validation messages for you to display. This provides an API that might feel the most familiar to most developers.

Solid

We provide two packages that are specific to Solid:

@felte/solid

This is the core package that contains all the basic functionality you need to handle your forms in Solid. Same as felte but specifically made for Solid.

@felte/reporter-solid

A reporter package that uses a Solid component to pass the validation messages for you to display. This provides an API that might feel the most familiar to most developers.

React

We provide two packages that are specific to React:

@felte/react

This is the main package that contains the basic functionality you need to handle your forms in React. Same as felte but specifically made for React.

@felte/reporter-react

A reporter packages that uses a React component to pass the validation messages for you to display. This provides an API that might feel the most familiar to most developers.

Preact

We provide two packages that are specific to Preact:

@felte/preact

This is the main package that contains the basic functionality you need to handle your forms in Preact. Same as felte but specifically made for Preact. The API is the same as @felte/react so you can refer to the same documentation.

@felte/reporter-preact

A reporter packages that uses a Preact component to pass the validation messages for you to display. This provides an API that might feel the most familiar to most developers. The API is the same as @felte/react so you can refer to the same documentation.

VanillaJS

We provide three packages that can be used with only VanillaJS. Two of them using Web Components. These elements do not use the shadow DOM since there is no reason to isolate styles.

@felte/element

This is the main package that contains the basic functionality you need to handle your forms in vanilla JS using a custom element. Similar to felte but specifically made to be used as a custom element. This is the recommended way to handle your forms when using Vanilla JS. Web components are well supported by all major browsers so this should be a safe option unless you need to support legacy browsers.

@felte/reporter-element

A reporter packages that uses a custom element to display validation messages on the DOM. This the recommended way to display your validation messages when using vanilla JS.

@felte/vanilla

This is the main package that contains the basic functionality you need to handle your forms in vanilla JS. Similar to felte and other integrations but with all code related to frameworks removed. This requires a bit more work to use, since you'll be the one in charge of cleaning up subscribers and listeners on it. It's API is basically the same as felte (Svelte's integration) so you can use Svelte's documentation as a reference. This can be used as a starting point to create your own integration/package for other environments. When it comes to vanilla JS we'd recommend using @felte/element using web components.

Validators

The following packages can be used with any of the framework specific felte wrappers:

@felte/validator-yup

A utility package to help you validate your form with Yup.

@felte/validator-zod

A utility package to help you validate your form with Zod.

@felte/validator-superstruct

A utility package to help you validate your form with Superstruct.

@felte/validator-vest

A utility package to help you validate your form with Vest.

Reporters

The following packages can be used with any of the framework specific felte wrappers:

@felte/reporter-tippy

A reporter that uses Tippy.js to display your validation messages without needing any extra work.

@felte/reporter-cvapi

A reporter that uses the browser's constraint validation API to display your validation messages.

@felte/reporter-dom

A reporter that displays the error messages in the DOM, either as a single element or a list of elements.

Contributing

If you want to contribute to this project you may check CONTRIBUTING.md for general guidelines on how to do so.

Download details:

Author: pablo-a
Source code: https://github.com/pablo-abc/felte
License: MIT license

#svetle #javascript #solid #react

Felte: A form Library For Svelte, Solid and React

Solid Forms: A Solidjs Library For Working with Forms

Solid Forms  

Solid Forms provides several form control objects useful for making working with forms easier. Demos and examples below.

# solidjs
yarn add solid-forms
# or
npm install solid-forms

Note: Solid Forms is brand new and should be considered "beta" until the version release hits 1.0.0. This means breaking changes can come in minor releases. We are following semantic versioning.

Getting Started

The basic building block of Solid Forms are FormControls (see IFormControl API). A FormControl is intended to model a single input element of a form. For example, an <input /> element or a radio button group. You can use a FormControl to save the value of the input, to handle validation and track errors, to track whether the input has been touched, changed, submitted, etc. Importantly, the FormControl itself is just a Solidjs Store object so all of it's properties are observable and you can easily respond to changes (e.g. with createEffect() or just using the control values inside of a component directly). All form controls in solid-forms are immutable unless you use the provided methods to update their state.

For example (Stackblitz demo),

import { Show, mergeProps, type Component } from "solid-js";
import { createFormControl } from "solid-forms";

export const TextInput: Component<{
  control?: IFormControl<string>;
  name?: string;
  type?: string;
}> = (props) => {
  // here we provide a default form control in case the user doesn't supply one
  props = mergeProps({ control: createFormControl(""), type: "text" }, props);

  return (
    <div
      classList={{
        "is-invalid": !!props.control.errors,
        "is-touched": props.control.isTouched,
        "is-required": props.control.isRequired,
      }}
    >
      <input
        name={props.name}
        type={props.type}
        value={props.control.value}
        oninput={(e) => {
          props.control.setValue(e.currentTarget.value);
        }}
        onblur={() => props.control.markTouched(true)}
        required={props.control.isRequired}
      />

      <Show when={props.control.isTouched && props.control.errors?.isMissing}>
        <small>Answer required.</small>
      </Show>
    </div>
  );
};

But the real power of FormControls comes from their composability with other controls such as FormGroups (see IFormGroup API) and FormArrays (see IFormArray API).

For example (Stackblitz demo),

import { Show, mergeProps, createEffect, type Component } from "solid-js";
import { createFormGroup, createFormControl } from "solid-forms";
// here we import the TextInput component we defined above
import { TextInput } from "./TextInput";

export const ExampleForm: Component<{}> = () => {
  const group = createFormGroup({
    name: createFormControl(""),
    email: createFormControl("", {
      required: true,
      validators: (value: string) =>
        value.length === 0 ? { isMissing: true } : null,
    }),
  });

  // This will automatically re-run whenever `group.isDisabled`, `group.isValid` or `group.value` change
  createEffect(() => {
    if (group.isDisabled || !group.isValid) return;

    console.log("Current group value", group.value);
  });

  const onSubmit = async () => {
    if (group.isSubmitted || !group.isValid) return;

    group.markSubmitted(true);
    // do stuff...
    // const { name, email } = group.value;
  };

  return (
    <form onSubmit={onSubmit}>
      <label for="name">Your name</label>
      <TextInput name="name" control={group.controls.name} />

      <label for="email">Your email address</label>
      <TextInput name="email" type="email" control={group.controls.email} />

      <button>Submit</button>
    </form>
  );
};

Lets begin by looking at how to use FormControls to model individual form fields, then we'll learn how to use FormGroups and FormArrays to model forms and form fieldsets (i.e. partial forms).

Creating a form control

See IFormControl in the API section for all properties and methods.

To model individual form fields, we'll use FormControls created via createFormControl(). We can create a FormControl with an initial value of "" like so:

const control = createFormControl("");

That's the same as this (learn more about these options in the API reference):

const control = createFormControl("", {
  id: Symbol("Control-1"),
  data: undefined,
  disabled: false,
  touched: false,
  dirty: false,
  readonly: false,
  submitted: false,
  pending: false,
  errors: null,
  validators: undefined,
});

If we want to set the value on our FormControl we can do the following (note, all control objects in solid-forms are immutable so you need to use the provided methods--e.g. setValue()--to update their state):

control.setValue("Hi");
control.value; // "Hi"

We can also mark our FormControl as touched (or required, disabled, readonly, submitted, pending, or dirty).

control.markTouched(true);
control.touched; // true
control.markTouched(false); // you get the idea

We can manually add errors to our FormControl

control.errors; // null
control.isValid; // true
control.setErrors({ required: true });
control.errors; // { required: true }
control.isValid; // false
control.patchErrors({ tooLong: "must be less than 5 characters" });
control.errors; // { required: true, tooLong: "must be less than 5 characters" }

We can add a validation function (or functions) which will be run after every value change

control.value; // ""
control.errors; // null
control.setValidators((value) =>
  typeof value === "string" && value.length === 0 ? { isMissing: true } : null
);
control.value; // ""
control.errors; // { isMissing: true }
control.setValue("Hi");
control.errors; // null

Under-the-hood, FormControls are just Solidjs stores so we also have the ability to observe any changes to those properties with Solidjs.

createEffect(() => {
  console.log("Value change: ", control.value);
});

// here we manually run validation inside of a render effect
createRenderEffect(() => {
  if (control.value.toLowerCase() !== control.value) {
    control.setErrors({ mustBeLowercase: true });
  } else {
    control.setErrors(null);
  }
});

<div classList={{ "is-invalid": !!control.errors }}>
  <p>The control's current value is: {JSON.stringify(control.value)}</p>
</div>;

You can see all the IFormControl properties and methods in the API section. Lets look at creating a reusable form field component with an input.

Creating a reusable form field input

Lets revist our TextInput example from above (Stackblitz demo),

import { Show, For, mergeProps, type Component } from "solid-js";
import { createFormControl } from "solid-forms";

export const TextInput: Component<{
  control?: IFormControl<string>;
  name?: string;
  type?: string;
}> = (props) => {
  // here we provide a default form control in case the user doesn't supply one
  props = mergeProps({ control: createFormControl(""), type: "text" }, props);

  return (
    <div
      classList={{
        "is-invalid": !!props.control.errors,
        "is-touched": props.control.isTouched,
        "is-required": props.control.isRequired,
        "is-disabled": props.control.isDisabled,
      }}
    >
      <input
        name={props.name}
        type={props.type}
        value={props.control.value}
        oninput={(e) => {
          props.control.setValue(e.currentTarget.value);
        }}
        onblur={() => props.control.markTouched(true)}
        required={props.control.isRequired}
        disabled={props.control.isDisabled}
      />

      <Show when={props.control.isTouched && !props.control.isValid}>
        <For each={Object.values(props.control.errors)}>
          {(errorMsg: string) => <small>{errorMsg}</small>}
        </For>
      </Show>
    </div>
  );
};

Breaking this example down: we'd like the ability for a parent component to pass in a FormControl for the TextInput to use, but we'd also like the TextInput to be usable on its own if the parent doesn't supply a control value. We can accomplish that just like any other Solidjs component using mergeProps from the Solidjs core library to provide a default control prop value if the user doesn't supply one.

export const TextInput: Component<{
  control?: IFormControl<string>;
  name?: string;
  type?: string;
}> = (props) => {
  props = mergeProps({ control: createFormControl(""), type: "text" }, props);

  // ...
};

Since FormControl (and FormGroups and FormArrays) are just Solidjs stores under-the-hood, we can easily use the core classList prop to add css classes to our TextInput if it is invalid, touched, required, or disabled.

  // ...

  return (
    <div
      classList={{
        "is-invalid": !!props.control.errors,
        "is-touched": props.control.isTouched,
        "is-required": props.control.isRequired,
        "is-disabled": props.control.isDisabled,
      }}
    >

We set the underlying <input /> element to be equal to the FormControl's value (value={props.control.value}), we react to input value changes and update the FormControl's value (props.control.setValue(e.currentTarget.value)), we mark the control as touched on blur events, and we setup the input to be required and disabled if the FormControl is required or disabled.

<input
  name={props.name}
  type={props.type}
  value={props.control.value}
  oninput={(e) => {
    props.control.setValue(e.currentTarget.value);
  }}
  onblur={() => props.control.markTouched(true)}
  required={props.control.isRequired}
  disabled={props.control.isDisabled}
/>

Finally, we decide to show errors associated with this FormControl if the control isn't valid AND if the control has been touched. When this happens, we show all the error messages associated with the control.

<Show when={props.control.isTouched && !props.control.isValid}>
  <For each={Object.values(props.control.errors)}>
    {(errorMsg: string) => <small>{errorMsg}</small>}
  </For>
</Show>

Form validation and errors

Validating form data and working with errors is a core part of handling user input. The are two primary ways of validating data in Solid Forms. The simple but more limited approach is to use validator functions. The more flexible and powerful approach is to just use Solidjs built-ins like createEffect() to observe control changes and then setErrors() and patchErrors() on a control as appropriate.

Validator functions

Validator functions are optional functions you can provide to a control which are run whenever the control's value changes (technically rawValue) and either return null (if there are no errors) or return an object with key-value entries if there are errors.

import { type ValidatorFn } from "solid-forms";

const requiredValidator: ValidatorFn = (rawValue: string) =>
  rawValue.length === 0 ? { isMissing: true } : null;

const lowercaseValidator: ValidatorFn = (rawValue: string) =>
  rawValue.toLowerCase() !== rawValue ? { isNotLowercase: true } : null;

// You can also create controls with validator functions
const control = createFormControl("", {
  validators: [requiredValidator, lowercaseValidator],
});

In this example, we provide a validator function that returns an error if the control's value is length 0 and a separate function which errors if the input string is not all lowercase. If we provide multiple validator functions to a control, all validator functions will be run on every change and their errors will be merged together.

You can update the validator function of a control with control.setValidators().

Observe changes and manually set errors

Validator functions are a nice and quick way to add validation to your controls, but a more powerful approach is to observe control changes and manually set errors. With this approach, we have access to the full array of control properties which we can include in our validation logic (validator functions just have access to the control's value).

For example:

const control = createFormControl("");

createRenderEffect(() => {
  if (control.value.includes("@")) {
    control.setErrors({ mustNotIncludeSymbol: "Cannot include '@' symbol." });
  } else {
    control.setErrors(null);
  }
});

Here we observe control value changes and set an error if the value includes an "@" symbol or else clear the errors (to indicate that the value is valid).

If we have multiple different validation effects, we should use the source property of control.setErrors() and control.patchErrors() to partition the errors associated with each effect.

For example (Stackblitz demo),

const control = createFormControl("");

createRenderEffect(() => {
  const source = "@ validator";

  if (control.value.includes("@")) {
    control.setErrors(
      { mustNotIncludeSymbol: "Cannot include '@' symbol." },
      { source }
    );
  } else {
    control.setErrors(null, { source });
  }
});

const source = Symbol("Max length validator");

createRenderEffect(() => {
  if (control.value.length > 10) {
    control.setErrors(
      { tooLong: "Cannot be more than 10 characters." },
      { source }
    );
  } else {
    control.setErrors(null, { source });
  }
});

Here when we use control.setErrors() and also provide the source option, we will only clear or overwrite existing errors that were also set by the same "source". Another way we could have accomplished this would be by using control.patchErrors(). The patchErrors method merges its changes into the existing control.errors property, rather than replacing the existing errors. If you pass an errors object with a key who's value is null, then that key will be deleted from the control errors.

For example:

createRenderEffect(() => {
  if (control.value.includes("@")) {
    control.patchErrors({ mustNotIncludeSymbol: "Cannot include '@' symbol." });
  } else {
    control.patchErrors({ mustNotIncludeSymbol: null });
  }
});

However, in this case using control.patchErrors() isn't as good an approach as using the "source" option. An accidental use of control.setErrors() elsewhere would still overwrite this render effect's validation (which wouldn't happen if you used "source").

Also note, we're using createRenderEffect() here (rather than createEffect()) since validation and errors are likely to affect the DOM (by displaying errors to the user).

Also note, when performing async validation you can mark a control as "pending" via control.markPending(true) to indicate that there is pending validation. control.isValid is only true if the control both doesn't have any errors and is also not pending. The control.markPending() method also accepts a source option for partitioning the pending state of different validation effects. A control will be considered pending so long as any "source" is still pending.

Using a FormGroup

See IFormGroup in the API section for all properties and methods.

To model a form (or fieldset) with multiple form fields, we'll use FormGroups created via createFormGroup(). A FormGroup has all of the properties that a FormControl has, but it also has additional properties like a controls property which contains the FormGroup's child controls.

At it's simplest, we can create a FormGroup with no children and the default state.

const group = createFormGroup();

This is the same as:

const group = createFormGroup(
  {},
  {
    id: Symbol("Control-1"),
    data: undefined,
    disabled: false,
    touched: false,
    dirty: false,
    readonly: false,
    submitted: false,
    pending: false,
    errors: null,
    validators: undefined,
  }
);

If we want to add two child FormControls representing a person's name to our FormGroup we could do the following (note, all control objects in solid-forms are immutable so you need to use the provided methods--e.g. setControl()--to update their state):

group.setControl("firstName", createFormControl(""));
group.controls; // { firstName: IFormControl<string> }
group.value; // { firstName: "" }

group.setControl("lastName", createFormControl(""));
group.controls; // { firstName: IFormControl<string>, lastName: IFormControl<string> }
group.value; // { firstName: "", lastName: "" }

We can also set all the FormGroup's controls at once with

group.setControls({
  firstName: createFormControl("John"),
  lastName: createFormControl("Carroll"),
});

group.controls; // { firstName: IFormControl<string>, lastName: IFormControl<string> }
group.value; // { firstName: "John", lastName: "Carroll" }

group.removeControl("firstName");
group.controls; // { lastName: IFormControl<string> }
group.value; // { lastName: "Carroll" }

If we want to set the value of our FormGroup we can:

group.setValue({ firstName: "Sandy", lastName: "Smith" });
group.value; // { firstName: "Sandy", lastName: "Smith" }

Note that FormGroup's derive their value from the value of their enabled child controls. When you use group.setValue() you are really setting the values of the FormGroup's children.

group.controls.firstName.value; // "Sandy"
group.controls.lastName.value; // "Smith"

You can use patchValue() to update the value of some controls but not others

group.patchValue({ lastName: "Carroll" });
group.value; // { firstName: "Sandy", lastName: "Carroll" }
group.controls.firstName.value; // "Sandy"
group.controls.lastName.value; // "Carroll"

Since a FormGroup's value is equal to the value of all their enabled children, is a child is disabled then it's value will be excluded from group.value.

group.value; // { firstName: "Sandy", lastName: "Carroll" }
group.controls.firstName.markDisabled(true);
group.value; // { lastName: "Carroll" }

If we'd like to get the FormGroup's value ignoring the disabled status of children, we can use the group.rawValue property.

group.rawValue; // { firstName: "Sandy", lastName: "Carroll" }
group.controls.firstName.markDisabled(true);
group.value; // { lastName: "Carroll" }
group.rawValue; // { firstName: "Sandy", lastName: "Carroll" }

FormGroups can have other FormGroups (or FormArrays) as children.

group.setControl(
  "addresses",
  createFormArray([
    createFormGroup({
      street: createFormControl(""),
      city: createFormControl(""),
      state: createFormControl(""),
      zip: createFormControl(""),
    }),
  ])
);

group.value;
// {
//   addresses: [
//     {
//       street: "",
//       city: "",
//       state: "",
//       zip: "" ,
//     },
//   ],
// }

We also have the ability to observe any properties of the FormGroup with Solidjs as well as changes to those properties

For example:

createRenderEffect(() => {
  if (!group.children.areValid) {
    group.setErrors(null);
    return;
  }

  const firstPartOfEmail = group.value.email.split("@")[0];

  if (group.value.name !== firstPartOfEmail) {
    group.setErrors({ nameAndEmailMismatch: true });
  } else {
    group.setErrors(null);
  }
});

Here, we're using a render effect to create a custom validator for the FormGroup. This effect is automatically subscribing to some changes under-the-hood (group.children.areValid, group.value.email, group.value.name) and will automatically re-evaluate when these props change. Lets look at what's happening...

The FormGroup "child", "children" and "self" props

You can view the full IFormGroup API reference for all of the self, children, and child properties.

Both FormControls and FormGroups (and FormArrays) have a self prop which itself is an object containing properties like isValid, isDisabled, isTouched, etc. In a FormControl, control.isValid is just an alias for control.self.isValid (same for control.isTouched, etc). In a FormGroup though, the two properties are different and the self object contains state local to that FormGroup.

For example, if you do group.markTouched(true) on the FormGroup, that updates group.self.isTouched to true but it doesn't affect the state of the FormGroup's children.

group.markTouched(true);
group.isTouched; // true
group.self.isTouched; // true
group.child.isTouched; // false
group.children.areTouched; // false
group.controls.firstName.isTouched; // false
group.controls.lastName.isTouched; // false

Meanwhile, group.isTouched is actually a getter function equal to group.self.isTouched || group.child.isTouched. The group.child.isTouched property is also a memoized getter (its created internally with createMemo()) which is true if any child is touched (it's false if there are no children). So a FormGroup is "touched" if either the FormGroup itself has been markTouched(true) or if any of the FormGroup's children have been markTouched(true). But if you just want to see if the FormGroup, itself, has been touched, then you can use group.self.isTouched. Meanwhile, group.children.areTouched will return true if all child controls have been touched. As expected, all of these properties are observable.

group.isTouched; // false
group.self.isTouched; // false
group.child.isTouched; // false
group.children.areTouched; // false

group.controls.firstName.markTouched(true);
group.isTouched; // true
group.self.isTouched; // false
group.child.isTouched; // true
group.children.areTouched; // false

group.controls.lastName.markTouched(true);
group.isTouched; // true
group.self.isTouched; // false
group.child.isTouched; // true
group.children.areTouched; // true

Above, we gave an example that used group.setErrors() in a way you may have found surprising:

createRenderEffect(() => {
  if (!group.children.areValid) {
    group.setErrors(null);
    return;
  }

  // ...
});

To the unfamiliar, this might look like we're clearing all errors on the FormGroup if any children are invalid. In reality, group.errors is a getter function equal to { ...group.children.errors, ...group.self.errors } or null if there are no children errors or self errors. Using group.setErrors() sets self.errors but it doesn't affect FormGroup children.

You can view the full IFormGroup API reference for all of the self, children, and child properties.

Using a FormArray

See IFormArray in the API section for all properties and methods.

As you might expect, FormArrays are very similar to FormGroups and are used to group multiple controls together. While a FormGroup groups multiple controls together using an object with named keys, a FormArray groups them together using an array.

FormArrays behave very similarly to FormGroups but with the change that you're dealing with an array of child controls (rather than an object containing child controls). FormArray also has one additional method push() which adds a new child control to the end of the FormArray. Other than push(), FormArray and FormGroup have the same interface (which comes from the IAbstractControlContainer interface). Read the FormGroup section if you haven't yet since all of it also applies to FormArray.

Here's an example using a FormArray (Stackblitz demo):

import { For, type Component } from "solid-js";
import { createFormArray, createFormControl, bindOwner } from "solid-forms";

const ExampleForm: Component<{}> = () => {
  const array = createFormArray([createFormControl("")]);

  const addPhoneNumber = bindOwner(() => array.push(createFormControl("")));

  const removePhoneNumber = () => array.removeControl(array.size - 1);

  return (
    <form>
      <div>
        <p>Can you provide us with all of your phone numbers?</p>
      </div>

      <For each={array.controls}>
        {(control, index) => {
          return (
            <>
              <label for={index()}>Phone number {index()}</label>

              <input
                name={index()}
                value={control.value}
                oninput={(e) => {
                  control.setValue(e.currentTarget.value);
                }}
                onblur={() => control.markTouched(true)}
              />
            </>
          );
        }}
      </For>

      <button onclick={addPhoneNumber}>Add phone number</button>
      <button onclick={removePhoneNumber}>Remove phone number</button>

      <button disabled={!array.isValid}>Submit</button>
    </form>
  );
};

In this example, we have a form asking for all of the user's phone numbers. Users can add or remove phone numbers from the form using buttons in the form (i.e. <button onclick={addPhoneNumber}>Add phone number</button>). As the user does this, callbacks will file which adds or removes FormControls from the FormArray. Since this is all observable, changes will be reflected in the component.

See the IFormArray API below for more information. Note, all control objects in solid-forms are immutable so you need to use the provided methods (e.g. IFormArray#push()) to update their state.

Creating controls asyncronously (i.e. bindOwner())

Hopefully, given everything you've read up until this point, most of this just makes sense with, I'm guessing, one exception:

import { bindOwner } from "solid-forms";

const ExampleForm: Component<{}> = () => {
  const array = createFormArray([createFormControl("")]);

  const addPhoneNumber = bindOwner(() => array.push(createFormControl("")));

  const removePhoneNumber = () => array.removeControl(array.size - 1);

  // ...
};

You may be wondering, what's this bindOwner() function from "solid-forms" and why doesn't removePhoneNumber() need it? (though if you're a Solidjs wizz you may have already guessed). Solidjs' reactivity has a concept of an "Owner" that you might never have run into before (the Solidjs "Getting Started" doc currently doesn't mention it and it's not something everyone will run into). The "Owner" is the parent of a given reactive context.

The tl;dr; is that, whenever you have a function that creates a control asyncronously, you need to wrap that function with bindOwner(). Here we're creating a control inside a button's onclick handler, which will be called asyncronously, so we need to use bindOwner(). We don't need to do that for removePhoneNumber because that function is just removing an already created control from the FormArray (it's not creating a new control).

For a longer (but still summarized) explaination of why this is needed and what an Owner is in Solidjs, here's my attempt at a tl;dr for that (with the caviate that I'm not expert so this is really just my current guess)...

When call a component function (or something else using computations like createEffect()), it runs immediately. When it does so, Signals inside that component (or createEffect(), etc), call getOwner() and get the current owner of the context. They are able to do this because getOwner() is a global variable. The initial "root" Owner is set by the top level render() function which then calls the top level App component.

The first thing the App component does when it's called is create a new Owner object for itself and set it to the global "owner" variable that is returned by getOwner(). Then the App component runs the actual component function you provided and when any Computations inside of it call getOwner() (and components are a type of Computation in solid) they receive the Owner associated with the App component. When the App component finishes executing the user provided function (which happens syncronously), then the App component resets the global "owner" viable to whatever it was before it started running. Any components nested inside the App component work the same way. As they are called, they create a new Owner object for themselves and replace the global owner with their own so that nested calls to getOwner() return this new Owner. When the child component function finishes executing, the original parent owner is restored. Etc.

So with this idea, immediately after a component is intialized the component's getOwner() context is removed. If you call code asyncronously, for example inside a button onclick handler, when that code runs it can't find the parent component's owner anymore. The bindOwner() function provided by Solid Forms binds the component's "Owner" context to a function so that, whenever that function is called (even asyncronously) it uses the right owner context. If you create a control asyncronously (e.g. inside of a button onclick handler) then you need to wrap that function with bindOwner(). The bindOwner() function that Solid Forms provides is just a super simple helper utility that looks like this:

import { getOwner, runWithOwner } from "solid-js";

export function bindOwner<T>(fn: () => T): () => T {
  const owner = getOwner();

  if (!owner) {
    throw new Error("No solidjs owner in current context");
  }

  return () => runWithOwner(owner, fn);
}

In the future, it seems possible that Solidjs will choose to provide this help directly within the core library.

Making reusable form components withControl()

To make it easier to build nice, reusable form components, Solid Forms provides an optional withControl() higher order component.

For example (Stackblitz demo):

import { Show, type Component } from "solid-js";
import {
  createFormGroup,
  createFormControl,
  withControl,
  type IFormGroup,
  type IFormControl,
  type IFormControlOptions,
} from "solid-forms";

const controlFactory = (props?: { required?: boolean }) => {
  const options: IFormControlOptions = props?.required
    ? { required: true, validators: MyValidators.required }
    : undefined;

  return createFormGroup({
    street: createFormControl("", options),
    city: createFormControl("", options),
    state: createFormControl("", options),
    zip: createFormControl("", options),
  });
};

const AddressField = withControl<{ legend?: string }, typeof controlFactory>({
  controlFactory,
  component: (props) => {
    const group = () => props.control;
    const controls = () => group().controls;

    return (
      <fieldset disabled={group().isDisabled}>
        <legend>{props.legend || "Your address"}</legend>

        <label for="street">Street</label>
        <TextInput name="street" control={controls().street} />

        <label for="city">City</label>
        <TextInput name="city" control={controls().city} />

        <label for="state">State</label>
        <TextInput name="state" control={controls().state} />

        <label for="zip">Zip</label>
        <TextInput name="zip" control={controls().zip} />
      </fieldset>
    );
  },
});

We can then reuse this AddressField component in a hypothetical parent component like so (Stackblitz demo):

import { AddressField } from "./AddressField";

export const ParentForm: Component<{}> = () => {
  const group = createFormGroup({
    firstName: createFormControl(""),
    lastName: createFormControl(""),
    address: AddressField.control({ required: true }),
  });

  const controls = () => group.controls;

  return (
    <form>
      <label for="firstName">First name</label>
      <TextInput name="firstName" control={controls().firstName} />

      <label for="lastName">Last Name</label>
      <TextInput name="lastName" control={controls().lastName} />

      <AddressField control={controls().address} legend="Your home address" />
    </form>
  );
};

Lets break this example down.

const controlFactory = (props?: { required?: boolean }) => {
  const options: IFormControlOptions = props?.required
    ? { required: true, validators: MyValidators.required }
    : undefined;

  return createFormGroup({
    street: createFormControl("", options),
    city: createFormControl("", options),
    state: createFormControl("", options),
    zip: createFormControl("", options),
  });
};

const AddressField = withControl<{ legend?: string }, typeof controlFactory>({
  controlFactory,
  component: (props) => {
    // ...
  },
};

The withControl() function expects an object with controlFactory and component properties. The component property expects a component function. This component function will always receive a control prop that has the same typescript-type as the control returned by the provided controlFactory function. The control factory function is responsible for constructing the control used by the component. The control factory function optionally receives the component's properties as arguments. When using the AddressField, it will have an optional control property. If you provide that property (like we did in the example above), then the controlFactory function will never be called. But withControl() allows us to use our AddressField by itself without providing a control property to it.

For example:

import { AddressField } from "./AddressField";

export const App: Component<{}> = () => {
  return (
    <div>
      <p>These are the questions we will ask you when we need your address.</p>

      <AddressField required />
    </div>
  );
};

This example will work just fine. In this case, withControl() will see that a control property wasn't provided and will use the controlFactory function you gave to construct it's control. Since we provided the optional required prop, that prop will be provided to the controlFactory function in the props param.

The controlFactory function, itself, operates like a component. It is only called once on initialization, and you can choose to use createEffect() and signals inside of it.

For example:

const controlFactory = (props?: { required?: boolean }) => {
  const options: IFormControlOptions = props?.required
    ? { required: true, validators: MyValidators.required }
    : undefined;

  const group = createFormGroup({
    street: createFormControl("", options),
    city: createFormControl("", options),
    state: createFormControl("", options),
    zip: createFormControl("", options),
  });

  createEffect(() => {
    console.log("current address value", group.value);
  });

  return group;
};

const AddressField = withControl<{ legend?: string }, typeof controlFactory>({
  controlFactory,
  component: (props) => {
    // ...
  },
};

Finally, withControl() will add a control property containing your controlFactory function to the created Solidjs component. You can see this in action in the ParentComponent example, above

import { AddressField } from "./AddressField";

export const ParentForm: Component<{}> = () => {
  const group = createFormGroup({
    firstName: createFormControl(""),
    lastName: createFormControl(""),
    // This use of `AddressField.control` is invoking the AddressField's
    // controlFactory function
    address: AddressField.control({ required: true }),
  });

  // ...
};

Examples

Simple example with validation

Also see Stackblitz demo.

const ExampleComponent: Component<{}> = () => {
  const control = createFormControl("", {
    // This optional validator function is run on every change.
    // If we return `null`, there are no errors. Else, we
    // can return an object containing errors.
    validators: (value: string) =>
      value.length === 0 ? { isMissing: true } : null,
  });

  return (
    <div>
      <label for="example">Please provide some text</label>

      <input
        name="example"
        type="text"
        value={control.value}
        oninput={(e) => {
          control.setValue(e.currentTarget.value);
        }}
        onblur={() => control.markTouched(true)}
      />

      <Show when={control.isTouched && control.errors?.isMissing}>
        <small>Answer required.</small>
      </Show>
    </div>
  );
};

Alternatively, this is effectively the same as the above Stackblitz demo:

const ExampleComponent: Component<{}> = () => {
  const control = createFormControl("");

  // Under the hood, controls are just Solidjs stores
  // so every property is observable. Here we
  // observe the `value` prop and set or clear
  // errors as it changes
  createRenderEffect(() => {
    if (control.value.length > 0) {
      control.setErrors(null);
    } else {
      control.setErrors({ isMissing: true });
    }
  });

  return (
    <div>
      <label for="example">Please provide some text</label>

      <input
        name="example"
        type="text"
        value={control.value}
        oninput={(e) => {
          control.setValue(e.currentTarget.value);
        }}
        onblur={() => control.markTouched(true)}
      />

      <Show when={control.isTouched && control.errors?.isMissing}>
        <small>Answer required.</small>
      </Show>
    </div>
  );
};

Medium example

See Stackblitz demo.

import { Show, type Component } from "solid-js";
import {
  createFormGroup,
  createFormControl,
  type IFormControl,
} from "solid-forms";

const ExampleForm: Component<{}> = () => {
  const group = createFormGroup({
    name: createFormControl(""),
    email: createFormControl(""),
  });

  const onSubmit = async () => {
    if (group.isSubmitted) return;
    // do stuff...
    // const { name, email } = group.value;
    // ...
    // group.markSubmitted(true);
  };

  return (
    <form onSubmit={onSubmit}>
      <label for="name">Your name</label>
      <TextInput name="name" control={group.controls.name} />

      <label for="email">Your email address</label>
      <TextInput name="email" type="email" control={group.controls.email} />

      <button>Submit</button>
    </form>
  );
};

const TextInput: Component<{
  control: IFormControl<string>;
  name?: string;
  type?: string;
}> = (props) => {
  const control = () => props.control;

  return (
    <>
      <input
        name={props.name}
        type={props.type || "text"}
        value={control().value}
        oninput={(e) => {
          control().markDirty(true);
          control().setValue(e.currentTarget.value);
        }}
        onblur={() => control().markTouched(true)}
        disabled={control().isDisabled}
        required={control().isRequired}
      />

      <Show when={control().isTouched && control().errors?.isMissing}>
        <small>Answer required.</small>
      </Show>
    </>
  );
};

Alternatively (Stackblitz demo):

const ExampleForm: Component<{}> = () => {
  const group = createFormGroup({
    name: createFormControl(""),
    email: createFormControl("", {
      required: true,
      validators: (value: string) =>
        !value.includes("@") ? { invalid: true } : null,
    }),
  });

  createRenderEffect(() => {
    if (!group.children.areValid) {
      group.setErrors(null);
      return;
    }

    const firstPartOfEmail = group.value.email.split("@")[0];

    if (firstPartOfEmail !== group.value.name) {
      group.setErrors({ invalid: "email must match name" });
    } else {
      group.setErrors(null);
    }
  });

  return (
    <form>
      <label for="name">Your name</label>
      <TextInput name="name" control={group.controls.name} />

      <label for="email">Your email address</label>
      <TextInput name="email" type="email" control={group.controls.email} />

      <button>Submit</button>
    </form>
  );
};

API

Note, all controls objects are immutable unless you use the provided methods to update their state.

IAbstractControl

interface IAbstractControl<
  RawValue = any,
  Data extends Record<ControlId, any> = Record<ControlId, any>,
  Value = RawValue
> {
  /**
   * The ID is used to determine where StateChanges originated,
   * and to ensure that a given AbstractControl only processes
   * values one time.
   */
  readonly id: ControlId;

  /**
   * The data property can store arbitrary custom data. Use the
   * `setData` method on `IAbstractControl` to update it.
   *
   * The `data` property is, itself, an object. You can set individual
   * keys on the data property with `setData` but you cannot reset
   * or clear the whole object. This is intentional. A library
   * maintainer can store private data within the `data` property
   * using a symbol without fear of the user accidently erasing it.
   */
  readonly data: Data;

  /**
   * The value of the IAbstractControl.
   *
   * In an IAbstractControlContainer,
   * `value` and `rawValue` can be different, but in a standard
   * `IAbstractControl` `value` is just an alias for `rawValue`.
   * See the IAbstractControlContainer interface for possible differences
   * between `value` and `rawValue`.
   */
  readonly value: Value;

  /**
   * The value of the IAbstractControl.
   *
   * In an IAbstractControlContainer,
   * `value` and `rawValue` can be different, but in a standard
   * `IAbstractControl` `value` is just an alias for `rawValue` and
   * rawValue just contains the control's value.
   * See the IAbstractControlContainer interface for possible differences
   * between `value` and `rawValue`.
   */
  readonly rawValue: RawValue;

  /**
   * `true` if this control is disabled, false otherwise.
   * This is an alias for `self.isDisabled`.
   */
  readonly isDisabled: boolean;

  /**
   * `true` if this control is touched, false otherwise.
   * This is an alias for `self.isTouched`.
   */
  readonly isTouched: boolean;

  /**
   * `true` if this control is dirty, false otherwise.
   * This is an alias for `self.isDirty`.
   */
  readonly isDirty: boolean;
  /**
   * `true` if this control is readonly, false otherwise.
   * This is an alias for `self.isReadonly`.
   */
  readonly isReadonly: boolean;
  /**
   * `true` if this control is submitted, false otherwise.
   * This is an alias for `self.isSubmitted`.
   */
  readonly isSubmitted: boolean;
  /**
   * `true` if this control is required, false otherwise.
   * This is an alias for `self.isRequired`.
   *
   * Note that this property doesn't
   * have any predefined meaning for IAbstractControls and it doesn't affect
   * validation in any way. It is up to you to decide what meaning, if any,
   * to give to this property and how to use it. For example, if you
   * validated the control inside a `createEffect()`, you could choose to alter the
   * validation based on whether the control was marked as `required` or
   * not.
   */
  readonly isRequired: boolean;

  /**
   * Contains a `ValidationErrors` object if this control
   * has any errors. Otherwise contains `null`.
   *
   * An alias for `self.errors`.
   */
  readonly errors: ValidationErrors | null;

  /**
   * A validator function that is run on rawValue changes and which
   * generates errors associated with the source "CONTROL_DEFAULT_SOURCE".
   */
  readonly validator: ValidatorFn | null;

  /**
   * `true` if this control is pending, false otherwise.
   * This is an alias for `self.isPending`.
   */
  readonly isPending: boolean;

  /**
   * Valid if `errors === null && !isPending`
   *
   * This is an alias for `self.valid`.
   */
  readonly isValid: boolean;

  /**
   * The `self` object on an abstract control contains
   * properties reflecting the control's personal state. On an
   * IAbstractControlContainer, the personal state can differ
   * from the control's state. For example, an
   * IAbstractControlContainer will register as disabled if
   * the control itself has been marked as disabled OR if
   * all of it's child controls are disabled.
   *
   * Marking the control container
   * itself as disabled doesn't mark the container's children as
   * disabled. On a standard IAbstractControl though,
   * the "self" properties are the same as regular properties.
   * I.e. `self.isInvalid` is the same as `isInvalid` on a
   * standard IAbstractControl (actually, `isInvalid` is
   * an alias for `self.isInvalid` on a standard control).
   */
  readonly self: {
    /** `this.self.errors === null && !this.self.isPending` */
    readonly isValid: boolean;

    /** `true` if this control is disabled, false otherwise. */
    readonly isDisabled: boolean;

    /** `true` if this control is touched, false otherwise. */
    readonly isTouched: boolean;

    /**
     * `true` if this control is dirty, false otherwise.
     *
     * Dirty can be thought of as, "Has the value changed?"
     * Though the isDirty property must be manually set by
     * the user (using `markDirty()`) and is not automatically
     * updated.
     */
    readonly isDirty: boolean;
    /**
     * `true` if this control is readonly, false otherwise.
     *
     * This property does not have any predefined meeting for
     * an IAbstractControl. You can decide if you want to give
     * it meaning by, for example, using this value to set
     * an input's readonly status (e.g.
     * `<input readonly={control.isReadonly} />`)
     */
    readonly isReadonly: boolean;

    /** `true` if this control is submitted, false otherwise. */
    readonly isSubmitted: boolean;

    /**
     * `true` if this control is required, false otherwise.
     *
     * Note that this property doesn't
     * have any predefined meaning for IAbstractControls and it doesn't affect
     * validation in any way. It is up to you to decide what meaning, if any,
     * to give to this property and how to use it. For example, if you
     * validated the control inside a `createEffect()` you could alter the
     * validation based on whether the control was marked as `required` or
     * not.
     */
    readonly isRequired: boolean;

    /** `true` if this control is pending, false otherwise. */
    readonly isPending: boolean;

    /**
     * Contains a `ValidationErrors` object if this control
     * has any errors. Otherwise contains `null`.
     */
    readonly errors: ValidationErrors | null;

    /**
     * *More advanced-ish*
     *
     * Contains a map of ControlId values and ValidationErrors.
     * The errorsStore allows partitioning errors so that
     * they can be associated with different sources and so
     * that one source does not overwrite another source.
     *
     * The `self.errors` property gets its errors from the errorsStore.
     */
    readonly errorsStore: ReadonlyMap<ControlId, ValidationErrors>;

    /**
     * More advanced-ish*
     *
     * A set of ControlIds. `self.isPending` is true so long
     * as `pendingStore.size > 0`. Because this is a set, you
     * can track multiple pending "things" at once. This
     * control will register as pending until all of the "things"
     * have resolved. Use the `markPending()` method with
     * the `source` option to update the pendingStore.
     */
    readonly pendingStore: ReadonlySet<ControlId>;

    /**
     * More advanced-ish*
     *
     * A map of ControlIds and ValidatorFns. The `validator`
     * property is composed of all the validator functions in the
     * `validatorStore`. The validatorStore allows you to change
     * individual validator functions on the control without
     * affecting other validator functions on the control.
     *
     * When you use the `setValidators` method, you are updating
     * the validatorStore.
     */
    readonly validatorStore: ReadonlyMap<ControlId, ValidatorFn>;
  };

  /**
   * If this control is disabled, the status is `"DISABLED"`,
   * else if this control is pending, the status is `"PENDING"`,
   * else if this control has any errors, the status is `"INVALID"`,
   * else the status is `"VALID"`.
   */
  readonly status: "DISABLED" | "PENDING" | "INVALID" | "VALID";

  [AbstractControlInterface]: true;

  /** set the control's value  */
  setValue(value: RawValue): void;

  /**
   * If provided a `ValidationErrors` object or `null`, replaces `self.errors`.
   * Optionally, provide a source ID and the change will be partitioned
   * assocaited with the source ID. The default source ID is
   * "CONTROL_DEFAULT_SOURCE".
   *
   * If you provide a `Map` object containing `ValidationErrors` keyed to source IDs,
   * that will replace the `self.errorsStore` associated with this control.
   */
  setErrors(
    value: ValidationErrors | null | ReadonlyMap<ControlId, ValidationErrors>,
    options?: { source?: ControlId }
  ): void;

  /**
   * If you provide a `ValidationErrors` object, that object is merged with the
   * existing errors associated with the source ID. If the error object has
   * keys equal to `null`, errors associated with those keys are deleted
   * from the errors object.
   *
   * If you provide a `Map` object containing `ValidationErrors` keyed to source IDs,
   * that object is merged with the existing `errorsStore`.
   */
  patchErrors(
    value: ValidationErrors | ReadonlyMap<ControlId, ValidationErrors>,
    options?: { source?: ControlId }
  ): void;

  /** sets `self.isTouched` */
  markTouched(value: boolean): void;

  /** sets `self.isDirty` */
  markDirty(value: boolean): void;

  /** sets `self.isReadonly` */
  markReadonly(value: boolean): void;

  /**
   * Sets `self.isRequired`.
   *
   * Note that this property doesn't
   * have any predefined meaning for IAbstractControls and it doesn't affect
   * validation in any way. It is up to you to decide what meaning, if any,
   * to give to this property and how to use it. For example, if you
   * validated the control inside a `createEffect()` you could alter the
   * validation based on whether the control was marked as `required` or
   * not.
   */
  markRequired(value: boolean): void;

  /**
   * Set `self.isDisabled`.
   *
   * Note that `self.isDisabled`` affect's the control's `status`
   * property. Additionally, `IAbstractControlContainer's` ignore
   * disabled children in many cases. For example, the `value` of a
   * control container is equal to the value of it's _enabled_ children
   * (if you want to see the value including disabled children, use
   * `rawValue`).
   */
  markDisabled(value: boolean): void;

  /** sets `self.isSubmitted` */
  markSubmitted(value: boolean): void;

  /** sets `self.pendingStore` and `self.isPending` */
  markPending(
    value: boolean | ReadonlySet<ControlId>,
    options?: { source?: ControlId }
  ): void;

  /** sets `validator` and `self.validatorStore` */
  setValidators(
    value:
      | ValidatorFn
      | ValidatorFn[]
      | ReadonlyMap<ControlId, ValidatorFn>
      | null,
    options?: { source?: ControlId }
  ): void;

  /**
   * The data property can store arbitrary custom data. Use the
   * `setData` method on `IAbstractControl` to update it.
   *
   * The `data` property is, itself, an object. You can set individual
   * keys on the data property with `setData` but you cannot reset
   * or clear the whole object. This is intentional. A library
   * maintainer can store private data within the `data` property
   * using a symbol without fear of the user accidently erasing it.
   */
  setData<K extends keyof Data>(key: K, data: Data[K]): void;
}

IAbstractControlContainer

export interface IAbstractControlContainer<
  Controls extends GenericControlsObject = any,
  Data = any
> extends IAbstractControl<
    ControlsRawValue<Controls>,
    Data,
    ControlsValue<Controls>
  > {
  /** Child controls associated with this container */
  readonly controls: Controls;

  /** The number of controls associated with this container */
  readonly size: number;

  /** Only returns values for enabled child controls. */
  readonly value: ControlsValue<Controls>;

  /**
   * Returns values for both enabled and disabled child controls.
   */
  readonly rawValue: ControlsRawValue<Controls>;

  /** Will return true if `this.self.isValid` and `this.children.areValid` */
  readonly isValid: boolean;

  /** Will return true if `this.self.isDisabled` or `this.children.areDisabled` */
  readonly isDisabled: boolean;

  /** Will return true if `this.self.isReadonly` or `this.children.areReadonly` */
  readonly isReadonly: boolean;

  /** Will return true if `this.self.isRequired` or `this.child.isRequired` */
  readonly isRequired: boolean;

  /** Will return true if `this.self.isPending` or `this.child.isPending` */
  readonly isPending: boolean;

  /** Will return true if `this.self.isTouched` or `this.child.isTouched` */
  readonly isTouched: boolean;

  /** Will return true if `this.self.isDirty` or `this.child.isDirty` */
  readonly isDirty: boolean;

  /** Will return true if `this.self.isSubmitted` or `this.children.areSubmitted` */
  readonly isSubmitted: boolean;

  /** Contains `{ ...this.children.errors, ...this.self.errors }` or `null` if there are none */
  readonly errors: ValidationErrors | null;

  readonly child: {
    /** Will return true if *any* `enabled` direct child control is `valid` */
    readonly isValid: boolean;
    /** Will return true if *any* direct child control is `disabled` */
    readonly isDisabled: boolean;
    /** Will return true if *any* `enabled` direct child control is `readonly` */
    readonly isReadonly: boolean;
    /** Will return true if *any* `enabled` direct child control is `required` */
    readonly isRequired: boolean;
    /** Will return true if *any* `enabled` direct child control is `pending` */
    readonly isPending: boolean;
    /** Will return true if *any* `enabled` direct child control is `touched` */
    readonly isTouched: boolean;
    /** Will return true if *any* `enabled` direct child control is `dirty` */
    readonly isDirty: boolean;
    /** Will return true if *any* `enabled` direct child control is `submitted` */
    readonly isSubmitted: boolean;
  };

  readonly children: {
    /** Will return true if *all* `enabled` direct child control's are `valid` */
    readonly areValid: boolean;
    /** Will return true if *all* direct child control's are `disabled` */
    readonly areDisabled: boolean;
    /** Will return true if *all* `enabled` direct child control's are `readonly` */
    readonly areReadonly: boolean;
    /** Will return true if *all* `enabled` direct child control's are `required` */
    readonly areRequired: boolean;
    /** Will return true if *all* `enabled` direct child control's are `pending` */
    readonly arePending: boolean;
    /** Will return true if *all* `enabled` direct child control's are `touched` */
    readonly areTouched: boolean;
    /** Will return true if *all* `enabled` direct child control's are `dirty` */
    readonly areDirty: boolean;
    /** Will return true if *all* `enabled` direct child control's are `submitted` */
    readonly areSubmitted: boolean;
    /** Contains *all* `enabled` child control errors or `null` if there are none */
    readonly errors: ValidationErrors | null;

    /**
     * Mark all direct children as disabled. Use the `deep: true`
     * option to instead mark all direct and indirect children
     * as disabled.
     */
    markDisabled(value: boolean, options?: { deep?: boolean }): void;

    /**
     * Mark all direct children as touched. Use the `deep: true`
     * option to instead mark all direct and indirect children
     * as touched.
     */
    markTouched(value: boolean, options?: { deep?: boolean }): void;

    /**
     * Mark all direct children as dirty. Use the `deep: true`
     * option to instead mark all direct and indirect children
     * as dirty.
     */
    markDirty(value: boolean, options?: { deep?: boolean }): void;

    /**
     * Mark all direct children as readonly. Use the `deep: true`
     * option to instead mark all direct and indirect children
     * as readonly.
     */
    markReadonly(value: boolean, options?: { deep?: boolean }): void;

    /**
     * Mark all direct children as required. Use the `deep: true`
     * option to instead mark all direct and indirect children
     * as required.
     */
    markRequired(value: boolean, options?: { deep?: boolean }): void;

    /**
     * Mark all direct children as submitted. Use the `deep: true`
     * option to instead mark all direct and indirect children
     * as submitted.
     */
    markSubmitted(value: boolean, options?: { deep?: boolean }): void;

    /**
     * Mark all direct children as pending. Use the `deep: true`
     * option to instead mark all direct and indirect children
     * as pending.
     */
    markPending(
      value: boolean,
      options?: { source?: ControlId; deep?: boolean }
    ): void;
  };

  [AbstractControlContainerInterface]: true;

  /**
   * Apply a partial update to the values of some children but
   * not all.
   */
  patchValue(value: unknown): void;

  /** sets the `controls` property */
  setControls(controls: Controls): void;

  /** stores the provided control in `controls[key]` */
  setControl(key: unknown, control: unknown): void;

  /**
   * If provided a control value, removes the given control from
   * `controls`. If provided a control key value, removes the
   * control associated with the given key from `controls`.
   */
  removeControl(key: unknown): void;
}

IFormControl

See the IAbstractControl interface, above. IFormControl has the same properties as that interface. Like other solid-forms controls, form controls immutable unless you use the provided methods to update it's state.

interface IFormControl<
  Value = any,
  Data extends Record<ControlId, any> = Record<ControlId, any>
> extends IAbstractControl<Value, Data, Value> {
  [FormControlInterface]: true;
}

createFormControl()

Use the createFormControl() function to create a new Solidjs store conforming to the IFormControl interface.

function createFormControl<
  Value,
  Data extends Record<ControlId, any> = Record<ControlId, any>
>(
  value?: Value,
  options?: IFormControlOptions<Data>
): IFormControl<Value, Data>;

interface IFormControlOptions<
  Data extends Record<ControlId, any> = Record<ControlId, any>
> {
  id?: ControlId;
  data?: Data;
  disabled?: boolean;
  touched?: boolean;
  dirty?: boolean;
  readonly?: boolean;
  required?: boolean;
  submitted?: boolean;
  errors?: null | ValidationErrors | ReadonlyMap<ControlId, ValidationErrors>;
  validators?:
    | null
    | ValidatorFn
    | ValidatorFn[]
    | ReadonlyMap<ControlId, ValidatorFn>;
  pending?: boolean | ReadonlySet<ControlId>;
}

IFormGroup

See the IAbstractControlContainer interface, above. IFormGroup has the same properties as that interface. Like other solid-forms controls, form groups immutable unless you use the provided methods to update it's state.

interface IFormGroup<
  Controls extends { [key: string]: IAbstractControl } = {
    [key: string]: IAbstractControl;
  },
  Data extends Record<ControlId, any> = Record<ControlId, any>
> extends IAbstractControlContainer<Controls, Data> {
  [FormGroupInterface]: true;
}

createFormGroup()

Use the createFormGroup() function to create a new Solidjs store conforming to the IFormGroup interface.

function createFormGroup<
  Controls extends { [key: string]: IAbstractControl } = {
    [key: string]: IAbstractControl;
  },
  Data extends Record<ControlId, any> = Record<ControlId, any>
>(
  controls?: Controls,
  options?: IFormGroupOptions<Data>
): IFormGroup<Controls, Data>;

interface IFormGroupOptions<
  Data extends Record<ControlId, any> = Record<ControlId, any>
> {
  id?: ControlId;
  data?: Data;
  disabled?: boolean;
  touched?: boolean;
  dirty?: boolean;
  readonly?: boolean;
  required?: boolean;
  submitted?: boolean;
  errors?: null | ValidationErrors | ReadonlyMap<ControlId, ValidationErrors>;
  validators?:
    | null
    | ValidatorFn
    | ValidatorFn[]
    | ReadonlyMap<ControlId, ValidatorFn>;
  pending?: boolean | ReadonlySet<ControlId>;
}

IFormArray

See the IAbstractControlContainer interface, above. IFormArray has the same properties as that interface with one addtion: push() for adding new child controls to the end of the form array.Like other solid-forms controls, form arrays immutable unless you use the provided methods to update it's state.

interface IFormArray<
  Controls extends ReadonlyArray<IAbstractControl> = ReadonlyArray<IAbstractControl>,
  Data extends Record<ControlId, any> = Record<ControlId, any>
> extends IAbstractControlContainer<Controls, Data> {
  [FormArrayInterface]: true;
  push(control: Controls[number]): void;
}

createFormArray()

Use the createFormArray() function to create a new Solidjs store conforming to the IFormArray interface.

function createFormArray<
  Controls extends ReadonlyArray<IAbstractControl> = ReadonlyArray<IAbstractControl>,
  Data extends Record<ControlId, any> = Record<ControlId, any>
>(
  controls?: Controls,
  options?: IFormArrayOptions<Data>
): IFormArray<Controls, Data>;

interface IFormArrayOptions<
  Data extends Record<ControlId, any> = Record<ControlId, any>
> {
  id?: ControlId;
  data?: Data;
  disabled?: boolean;
  touched?: boolean;
  dirty?: boolean;
  readonly?: boolean;
  required?: boolean;
  submitted?: boolean;
  errors?: null | ValidationErrors | ReadonlyMap<ControlId, ValidationErrors>;
  validators?:
    | null
    | ValidatorFn
    | ValidatorFn[]
    | ReadonlyMap<ControlId, ValidatorFn>;
  pending?: boolean | ReadonlySet<ControlId>;
}

Helpers

withControl()

A higher order component function for creating reusable form components.

import { withControl } from "solid-forms";

function withControl<
  Props extends {},
  ControlFactory extends (...args: [any, ...any[]]) => IAbstractControl
>(
  options: IWithControlOptions<Props, ControlFactory>
): WithControlReturnType<Props, ControlFactory>;

interface IWithControlOptions<
  Props extends {},
  ControlFactory extends (...args: [any, ...any[]]) => IAbstractControl
> {
  controlFactory: ControlFactory;
  component: Component<
    WithControlProps<Props, ControlFactory> & {
      control: ReturnType<ControlFactory>;
    }
  >;
}

type WithControlReturnType<
  Props extends {},
  ControlFactory extends (...args: [any, ...any[]]) => IAbstractControl
> = ((
  props: WithControlProps<Props, ControlFactory> & {
    control?: ReturnType<ControlFactory>;
  }
) => JSX.Element) & {
  /**
   * Factory function to build the component's default form control.
   * Note, you can pass any form control to the component which
   * satisfies the component's interface. You do not need to use
   * this factory function.
   *
   * Example usage:
   * ```ts
   * const TextInput = withControl({
   *   // etc...
   * });
   *
   * createFormGroup({
   *   street: TextInput.control(),
   *   city: TextInput.control(),
   *   state: TextInput.control(),
   *   zip: TextInput.control(),
   * })
   * ```
   */
  control: ControlFactory;
};

bindOwner()

Helper to bind the owner of the current context to the supplied function.

import { bindOwner } from "solid-forms";

function bindOwner<T>(fn: () => T): () => T;

isAbstractControl()

Check's if an object is an IAbstractControl.

import { isAbstractControl } from "solid-forms";

function isAbstractControl(object?: unknown): object is IAbstractControl;

isAbstractControlContainer()

Check's if an object is an IAbstractControlContainer.

import { isAbstractControlContainer } from "solid-forms";

function isAbstractControlContainer(
  object?: unknown
): object is IAbstractControlContainer;

isFormControl()

Check's if an object is an IFormControl.

import { isFormControl } from "solid-forms";

function isFormControl(object?: unknown): object is IFormControl;

isFormGroup()

Check's if an object is an IFormGroup.

import { isFormGroup } from "solid-forms";

function isFormGroup(object?: unknown): object is IFormGroup;

isFormArray()

Check's if an object is an IFormArray.

import { isFormArray } from "solid-forms";

function isFormArray(object?: unknown): object is IFormArray;

About

This library was created by John Carroll with significant inspiration from Angular's ReactiveFormsModule.


Author: jorroll
Source code: https://github.com/jorroll/solid-form
License:  Unlicense license
#react #typescript #javascript #solid 

Solid Forms: A Solidjs Library For Working with Forms