@web-im/react
v1.0.1
Published
Web-IM React utilities (hooks, contexts)
Readme
@web-im/react
Client-side React hooks and a tiny generic context. Zero non-React deps.
pnpm add @web-im/react react react-dom
# or
npm install @web-im/react react react-domESM only. React 18+ peer.
Imports
import { useDebouncedValue, useTimeout } from '@web-im/react/hooks';
import { AppProvider, useAppContext } from '@web-im/react/contexts';
// or flat barrel:
import { useDebouncedValue, AppProvider } from '@web-im/react';Hooks
useDebouncedValue<T>(value, delay = 300): T
const debounced = useDebouncedValue(query, 300);
useEffect(() => {
fetchResults(debounced);
}, [debounced]);useDebounceEffect(fn, waitTime, deps)
useEffect with a debounce. Runs fn(...deps) after waitTime ms of stillness.
useDebounceEffect(([q]) => {
search(q);
}, 250, [query]);useTimeout(callback, delay | null)
Setup a one-shot timer that uses the latest callback. Pass null to pause.
useTimeout(() => setVisible(false), open ? 3000 : null);useInterval(callback, delay | null)
Same as useTimeout but recurring.
useInterval(() => refetch(), 5000);useIsomorphicLayoutEffect
useLayoutEffect in browsers, useEffect during SSR.
useIsomorphicLayoutEffect(() => measureLayout(), [size]);useMounted(): () => boolean
const isMounted = useMounted();
fetch('/x').then(r => { if (isMounted()) setData(r); });useIsInit(): boolean
true on first render, false thereafter.
if (useIsInit()) {
console.log('first paint');
}useIsMobile(): boolean
Tracks window.innerWidth < 768 via matchMedia.
const isMobile = useIsMobile();
return isMobile ? <MobileNav/> : <DesktopNav/>;useCountdown(date)
const { days, hours, minutes, seconds } = useCountdown('2026-12-31T23:59:59Z');useScript(src, options?): 'idle' | 'loading' | 'ready' | 'error'
Inject a <script> tag once and watch its load state.
const status = useScript('https://js.stripe.com/v3/', { position: 'head-end' });
if (status === 'ready') initStripe();useTabs<T>(defaultTab)
const { currentTab, setCurrentTab, onChangeTab } = useTabs(0);
<Tabs value={currentTab} onChange={onChangeTab}>...</Tabs>useTable(props?)
Table state for sort/paginate/select. Plus three pure helpers: descendingComparator, getComparator, emptyRows.
const t = useTable({ defaultOrderBy: 'created_at', defaultRowsPerPage: 10 });
const sorted = [...rows].sort(getComparator<Row>(t.order, 'created_at'));
const empty = emptyRows(t.page, t.rowsPerPage, rows.length);Contexts
AppProvider<T> / useAppContext<T>()
Generic state container — useful as a building block when you don't need a reducer.
import { AppProvider, useAppContext } from '@web-im/react/contexts';
type State = { count: number };
function Counter() {
const { state, setState } = useAppContext<State>();
return (
<button onClick={() => setState({ count: state.count + 1 })}>
{state.count}
</button>
);
}
export default function App() {
return (
<AppProvider initial={{ count: 0 }}>
<Counter/>
</AppProvider>
);
}useAppContext throws if used outside <AppProvider>.
Repository
github.com/morsaken/web-im-helpers — see the workspace README for the companion @web-im/utils package.
License
MIT
