#What is the best way to pass reactive values to custom hooks?

10 messages · Page 1 of 1 (latest)

final nebula
#

I have a custom hook that freezes a passed text, once it changes to an empty string, for a set time to avoid the UI jumping during animations. Since I am accessing the text directly in track of useTask$, it is not a property access and thats why useTask$ is not triggered when the text changes.

// hooks/useFrozenText.ts

export function useFrozenText(text: string | undefined, delay: number) {
  const frozenText = useSignal<string | undefined>();

  useTask$(({ track }) => {
    const nextText = track(() => text);
    if (isBrowser && !nextText) {
      setTimeout(() => (frozenText.value = nextText), delay);
    } else {
      frozenText.value = nextText;
    }
  });

  return frozenText;
}

// components/Response.tsx

type ResponseProps = Partial<{
    status: 'info' | 'error' | 'success';
    message: string;
}>;

export const Response = component$(({ status, message }: ResponseProps) => {
  // Freeze message while element collapses to prevent UI from jumping
  const frozenMessage = useFrozenText(message, 200);

  return (
    <Expandable expanded={!!message}>
        {frozenMessage.value}
    </Expandable>
  );
});
#

Using an object with a getter works, but the DX is pretty bad because a lot of code has to be written. So I wonder if there is a better way.

// hooks/useFrozenText.ts

export function useFrozenText(text: { value: string | undefined }, delay: number) {
  const frozenText = useSignal<string | undefined>();

  useTask$(({ track }) => {
    const nextText = track(() => text.value);
    if (isBrowser && !nextText) {
      setTimeout(() => (frozenText.value = nextText), delay);
    } else {
      frozenText.value = nextText;
    }
  });

  return frozenText;
}

// components/Response.tsx

type ResponseProps = Partial<{
    status: 'info' | 'error' | 'success';
    message: string;
}>;

export const Response = component$((props: ResponseProps) => {
  // Freeze message while element collapses to prevent UI from jumping
  const frozenMessage = useFrozenText(
    {
      get value() {
        return props.message;
      },
    },
    200
  );

  return (
    <Expandable expanded={!!message}>
        {frozenMessage.value}
    </Expandable>
  );
});

Furthermore, I wonder if the reactivity breaks or the rendering is no longer fine-granular when I destruct the props.

mild wharf
#

I'd be quiet frustrated if a tracked value within a task does not react to changes - hopefully it has not happened to me yet

#

I'd double check upstream of the component tree if the props that the Reponse receives is what I expect

final nebula
#

When a raw value (no signal) is passed to a custom hook, useTask$ tracking does not work. In my reproduction, there is a counter signal in the routes/index.tsx file. When the counter is incremented, the useTask$ of hooks/useCustom1.ts and hooks/useCustom2.ts should log the number. However, useCustom1 runs, but the tracking in useTask$ does not work which is why the value is not logged to the console.

mild wharf
#

I see

#

kinda makes sense to me; or maybe not 🤔

final nebula
#

I also tried to build it with a function e.g. useFrozenTask$(() => value, 200) but without success.

mild wharf
#
export const freezeText = $(() => {
  // ...
});

export const Response = component$((props: ResponseProps) => {
  const frozenMessage = useSignal<string>();
  useTask(async () => {
    // ...
    frozenMessage.value = await freezeText(...)
  });

  return (
    <Expandable expanded={!!message}>
        {frozenMessage.value}
    </Expandable>
  );
});