typescriptsolid-js

Trying to understand the actual usecases of splitProps and mergeProps in SolidJS


import { Accessor, mergeProps, splitProps } from "solid-js";

type ButtonProps = {
  count: Accessor<number>;
  listener: (step: number, _event: MouseEvent) => void;
};

export default function Button(props: ButtonProps) {
  const [coll1, coll2] = splitProps(props, ["count"], ["listener"]); // split props helps in grouping props into smaller objects
  const propsBack = mergeProps(coll1, coll2); // merges back smaller sub objects into bigger objects
  const { count, listener } = propsBack;
  const { count: _c, listener: _l } = props; //why you need merge and split even?
  const { count: nC, listener: nL } = { ...props }; //why you need merge and split even?
  return (
    <button
      style={{
        width: "80px",
        padding: "5px",
        "font-size": "24px",
        margin: "10px",
        color: "white",
        "background-color": nC() % 2 == 0 ? "lightpink" : "lightblue",
      }}
      onClick={[nL, 2]} //delegated-event
    >
      {nC()}
    </button>
  );
}
import { createSignal } from "solid-js";
import Button from "./Button";

export default function Counter() {
  const [count, setCount] = createSignal(0);
  const listener = (step: number, event: MouseEvent) => {
    event.stopPropagation(); 
    console.log("Button Clicked!");
    setCount((c) => c + step);
  };
  return (
    <div
      onClick={() => {
        console.log("Div Clicked!");
        setCount((c) => c + 1);
      }}
      style={{
        "background-color": "lightyellow",
        padding: "10px",
        margin: "15px",
      }}
    >
      <p
        style={{
          color: "black",
        }}
      >
        {count()}
      </p>
      <Button count={count} listener={listener} />
    </div>
  );
}

I am trying to understand for what reason these 2 functions exists? In docs it says helps to retain reactivity that normally we can't do with de-structuring or Object.assign(). But My code works perfectly doesn't matter what way I do! So I am really not understanding how these two functions are useful even?


Solution

  • In SolidJS, reactivity is relayed via function calls. In the following example, double is not a reactive value, but it behaves like one because of how the SolidJS runtime executes computations:

    const [count, setCount] = createSignal(0);
    
    const double = () => count() * 2;
    createEffect(() => console.log(double()));
    

    The SolidJS runtime maintains a stack of currently executing computations and tracks any signals accessed during their execution. This is how it associates the effect with the count signal—even though the effect never calls count() directly. Instead, it calls double(), which internally accesses count().

    This behavior is especially important when working with component props. Solid automatically preserves reactivity when a signal is passed directly as a prop by converting it into a getter function under the hood. If you inspect the compiled code, you’ll see that Solid relies on getter functions when passing a reactive value as a prop:

    <Button count={count()} />
    

    Which produces the following code:

    _$createComponent(Button, {
      get count() {
        return count();
      }
    });
    

    You need to maintain the function calls when splitting and merging if you are going to pass those values around.

    For example, the count property on the props object in the Button component is a reactive value, if you destructure it, you will get a non-reactive value:

    const { count } = props; // count is a static value.
    

    To manipulate reactive objects without breaking their reactivity, Solid provides helper functions: splitProps and mergeProps.

    splitProps creates reactive objects from a reactive source, and mergeProps combines multiple reactive sources into a single reactive object.

    A common use case for these functions is forwarding props. Suppose you have a Button component and want to create a variation like PrimaryButton. You can wrap the existing component in a new one, relaying the props directly to the inner component:

    const PrimaryButton = (props) => {
      return <Button {...props} class="is-primary" />
    };
    

    However, it may not always be desirable to pass all props through. Doing so could introduce unintended attributes or make it harder to override specific ones. With splitProps and mergeProps, you can selectively forward a subset of props or override them while preserving their reactivity—making your components more robust and deliberate in how they manage incoming values.

    const PrimaryButton = (props) => {
      const [local, others] = splitProps(props, ["onFocus"]);
    
      const handleFocus = (event) => {
        // Your custom behavior here
        local.onFocus?.(event);
      };
    
      const merged = mergeProps(others, {
        class: "is-primary",
        onFocus: handleFocus
      });
    
      return <Button {...merged} />;
    };
    

    Another common use case is when building a wrapper component that collects and distributes props across multiple child components. For example, imagine a Slider that manages layout and behavior and delegates props to child components such as Images, Buttons, and Dots:

    const Slider = (props) => {
      const [imagesProps, buttonsProps, rest] = splitProps(
        props,
        ["images"],
        ["prevLabel", "nextLabel"]
      );
    
      return (
        <div class="slider">
          <Images items={imagesProps.images} />
          <Buttons prevLabel={buttonsProps.prevLabel} nextLabel={buttonsProps.nextLabel} />
          <Dots count={imagesProps.images.length} onSelect={rest.onSelect} />
        </div>
      );
    };
    

    In this example, splitProps separates the props used by specific subcomponents so that they can be applied independently without breaking reactivity. This pattern keeps our wrapper component flexible and ensures proper prop distribution.

    Disclaimer: Portions of this explanation are adapted from material originally written for SolidJS: The Complete Guide, which explores these concepts in greater depth and practical context.