Skip to content

elite174/solid-awesome-hooks

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

45 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

solid-awesome-hooks

version npm

đź›  A collection of useful hooks for solid-js đź› 

Hook list

useAbortController

Definition

import { type Owner } from "solid-js";

type Options = {
  reason?: any;
  fallbackOwner?: Owner | null;
};

/**
 * Returns AbortController instance
 * Can be useful inside createResource
 * If there's no owner scope abort() won't be called
 */
export declare const useAbortController: ({ reason, fallbackOwner }?: Options) => AbortController;

Example

import { onMount } from "solid-js";
import { useAbortController } from "solid-awesome-hooks";

const Component = () => {
  onMount(() => {
    // The controller will call `abort` on cleanup
    const controller = useAbortController();

    fetch("some api endpont", { signal: controller.signal });
  });

  return null;
};

useAsyncAction

Definition

import { type Accessor, type Setter } from "solid-js";

type ActionState = "pending" | "resolved" | "errored" | "ready";

type TryAction = <T>(action: () => Promise<T>) => Promise<T>;

export type AsyncAction = {
  /** Pass an async function here */
  try: TryAction;
  state: Accessor<ActionState>;
  errorMessage: Accessor<string | undefined>;
  setErrorMessage: Setter<string | undefined>;
  /** Resets progress, error states and error message */
  reset: VoidFunction;
};

export declare const useAsyncAction: () => AsyncAction;

Example

import { Show } from "solid-js";
import { useAsyncAction } from "solid-awesome-hooks";

const Component = () => {
  const action = useAsyncAction();

  const handleClick = async () => {
    let data;

    try {
      data = await action.try(someFetch);
    } catch (error) {
      console.error(error);
    }

    console.log(data);
  };

  return (
    <section>
      <button onClick={handleClick} disabled={action.state() === "pending"}>
        click
      </button>
      <button onClick={action.reset} disabled={action.state() !== "errored"}>
        ResetError
      </button>
      <Show when={action.errorMessage()}>{(errorMessage) => <p>{errorMessage()}</p>}</Show>
    </section>
  );
};

useClickOutside

Definition

import { type Accessor, type Setter } from "solid-js";

export declare const useClickOutside: (
  callback: (e: MouseEvent) => void,
  options?: {
    /** Boolean signal which will trigger listening to the click event */
    enabled: Accessor<boolean>;
  }
) => Setter<HTMLElement | undefined>;

Example

const [isListeningEnabled] = createSignal(true);

const setElementRef = useClickOutside((event) => console.log(`Clicked outside: ${e}`, {
    enabled: isListeningEnabled
})

// somewhere in JSX
<section ref={setElementRef}>
    Listen to click outside of this section
</section>

useContextStrict

Definition

import { type Context } from "solid-js";
/**
 * Same as solid's useContext, but it throws an error if there's no context value
 * @param context
 * @param errorMessage
 */
export declare const useContextStrict: <T>(context: Context<T>, errorMessage?: string) => NonNullable<T>;

Example

import { createContext } from "solid-js";
import { useContextStrict } from "solid-awesome-hooks";

type ContextType = {
  text: string;
};

const SomeContext = createContext<ContextType>();

const SomeService = (props) => (
  <SomeContext.Provider value={{ text: "Some text" }}>{props.children}</SomeContext.Provider>
);

// ... somewhere in the component

const Component = () => {
  // No TS error!
  const { text } = useContextStrict(SomeContext);

  // ...
};

useModulePreloader

This hook preloads modules imported with lazy when the browser is idle one by one.

Definition

import { lazy } from "solid-js";
/**
 * Preloads modules imported with `lazy` when the browser is idle one by one
 * @param lazyModules
 */
export declare const useModulePreloader: (lazyModules: Array<ReturnType<typeof lazy>>) => void;

Example

import { lazy } from "solid-js";
import { useModulePreloader } from "solid-awesome-hooks";

const LazyPopoverContent = lazy(() => import("./PopoverContent"));

const Component = () => {
  /**
   * Sometimes it's useful to preload components
   * which are hidden from a user (e.g. date pickers, color pickers or some modal content).
   * At the same time we don't want to show suspense fallbacks for lazy components.
   * useModulePreloader hook can preload lazy modules when the browser is idle
   * and the user won't see any suspense fallbacks when the component renders.
   */
  useModulePreloader([LazyPopoverContent]);

  return (
    <Popover
      Content={
        <Suspense>
          <LazyPopoverContent />
        </Suspense>
      }
    />
  );
};

usePinchZoom

This hook detects pinch zoom (with only 2 pointers) on the tracking html element. Under the hood it uses touchmove event. The callbacks onZoomIn and onZoomOut are fired when touchmove event is fired.

Definition

import { type Setter } from "solid-js";

interface UsePinchZoomParams {
  /**
   * Callback to be called on zoom in
   * @param distanceGrowthPX - absolute distance growth between 2 pointers
   */
  onZoomIn?: (distanceGrowthPX: number) => void;
  /**
   * Callback to be called on zoom out
   * @param distanceGrowthPX - absolute distance growth between 2 pointers
   */
  onZoomOut?: (distanceGrowthPX: number) => void;
  options?: {
    /**
     * @default true
     */
    preventTouchMoveEvent?: boolean;
  };
}

export declare const usePinchZoom: ({ onZoomIn, onZoomOut, options }: UsePinchZoomParams) => Setter<HTMLElement>;

Example

const setElementRef = usePinchZoom({
  onZoomIn: (distanceGrowth) => console.log(`onZoomIn: ${distanceGrowth}`),
  onZoomOut: (distanceGrowth) => console.log(`onZoomOut: ${distanceGrowth}`)
})

// somewhere in JSX
<section ref={setElementRef}>
    Listen to pinch zoom in this component
</section>

usePolling

This hook can be useful when you need to implement polling for a resource

Definition

import { type Accessor, type Owner } from "solid-js";

type UsePollingOptions = {
  /**
   * Time interval to call "poll" function
   * @default 3000
   */
  timeInterval?: number;
  enabled?: Accessor<boolean>;
  /**
   * The maximum number of function calls
   * When request count exceeds this limit, polling stops
   * Pass Infinity to avoid this behavior if necessary
   * @default 10
   */
  callLimit?: number;
  /**
   * This hook uses setTimeout for polling, so it might be the case when `poll` function triggers reactive things.
   * To make it work correctly pass proper owner for the `poll` function.
   * Otherwise it will be assigned automatically (the owner of the hook will be used)
   */
  owner?: Owner | null;
};

/**
 * @param readyTrigger Reactive signal that tells that the poll function can now be scheduled
 * @param poll Function
 * @param options {UsePollingOptions}
 */
export declare const usePolling: (
  readyTrigger: Accessor<unknown>,
  poll: VoidFunction,
  options?: UsePollingOptions
) => void;

Example

import { usePolling } from "solid-awesome-hooks";
import { createResource } from "solid-js";

// Inside a component...
const [data, { refetch }] = createResource(() => fetchData());

usePolling(data, refetch, {
  enabled: () => data()?.status === GenerationStatus.IN_PROGRESS,
});

useSaveToStorage

This hook will save serializable signal data to some storage (default is localStorage)

Definition

import { type Accessor } from "solid-js";
type Serializable = number | string | boolean | object | null | undefined;
type SaveToStorageOptions = {
  /** @default localStorage */
  storage?: Storage;
  /**
   * If set to true it will save the data when the browser is idle
   * @default true
   */
  saveWhenIdle?: boolean;
  /**
   * If set to true it will save the data only after first change
   * (it passed to solid's `on` `defer` option)
   * @default true
   */
  defer?: boolean;
  /**
   * If set to true it will remove the key from storage if the data is null or undefined
   * @default false
   */
  clearOnEmpty?: boolean;
};
/**
 *
 * @param key - key name in storage
 * @param data - Reactive accessor to the data
 * @param options
 */
export declare const useSaveToStorage: <T extends Serializable>(
  key: string,
  data: Accessor<T>,
  options?: SaveToStorageOptions
) => void;

Example

import { createSignal } from "solid-js";
import { useSaveToStorage } from "solid-awesome-hooks";

const Component = () => {
  const [dataToSave] = createSignal("data");

  useSaveToStorage("app:data", dataToSave);

  // ...
};

useScrollTo

This hook comes in handy when you need to scroll some element on some trigger

Definition

import { type Accessor } from "solid-js";

interface Params extends ScrollOptions, ScrollToOptions {
  scrollTrigger: Accessor<unknown>;
  /**
   * if set to true scrolling will be skipped on initial rendering
   * @default true
   */
  defer?: boolean;
}

export declare const useScrollTo: <T extends HTMLElement>(params: Params) => import("solid-js").Setter<T>;

Example

import { useScrollTo } from "solid-awesome-hooks";

const Component = () => {
  // Get search params from the router
  const [searchParams] = useSearchParams();

  // scroll page content to the top when changing videos page
  const setScrollableElement = useScrollTo({
    scrollTrigger: () => searchParams.page,
    behavior: "smooth",
    top: 0,
  });

  return <div ref={setScrollableElement}>{/** Some content here */}</div>;
};

useSortState

Definition

export declare enum SortState {
  ASCENDING = 1,
  DESCENDING = -1,
}

export declare const useSortState: (initialSortState?: SortState) => {
  order: import("solid-js").Accessor<SortState>;
  setOrder: import("solid-js").Setter<SortState>;
  isAscending: import("solid-js").Accessor<boolean>;
  isDescending: import("solid-js").Accessor<boolean>;
  /** Switches order to another one */
  toggleOrder: () => SortState;
  /** Resets sort order to the initial */
  resetOrder: () => SortState;
};

useSyncState

You should use this hook only when you want to sync your props with local state.

Definition

import { type Accessor } from "solid-js";
/**
 * This hook may be used to sync your state (signals or stores) with props.
 * Basically it's just a shorthand for createComputed(on(source, setter, { defer }))
 * @param source Reactive signal
 * @param setter A function which runs immediately when source changes
 * @param defer A boolean value which is passed to on's defer option. Default - true.
 */
export declare const useSyncState: <T>(source: Accessor<T>, setter: (value: T) => void, defer?: boolean) => void;

Example

import { useSyncState } from "solid-awesome-hooks";
import { batch } from "solid-js";

// Somewhere inside datepicker component
// sync state with props
useSyncState(
  () => props.selectedDate,
  (selectedDate) => {
    if (!selectedDate) return;

    batch(() => {
      setCurrentYear(selectedDate.getFullYear());
      setCurrentMonth(selectedDate.getMonth());
      setCurrentDate(selectedDate.getDate());
    });
  }
);

useVisibleState

This hook is useful when you work with dropdowns or popovers. These things might be controlled by boolean state, so you don't need to write it every time.

Definition

type Action = "hide" | "reveal";

export declare const useVisibleState: (initialState?: boolean) => {
  isOpen: import("solid-js").Accessor<boolean>;
  setOpen: import("solid-js").Setter<boolean>;
  hide: () => false;
  reveal: () => true;
  /** A useful wrapper which adds `reveal` or `hide` action for wrapping function */
  withAction: <T extends any[], U>(action: Action, callback?: (...args: T) => U) => (...args: T) => U;
};

Example

import { Popover } from "some-lib";
import { useVisibleState } from "solid-awesome-hooks";

const Component = () => {
  const popover = useVisibleState();

  return (
    <Popover
      open={popover.isOpen()}
      onOpenChange={popover.setOpen}
      trigger={<button type="button">Popover trigger</button>}
      content={
        <div>
          <p>Some content</p>
          <button type="button" onClick={popover.hide}>
            Close popover
          </button>
        </div>
      }
    />
  );
};