Intro to

 

 

 

 

for React Developers (or anyone else)

Travis Waith-Mair

@travisWaithMair

non-traditional.dev

Solid Feels Familiar to React Devs

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

function Counter() {
  const [count, setCount] = createSignal(0);
  const increment = () => setCount(count() + 1);

  return (
    <button type="button" onClick={increment}>
      {count()}
    </button>
  );
}

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

Solid uses JSX, Components and Props

function Welcome(props) {
  return <p>Welcome, {props.name}</p>;
}

Solid primitives use a

 "Hook-like" API

  const [user, setUser] = createSignal();
  
  createEffect(() => {
    getUser(props.id)
      .then(user => {
        setUser(user)
      })
  
  })

Solid is not a drop-in replacement of React

Unlike frameworks like Preact, one cannot just use existing React components in a Solid Application.

Reactivity

instead of

VDOM

React VDOM

Reactivity

Solid JS will surgically update only those parts of the App that are dependent on reactive values

Example

function Counter() {
  const [count, setCount] = createSignal(1);
  const increment = () => setCount(count() + 1);

  console.log("I only get logged once")

  return (
    <button type="button" onClick={increment}>
      {count()}
    </button>
  );
}

With Solid.js, it's is all about the primitives

Components in Solid "disappear" after the initial render.

 

Solid is built on "fine-grained" reactive primitives that do the heavy lifting of optimizing DOM updates.

Solid.js Primitives

Everything in Solid can be broken down into a Signal, a Memo, or an Effect.

createSignal

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

<p>The current count is: {count()}</p>

<button onClick={() => setCount(count() + 1)}>
  Increment
</button>

Signals are simply functions that return a value. They are used to keep reactive state.

createEffect

createEffect(() => {
  document.title = `The current count is: ${count()}`;
});

*No Dependency Array needed

Effects let you do actions based on when signals update.

createEffect

createEffect(() => {
  document.title = "I'm not reactive";
});

This effect will only run once since there are no reactive values that will trigger an update

If there are no reactive values, they don't update.

createMemo

const fullName = createMemo(() => `${firstName()} ${lastName()}`);

<p>{fullName()}</p>

It is both an effect and a signal

Works best for expensive calculations

Simple Counter Example

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

createEffect(() => {
  document.title = `The current count is: ${count()}`;
});

return (
    <div>
      <p>The current count is: {count()}</p>
      <button onClick={() => setCount(count() + 1)}>Plus</button>
    </div>
  );
}

The effect and paragraph are updated when ever the count signal is updated

A More Complicated Counter Example

function Counter() {
const [count, setCount] = createSignal(0);
const [myName, setMyName] = createSignal("Travis");

createEffect(() => {
  document.title = `The current count is: ${count()}`;
});

return (
    <div>
      <p>The current count is: {count()}</p>
      <input value={myName()} onChange={e=>setMyName(e.target.value)}/>
      <button onClick={() => setCount(count() + 1)}>Plus</button>
    </div>
  );
}

The effect and paragraph are only updated whenever the count signal is updated, but not when the myName value is updated

Primitives don't follow React's Hook rules

export const [count, setCount] = createSignal(0);

function Counter(props) {

if(props.logCount){
  createEffect(() => {
    console.log(count());
  });
}

return (
    <div>
      <p>The current count is: {count()}</p>
      <button onClick={() => setCount(count() + 1)}>Plus</button>
    </div>
  );
}

Gotchas coming from React

Spanish

Universidad

 

Presidente

 

Embarazada?

English

University

 

President

 

Embarrassed

"False Friends" in Spanish

Solid has it's "false friends" for React Devs

Don't return early

function DataDisplay(props){

  if(props.data === undefined){
      return <div>Loading...</div>
  }
    
  return <div>{/* display props.data */}</div>
}

Functions "Disappear" after the initial render

function DataDisplay(props){

  if(props.data === undefined){
      return <div>Loading...</div>
  }
   
  /**
  * This code is never run, which
  * means the reactivity is never
  * wired up.
  */
  return <div>{/* display props.data */}</div>
}

Instead, use Control Flow components

import { Show } from "solid-js";

function DataDisplay(props){
  return (
    <Show when={props.data} fallback={<div>Loading...</div>}>
    	<div>{/* display props.data */}</div>
    </Show>
    );
}

Other Control Flow components like Switch, For, ErrorBoundries, and Suspense.

<Switch fallback={<div>Not Found</div>}>
  <Match when={state.route === "home"}>
   <Home />
  </Match>
  <Match when={state.route === "settings"}>
   <Settings />
  </Match>
</Switch>
<For 
  each={state.list} 
  fallback={<div>Loading...</div>}
>
  {(item) => <div>{item}</div>}
</For>
<Suspense fallback={<div>Loading...</div>}>
  <AsyncComponent />
</Suspense>
<ErrorBoundary 
  fallback={
  (<div>
     Something went terribly wrong
   </div>)
}
>
  <MyComp />
</ErrorBoundary>

Don't do logic outside of JSX or reactive primitives

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

const doubleCount = count() * 2 //NO NO

return (
    <div>
      <p>The double count is: {doubleCount}</p>
      <button onClick={() => setCount(count() + 1)}>Plus</button>
    </div>
  );
}

Derive the value in a function

 

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

const doubleCount = () => count() * 2 

return (
    <div>
      <p>The double count is: {doubleCount()}</p>
      <button onClick={() => setCount(count() + 1)}>Plus</button>
    </div>
  );
}

OR.....

In the JSX directly

 

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

return (
    <div>
      <p>The double count is: {count()*2}</p>
      <button onClick={() => setCount(count() + 1)}>Plus</button>
    </div>
  );
}

Don't destructure Props

function Greeting({salutation, name}){
   return <p>{salutation}, {name}</p>
}

// or

function Greeting(props){
   const {salutation, name} = props

   return <p>{salutation}, {name}</p>
}

Prop values could be reactive

props = {
  salutation:'Hello',
  get name(){
    return userName()
  }
}
<Greeting salution="Hello" name={userName()}/>

Instead use props in JSX

function Greeting(props}){
   return <p>{props.salutation}, {props.name}</p>
}
// default props
props = mergeProps({ name: "Smith" }, props);

// clone props
newProps = mergeProps(props);

// merge props
props = mergeProps(props, otherProps);

Solid also provides the splitProps and mergeProps utility functions.

 

These utilities allow you to safely split or merge props without breaking reactivity.

function MyComponent(props) {
  const [local, others] = splitProps(props, ["children"]);

  return (
    <>
      <div>{local.children}</div>
      <Child {...others} />
    </>
  );
}

Don't use React inline style objects

function MyInlineStyle()
   return (
   <p 
     style={{
       color:'blue',
       backgroundColor: 'olivedrab'
     }}
   >
    {/*...*/}
   </p>
 );
}

Use dash-separated property names

function MyInlineStyle()
   return (
   <p 
     style={{
       color:'blue',
       'background-color': 'olivedrab',
       '--my-custom-property': '100px'
     }}
   >
    {/*...*/}
   </p>
 );
}

or ....

Just use a string!!!!

function MyInlineStyle()
   return (
   <p 
     style={`
       color: blue;
       background-color': olivedrab;
       --my-custom-property: 100px;
     `}
   >
    {/*...*/}
   </p>
 );
}

There is no need for useRef or useCallback

import {  onMount } from "solid-js";

function MyForm() {
  const handleSubmit = (e) => {
    /* handle submit */
  };

  let myInput;
  
  onMount(() => myInput.focus());

  return (
    <form onSubmit={handleSubmit}>
      <input ref={myInput} />
    </form>
  );
}

Before you give up

There is an eslint plugin

npm install --save-dev eslint eslint-plugin-solid
# or
pnpm add --save-dev eslint eslint-plugin-solid
yarn add --dev eslint eslint-plugin-solid

# optional, to create an ESLint config file
npx eslint --init
# or
pnpm eslint --init
yarn eslint --init

Why Bother Learning Solid

StateofJS 2021 results

Solid has the highest Satisfaction rating

Solid.JS is Performant by default

Solid.JS has a robust ecosystem

  • Routers
  • UI Components
  • etc.

 

Shameless plug for Solid Bedrock Layout

For example, TanStack has "Solid" versions of their popular tools like TanStack-Query and TanStack Table

Solid Start Meta Framework

SolidStart enables you to render your application in different ways depending on your use case:

  • Client-side rendering (CSR)
  • Server-side rendering (SSR)
  • Streaming SSR
  • Static site generation (SSG)

Where do I go from here?

solidjs.com

Solidjs Discord

https://discord.gg/solidjs

Thank You

 

Twitter: @travisWaithMair

Blog:

non-traditional.dev

Intro to Solid.js for React Developers (or anyone else)

By Justin Travis Waith-Mair

Intro to Solid.js for React Developers (or anyone else)

Solid.js has an API that feels very familiar to React developers, but under the hood is completely different. Solid.js has a suite of “fine-grained reactive primitives.” It is these primitives that we will explore and learn how to use coming from the perspective of a React.js Developer.

  • 113