Introduction to SolidJS

In this tutorial, you’ll learn what SolidJS is, how it works, and its fundamentals such as reactivity, architecture, events, effects, etc. Then you’ll use this knowledge to build a simple todo app. Finally, you’ll understand how SolidJS compares with React and how it’s better and worse.

SolidJS is a declarative JavaScript framework used for building UIs and web applications pragmatically and performatively. Even though it's relatively new, its similarity with other JavaScript frameworks like React and Svelte has made it more adoptable for the developer community.

So in this tutorial, I'll give you a complete walkthrough of SolidJS and help you understand the essentials. You'll also build a simple web application with SolidJS to get a better hang of the basics. Finally, I'll compare and contrast it with the most popular framework in town — React.

What is SolidJS?

Features of SolidJS

SolidJS takes the syntax and developer experience of React and combines it with the performance of Svelte to give you the best of both worlds.

Just like React, Solid uses JSX to render HTML in the browser. It also uses similar syntax for reactivity to update DOM in real-time. However, unlike React, it uses a compiled DOM instead of virtual DOM.

This means you get a smaller size pure JavaScript bundle, much like the production bundle of your Svelte or Vanilla JS application. This makes it blazingly fast and highly performant.

SolidJS Essentials

Let's now understand the fundamentals of SolidJS using some code examples. To follow along, you can directly run these snippets in the SolidJS playground.

Architecture

Solid follows the component architecture for modularity and reusability. Let's see how you can create an entry component and render it to the DOM.

Here's a component HelloWorld that is injected into the DOM inside an HTML element that has an id app:

import { render } from "solid-js/web";

function HelloWorld() {
  

  return (
    <h1>Hello World</h1>
  );
}

render(() => <HelloWorld />, document.getElementById("app")!);

From the above code, it's also clear that components are functions that return the HTML template using JSX.

HelloWorld Example

Solid components allow only a single JSX element at the top level. To combat this, you can use special JSX elements called fragments:

    <>
     <h1>Hello World </h1>
     <p>This is my first solid app </p>
    </>

Nesting Components

Components can be nested inside one another. For instance, here you have a <Header/>component:

function Header() {
  return (
    <h1>
     Header
    </h1>
  );
}

export default Header;

That is rendered inside the main <App/>component:

import { render } from "solid-js/web";
import Header from './header';

function App() {
 

  return (
    <>
      <Header/>
      <h1>
      Hello World
      </h1>
    </>
  );
}

render(() => <App />, document.getElementById("app")!);

That should give you both the components on the DOM like this:

Header and HelloWorld

Props

Props are data and functions you can pass from one component to another to share data and features between the two components. In the previous example, you can pass a title prop to the <Header/> component:

...
    <Header title="Header"/>
...

Then consequently consume this prop inside the child component:

function Header({title}) {
  return (
    <h1>
     {title}
    </h1>
  );
}

export default Header;

Reactivity and Events

Reactivity allows you to create variables that dynamically trigger a DOM update when changed.

SolidJS allows you to create reactive variables called signals using the createSignal function. This function takes in the initial value of the variable as a parameter. And it returns an array where the first element is the variable itself and the second element is a function used to mutate that variable.

Here's an example that changes the reactive variable called name:

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

function DisplayName() {
  const [name, setName] = createSignal('fuzzysid');
  const changeName = () => setName('siddhant')

  return (
    <>
      <h1> {name} </h1>
      <button onClick={changeName}> Change Name </button>
    </>
  );
}

render(() => <DisplayName />, document.getElementById("app")!);

Initially, the name outputs the string 'fuzzysid':

Reactivity Before Update

A click event is used on the button that fires a function called changeName. When you press the Change Name button, the changeName function updates the name signal to 'siddhant':

Reactivity After Update

What a state is to React components, a signal is to Solid components. Here's another useful and typical example of reactive variables:

import { render } from "solid-js/web";
import { createSignal } from "solid-js";

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

  return (
    <>
      <h1> Counter: {count} </h1>
      <button onClick={()=>setCount(count=>count-1)}>  Decrement </button>
       
      <button onClick={()=>setCount(count=>count+1)}>  Increment </button>
    </>
  );
}

render(() => <Counter />, document.getElementById("app")!);

You can take the current value of a signal inside the signal's mutation's callback function. The above code used the present value of count to increment and decrement it accordingly.

Effects

Effects are triggers or watchers that can help you fire a function when a signal updates.

Let's say, in the previous example, you want to throw an alert when the count becomes greater than 5.

To do so, you can create an effect using the createEffect function. It takes a callback function as a parameter. This callback function is fired whenever a signal invoked inside it changes.

createEffect(()=>{
    if(count()>5) alert('Count is greater than five')
})

So now, if you click on the Increment button, you should get an alert when it becomes greater than 5:

Effects Example

Conditionals

Solid provides special components called <Show> to render a template based on a condition or the value of a signal. Here's an example of that:

import { render } from "solid-js/web";
import { createSignal,Show } from "solid-js";

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

  return (
    <>
      <h1> Counter: {count} </h1>
      <button onClick={()=>setCount(count=>count-1)}>  Decrement </button>
       
      <button onClick={()=>setCount(count=>count+1)}>  Increment </button>
      <Show when={count()>5}>
        <p>Count is greater than five </p>
      </Show>
    </>
  );
}

render(() => <Counter />, document.getElementById("app")!);

Once the count becomes greater than 5, the <p> will start showing on the DOM:

Conditionals Example

Loops

Just like <Show>, Solid also provides a special component called <For> to render a list on the DOM from an array.

In the following example, you use the <For> component to render a list of fruits from the fruits signal. Inside the opening brackets of the <For> component, you specify the variable name that will be used to extract items to loop through using the each prop.

Then, you can take a callback inside which you get the individual item as the first parameter and an optional index of the iteration as a second parameter.

import { render } from "solid-js/web";
import { createSignal, For } from "solid-js";

function Counter() {
  const [fruits,setFruits] = createSignal([
    'Mango 🥭',
    'Apple 🍎',
    'Banana 🍌',
    'Melon 🍈',
    'Watermelon 🍉'
  ]);

  return (
    <>
      <h1> Fruits </h1>
      <For each={fruits()}>{(fruit,index)=>
        <div>{index()+1}: {fruit}</div>
      }
      </For>
    </>
  );
}

render(() => <Counter />, document.getElementById("app")!);

Here's what the list looks like when rendered in the DOM:

For Example

Lifecycle Methods

Most frameworks provide several lifecycle methods for different purposes. This often leads to a steeper learning curve as well as unnecessary complexity.

Solid stays truly reactive: it gives you the createEffect and signals to build whatever interaction you want. However, to simplify things further, it gives you two primary lifecycle methods.

onMount

The onMount method is fired just once in the entire lifecycle of a Solid component. It runs only the first time your component mounts on the DOM. It's an excellent place to make any API calls your application needs.

For instance, consider the following example that uses the onMount method to fetch and render some data:

import { render } from "solid-js/web";
import { createSignal, onMount, For } from "solid-js";

function App() {
  const [posts, setPosts] = createSignal([]);

  onMount(async () => {
    const res = await fetch(`https://jsonplaceholder.typicode.com/posts`);
    setPosts(await res.json());
  });

  return <>
    <h1>Effects: onMount</h1>

    <div>
      <For each={posts()}>{ post =>
          <p>
            {post.body}
          </p>
      }</For>
    </div>
  </>;
}

render(() => <App />, document.getElementById('app'));

It renders a list of posts fetched from a dummy API:

onMount Example

onCleanup

onCleanup is the exact opposite of onMount. It's fired whenever a component unmounts from the DOM completely. Ideally, you can use this method to clear any event listeners you may have added to your DOM elements.

Here's an example that does that:

import { render } from "solid-js/web";
import {  onMount, onCleanup } from "solid-js";

function App() {

  const handleMouseEnter=()=>{
    console.log('Entered the box')
  }

  onCleanup(()=>{
    const box=document.querySelector('#box');
    box.removeEventListener('mouseenter',handleMouseEnter)
  })

  onMount(async () => {
    const box=document.querySelector('#box');
    const onMouseEnter=box.addEventListener('mouseenter',handleMouseEnter)
  });

  return <>
    <h1>Effects</h1>

    <div id="box" style={{height:'200px',width:'200px',border:'1px solid red'}}>

    </div>
  </>;
}

render(() => <App />, document.getElementById('app'));

When you enter the red box, the mouseenter event fires, and you get a message on the console. As a cleanup, when the component unmounts, we remove this event listener from that <div>.

OnCleanup Example

Build a Todo App with SolidJS

Let's combine all that knowledge to build a small todo application with Solid.

Setup

To get started, you'll create a new SolidJS project configured with Vite by running:

npx degit solidjs/templates/js my-app

That should create a new Solid project for you. Navigate inside this project and open it in a code editor (like VS Code):

cd my-app && code .

Next, install the dependencies by running:

npm i

And finally, kickstart the app by running:

npm run start

That should open the project for you at http://localhost:3000. Awesome!

Architecture & Boilerplate Code

Your initial project structure gives the <App/> component by default. This is going to be your entry point component. For starters, clean everything that's inside it:

import styles from './App.module.css';


function App() {
 
  return (
    <div class={styles.App}>
      
    </div>
  );
}

export default App;

Notice that you also have an App.module.css file being imported here. It's then referenced inside the <div> container. You'll add all our styles inside this App.module.css file and reference it the same way.

Now let's create some boilerplate files. Here's what the architecture of our Todo app would look like:

Todo App Architecture

You'll have your top-most parent <App/> component that would store all the todos and the functions to manipulate these todos. This <App/> component will have two child components.

First, the <AddTodo> component will be responsible for adding a new todo.

Second, the <Todos/> component will be responsible for rendering the list of todos via its own child — <TodoItem/> component.

Create a JSX file for each of the above components.

The Component

The <AddTodo/> component will render an input field and a button. The input field is where the user types a new todo. The button will be used to send this new todo back to a function in the parent <App/> component to add this todo to the todos list. This function will be taken as props inside the <AddTodo/> component.

To store the newTodo, create a signal since you want this property to be reactive based on what the user enters in the input.

 const [newTodo,setNewTodo]=createSignal('')

Now let's add the template inside the component's JSX:

import {createSignal} from 'solid-js'


function AddTodo({addTodo}){

    const [newTodo,setNewTodo]=createSignal('')

    
    return(
        <div>
            <input 
                onChange={handleChange} 
                value={newTodo()} type="text" 
                placeholder="Type a new todo..."
            />
            <button onClick={handleClick}>Add Todo</button>
        </div>
    )
}

export default AddTodo;

You bind the input field to the newTodo signal. You now need to create the handleChange function that updates the newTodo signal based on the onChange event.

You also need to create the handleClick function that passes the newTodo signal to the addTodo function prop and clears the input state.

 const handleChange=(e)=>setNewTodo(e.target.value)

 const handleClick=()=>{
        addTodo(newTodo)
        setNewTodo('')
 }

Here's how the entire <AddTodo/> component looks like:

import {createSignal} from 'solid-js'


function AddTodo({addTodo}){

    const [newTodo,setNewTodo]=createSignal('')

    const handleChange=(e)=>setNewTodo(e.target.value)

    const handleClick=()=>{
        addTodo(newTodo)
        setNewTodo('')
    }
    
    return(
        <div>
            <input 
                onChange={handleChange} 
                value={newTodo()} type="text" 
                placeholder="Type a new todo..."
            />
            <button onClick={handleClick}>Add Todo</button>
        </div>
    )
}

export default AddTodo;

You also need to render this inside the <App/> component:

import styles from './App.module.css';
import {createSignal} from 'solid-js'
import AddTodo from './AddTodo';

function App() {

  const addTodo=(newTodo)=>{
   
  }

  const deleteTodo=(todo)=>{

  }

  return (
    <div class={styles.App}>
      <h1 class={styles.heading}>Todos</h1>
      <AddTodo addTodo={addTodo}/>
    </div>
  );
}

export default App;

Once you do that, you should see an input field with a submit button rendered by the <AddTodo/> component on the screen:

AddTodo Component

Rendering Todos

Now let's work on two simple components responsible for rendering the todos. First is the <Todos/> component. As you already know, this component takes in two props — a list of todos called the todos signal and the deleteTodo method.

import { For } from 'solid-js';
import TodoItem from './TodoItem';

function Todos({todos,deleteTodo}){
    console.log(todos())
    return(
        <For each={todos()}>{(todo,ind)=>
           
        }
        </For>
    )

}

export default Todos;

You use the <For/> component to loop through the list of todos. Now, you need to pass each todo item and the deleteTodo method further down to your <TodoItem/> component. This component is responsible for rendering each list item.

function TodoItem({todo,deleteTodo}){
    const handleDelete=()=>deleteTodo(todo);
    return(
        <p onClick={handleDelete}>{todo}</p>
    )
}

export default TodoItem;

When you click on an item, you invoke the deleteTodo function and pass the clicked todo item as a parameter.

Finally, render the <Todos/> component inside the root <App/> component.

import styles from './App.module.css';
import {createSignal} from 'solid-js'
import Todos from './Todos';
import AddTodo from './AddTodo';

function App() {
  const [todos,setTodos]=createSignal([])

  const addTodo=(newTodo)=>{
  }

  const deleteTodo=(todo)=>{
    
  }

  return (
    <div class={styles.App}>
      <h1 class={styles.heading}>Todos</h1>
      <AddTodo addTodo={addTodo}/>
      <Todos deleteTodo={deleteTodo} todos={todos}/>
    </div>
  );
}

export default App;

Great! Let's complete the todo app by writing the logic for adding and deleting a todo.

Adding and Deleting Todos

To add a new todo, you only need to complete the logic inside the addTodo function in the <App/> component. This function already takes a newTodo parameter, the new todo item you need to add.

const addTodo=(newTodo)=>{
    setTodos([newTodo(),...todos()]);
}

You'll add the newTodo in the todos signal that is an array using its setter function setTodos. Use JavaScript array destructuring here to ensure to keep the previous todos intact. Let's give this a whirl now:


It looks like you're able to add todos now. Awesome!

To delete the todo when they're clicked, populate your deleteTodo function:

  const deleteTodo=(todo)=>{
    let newTodos=todos;
    newTodos=newTodos().filter(_todo=>_todo!=todo);
    setTodos(newTodos)
  }

First, make a copy of the current todos array. Then, filter through this copy to remove the todo you wish to delete. Finally, update your original todos signal with this copy using the setTodos setter. Let's test it out now:


You can delete todos as well; fantastic!

Styling the App

Functionally your app is complete, but it surely misses some aesthetics. Let's add some simple yet neat-looking styles. You can directly update the styles in our App.module.css file:

*{
  margin: 0;
  padding: 0;
}

.App {
  text-align: center;
  height: 100vh;
  background: #F8F8F8;
  padding: 100px;
  display:flex;
  flex-direction: column;
  align-items: center;
}

.heading{
  font-size: 96px;
  margin-bottom: 20px;
}

input{
  border-radius: 12px;
  outline: none;
  border: none;
  padding: 5px 20px;
  margin-right: 20px;
  height: 60px;
  width: 400px;
  font-size: 24px; 
  box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
}

button{
  height: 65px;
  width: 150px;
  border: none;
  outline: none;
  border-radius: 12px;
  font-size: 24px;
  padding: 5px 15px;
  box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
  cursor: pointer;
  font-weight: 600;
  background:#2e3136;
  color:#fff;
}

p{
  height: 60px;
  background: white;
  width: 600px;
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  font-size: 24px;
  margin: 20px 0px;
  box-shadow: rgba(0, 0, 0, 0.05) 0px 1px 2px 0px;
}

p:hover{
  background: rgb(232, 169, 146);
  color:#fff;
  cursor: pointer;
}


.header {
  background-color: #282c34;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  font-size: calc(10px + 2vmin);
  color: white;
}

And that should give us a much better-looking todo app now:

Updated-app

SolidJS vs. React

If you're coming from a React background, building the todo app must feel like a piece of cake to you! This is because, in terms of syntax and architecture, Solid is strikingly similar to React. It uses the same structure of using functional components, returning JSX, fragments, one-way data binding, etc.

Using effects, refs and events are also much similar to React in Solid. So it's safe to say that Solid provides almost the same if not slightly different syntactic sugar as React. Then why do we need another React-like framework for building web applications?

Solid differs from React in terms of performance, developer experience, rendering algorithm, and reactivity. Let's skim through the key differences:

Performance

At the beginning of this tutorial, we discussed how Solid doesn't use a virtual DOM. Instead, it uses a compiled DOM. This gives it a significant edge over React and any other framework in terms of run-time performance. It generates a blazingly fast production bundle that is aimed to provide a more rich user experience for your users.

SolidJS Performance

If you wish to dive deeper into these stats and take a closer look at performance benchmarks, here's a comparison guide you can refer to.

Solid is Truly Reactive

Reactivity lies at the heart of SolidJS, which is why you'll never find any unnecessary renders of your Solid components. Only the part of a template that needs to update will re-render.

With React, however, change in a state leads to re-rendering the entire component. There are ways to combat this, but it needs additional efforts to learn and incorporate these concepts into your applications.

Community Support, Documentation, and Resources

Even though Solid has excellent and extensive documentation, the React community is much larger since it has been around for longer. That can be a deal-breaker if you want to build scalable web applications quickly. Even third-party libraries are short in number for Solid in comparison to React.

Conclusion

Solid has an exciting future and is worth a side-project or an MVP for your startup. If you wish to explore Solid in-depth, you can check out an official tutorial by the Solid team here that walks you through every concept in Solid. You can also explore the entire code for the demo in this tutorial here. Until next time!

Siddhant Varma

Written by Siddhant Varma

He is a full-stack JavaScript engineer who loves building pixel perfect UIs and engineering tech products. He loves creating technical content for developers and has worked as a frontend engineer for many startups.

LoginRadius CIAM Platform

Our Product Experts will show you the power of the LoginRadius CIAM platform, discuss use-cases, and prove out ROI for your business.

Book A Demo Today