Patrick Desjardins Blog
Patrick Desjardins picture from a conference

SolidJS: How to React on Property Change

Posted on: 2022-03-07

In this article, we will show a couple of ways to react to a change of property with SolidJS. There are many types of reactions:

  • We can recompute another variable. For example, a value is built upon a property.
  • We can execute code. For example, clearing a list or a canvas.

Derived Signal

The first case is called a derived signal. It is a well-documented case where you have a computed value that depends on another. However, the documentation is around derived signals and not props. But, the reality is that it behaves the same way hence the same mindset should be observed. By that, the derived value will automatically be recomputed when the property changes.

If we use an example with a single component that passes a single property, we can change the name every second.

interface HelloWorldProps {
  name: string;
}

const HelloWorld = (props: HelloWorldProps) => {
  return <h1>Hello, {props.name}!</h1>;
};

const names = ["World", "Test", "You"];
const App: Component = () => {
  const [index, setIndex] = createSignal(0);
  onMount(() => {
    const id = setInterval(() => {
      setIndex((prev) => {
        return prev + 1;
      });
    }, 1000);
    onCleanup(() => clearInterval(id));
  });
  return <HelloWorld name={names[index() % names.length]} />;
};

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

A derived property could be added in the HelloWorld component.

const HelloWorld = (props: HelloWorldProps) => {
  const derivedName = () => `[${props.name}]`;
  return <h1>Hello, {derivedName()}!</h1>;
};

In this example, the component will have the derivedName function called every time the property is changing. However, the HelloWorld itself is rendered only once. For example, writing this variation:

const HelloWorld = (props: HelloWorldProps) => {
  console.log("HelloWorld Rendered");
  const derivedName = () => {
    console.log("Derived Name Called");
    return `[${props.name}]`;
  };
  return <h1>Hello, {derivedName()}!</h1>;
};

The console output looks like this:

HelloWorld Rendered 
Derived Name Called (67 times)

So, the function is not re-created every second; only the function is called because it derives from the changing property.

Executing code

In the previous case, we were computing a value, but what if you need to execute a piece of code if the a property change? Similar to React, it is possible using an effect. With SignalJS, we need to use createEffect. What is excellent and confusing if you are coming from React Hooks, is that you do not need to explicitly specify in the creation of the effect which variable to look at. SolidJS framework handles the reactivity based on what is being used within the effect.

For example:

createEffect(() => {
  document.bgColor = document.bgColor === "red" ? "white" : "red";
});

The background will flip to red since the effect will be executed once when the HelloWorld is rendered (first execution of the component). What if we want to change the color every time the props.name change? For that, we need to use the props.name inside the createEffect. For example, adding a console.log using the property causes the SignalJS framework to know about the property. And, every change of the property must run the effect and hence make the color swap.

createEffect(() => {
  console.log(props.name); // Make the createEffect to be called at every change of the property "name"
  document.bgColor = document.bgColor === "red" ? "white" : "red";
});

So, there is a caveat here, what if we want to change the background without using console.log? Removing the console.log makes the component renders once, as we saw earlier. So, you can cheat your way to use the variable without assigning.

createEffect(() => {
  props.name; // Make the effect to depend on the property
  document.bgColor = document.bgColor === "red" ? "white" : "red";
});

However, there is an utility function that can be used that make the code cleaner.

  createEffect(
    on(
      () => props.name,
      () => {
        document.bgColor = document.bgColor === "red" ? "white" : "red";
      }
    )
  );
  return <h1>Hello, {derivedName()}!</h1>;
};

The on function takes a function that returns the property you want to watch. In that example, the function () => props.name cause the effect to be evaluated every time the reference of the name is changing. The second function of the on is the code you would put directly in the createEffect. For one rare time, SolidJS is more complex than React, and the documentation about on covers the case of a signal, not props which is confusing.

Conclusion

You can find all the code in CodeSandBox.io. SolidJS offers simplicity by removing the burden of using a Linter to avoid stale data in React by analyzing what is used within the body of the effect or by being smart enough to update the derivative function. The simplicity comes with the caveat that you may expect an effect to be called but it won't. For example, if you debug and use property in the console.log, the effect will suddenly behave differently. Or, if you remove the line in the last example that does nothing to the property, you will end up with a stale background color instead of an alternating one.