qwik icon indicating copy to clipboard operation
qwik copied to clipboard

[🐞] Strange behavior and errors using closures and props

Open robisim74 opened this issue 2 years ago • 1 comments

Which component is affected?

Qwik Runtime

Describe the bug

I'm creating a custom hook that returns a closure:

export const useFn = () => {
  const state = useContext(myContext);

  return (val: number) => {
    return (state.value + val).toString();
  };
};

The function is pure: it depends only on the parameter and always returns the same result, because myContext is static and immutable:

useContextProvider(myContext, {
  value: 0,
});

I use the function like this::

export const MyComponent = component$((props: IMyComponent) => {
  const count = useSignal(0);
  const c = useFn();

  return (
    <>
      <p>{props.val}</p>

      <p>{c(count.value)}</p>
    </>
  );
});

Then I call MyComponent with another closure in the props:

export default component$(() => {
  const c = useFn();

  return (
    <div>
      <MyComponent val={c(1)} />
    </div>
  );
});

I get the following error:

[vite] Internal server error: Code(3): Only primitive and object literals can be serialized

After numerous attempts, I discovered these situations:

  1. if i remove the function from the props, everything works
  2. if I leave the function in the props, and I don't pass the signal value to the function in the child component, everything works
  3. if I rename the function to makeFn (without initial use*) everything works (except the linter)

It is incomprehensible to me why the props of the parent component and the signal as a parameter in the child component are mutually exclusive.

Thanks

Reproduction

https://stackblitz.com/edit/qwik-starter-vdcrdc?file=src%2Froutes%2Findex.tsx

Steps to reproduce

No response

System Info

Not relevant

Additional Information

If I inspect the production files, I can see that in the case of use*, the function is considered immutable yes, but included in a derived signal _fnSignal:

const s_xYL1qOwPyDI = () => {
  const c = useFn();
  return /* @__PURE__ */ _jsxQ("div", null, null, /* @__PURE__ */ _jsxC(MyComponent, {
    get val() {
      return c(1);
    },
    [_IMMUTABLE]: {
      val: _fnSignal((p0) => p0(1), [
        c
      ], "p0(1)")
    }
  }, 3, "H1_1"), 1, "H1_2");
};

In the case of the function called with make*, instead it remains as is and everything works:

const s_xYL1qOwPyDI = () => {
  const c = makeFn();
  return /* @__PURE__ */ _jsxQ("div", null, null, /* @__PURE__ */ _jsxC(MyComponent, {
    val: c(1)
  }, 3, "H1_1"), 1, "H1_2");
};

robisim74 avatar May 03 '23 14:05 robisim74

Qwik requires that all of the state is serializable. A function closure is not serializable. To make it serializable, you need to wrap it with $()

export const useFn = () => {
  const state = useContext(myContext);

  return $((val: number) => {
    return (state.value + val).toString();
  });
};

this should do the trick.

I am going to close this issue because it is working as intended.

mhevery avatar Mar 29 '24 15:03 mhevery