đź› A collection of useful hooks for solid-js đź›
- useAbortController
- useAsyncAction
- useClickOutside
- useContextStrict
- useModulePreloader
- usePinchZoom
- usePolling
- useSaveToStorage
- useScrollTo
- useSortState
- useSyncState
- useVisibleState
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;
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;
};
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;
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>
);
};
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>;
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>
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>;
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);
// ...
};
This hook preloads modules imported with lazy
when the browser is idle one by one.
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;
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>
}
/>
);
};
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.
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>;
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>
This hook can be useful when you need to implement polling for a resource
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;
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,
});
This hook will save serializable signal data to some storage (default is localStorage
)
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;
import { createSignal } from "solid-js";
import { useSaveToStorage } from "solid-awesome-hooks";
const Component = () => {
const [dataToSave] = createSignal("data");
useSaveToStorage("app:data", dataToSave);
// ...
};
This hook comes in handy when you need to scroll some element on some trigger
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>;
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>;
};
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;
};
You should use this hook only when you want to sync your props with local state.
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;
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());
});
}
);
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.
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;
};
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>
}
/>
);
};