Patrick Desjardins Blog
Patrick Desjardins picture from a conference

SolidJS: CreateEffect without Triggering Initialization

Posted on: 2022-03-18

In SolidJS, it is possible to listen to a specific variable. When the reference change (object) or value (primitive type), the effect is triggered. Coming from React, you might relate to the useEffect hook. In the ecosystem of SolidJS, it is called createEffect.

React does not handle a use case well because you want to listen for change but not have the effect triggered on initialization. For example, if you're going to create an effect on a property of your component. You want to be notified when the property change. However, you do not want the inside of the effect function to be called upon initialization.

There is some workaround with React by creating a variable that keep track of if the component is mounted and inside the effect to have a condition to check if the variable is set. Like many aspects of SolidJS, it handles it for you. No need to pollute your code with these tricks. Instead, you can pass an option to the createEffect that will defer the first change.

Here is a full code sample of a small increment application that give the count to a child component. The child renders the property and has an effect that consoles the value as well. Without the defer option, the console display on rendering the value 1.

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

interface ChildProps {
  count: number;
}
const Child = (props: ChildProps) => {
  createEffect(() => {
    console.log(props.count);
  });
  return <p>{props.count ?? "-"}</p>;
};

const App: Component = () => {
  const [innerStateValue, setInnerStateValue] = createSignal(1);
  return (
    <div>
      <h1>Hello, world!</h1>
      <Child count={innerStateValue()} />
      <div>
        <button
          onclick={() => {
            setInnerStateValue((prev) => prev + 1);
          }}
        >
          Increment
        </button>
      </div>
    </div>
  );
};

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

What if we only want to console.log the user action. We want to skip the first effect. We can change the child component to use the createEffect with the on function.

const Child = (props: ChildProps) => {
  createEffect(
    on(
      () => props.count,
      () => console.log(props.count),
      {
        defer: true
      }
    )
  );
  return <p>{props.count ?? "-"}</p>;
};

The on gives the possibility to provide one or many dependencies that will trigger the effect if their reference change. In that case, SolidJS watches props.count. The second argument is the code to be executed. Lastly, in the third position is the options object to specify defer: true. With that in place, running the code will display the number 1 on the HTML but not in the console. However, if the user clicks the button, the value appears in the HTML and in the console. Thus, precisely what desired.

You can play with the complete code in CodeSandbox.io.