Some utility hooks.
useTheme
returns 'dark' or 'light' according to prefers-color-scheme media query.
It also changes document root color-scheme accordingly.
import { useTheme } from '@saramorillon/hooks'
function MyComponent() {
const theme = useTheme()
return theme === 'dark' ? <Dark /> : <Light />
}
useTheme(): Theme
None
Theme
- The theme ("light" or "dark").
useQuery
provides an easy way to make any action that triggers a loading, may return some data and may generate an error (typical API call use case).
import { useQuery } from '@saramorillon/hooks'
import { getData } from './service'
function MyComponent() {
const { execute, loading, error, result } = useQuery(getData)
if (loading) return <Spinner />
if (error) return <Error />
return (
<>
<Button onClick={execute}>Load data</Button>
<Table data={result} />
</>
)
}
useQuery<T>(queryFn: () => Promise<T>, options?: IQueryOptions<T>): IQueryResult<T>
queryFn: () => Promise<T>
- The function to run.
options?: IQueryOptions<T>
- An object containing:
-
autoRun?: boolean
- indicates weither of not the query should be run automatically on component mount -
defaultValue?: T
- the default value returned by the hook
An object containing:
-
execute: () => Promise<void>
- the function to execute the action -
loading: boolean
- indicates weither the action is currently pending or not -
error?: unknown
- the error potentially generated by the action -
result: T
- the value returned by the action
useQuery
instead.
useFetch
provides an easy way to make any action that triggers a loading, returns some data and may generate an error (typical API call use case).
It also provides a replay function to replay the action and a replace function to replace the data.
import { useFetch } from '@saramorillon/hooks'
import { getData } from './service'
function MyComponent() {
const [data, status, refresh] = useFetch(getData, null)
if (status.loading) return <Spinner />
if (status.error) return <Error />
return (
<>
<Button onClick={refresh} />
<Table data={data} />
</>
)
}
useFetch<T>(fetchFn: () => Promise<T>, defaultValue: T, autoFetch: boolean): [T, IFetchedStatus, () => void, React.Dispatch<React.SetStateAction<T>>]
fetchFn: () => Promise<T>
- The action to run.
defaultValue: T
- The default value to return.
autoFetch: boolean
- Weither the fetch function should be run on mount or not. Default is true
.
An array containing:
-
T
- the value returned by the action -
IFetchedStatus
- an object containing-
loading: boolean
- indicates weither the action is currently pending or not -
error: unknown
- the error potentially generated by the action
-
-
() => void
- a replay function -
React.Dispatch<React.SetStateAction<T>>
- a replace function
usePagination
provides an easy way to generate a full pagination system.
import { usePagination } from '@saramorillon/hooks'
import { getData } from './service'
const limit = 10
function MyComponent() {
const { page, maxPages, setMaxPages, first, previous, next, last, canPrevious, canNext } = usePagination()
const fetch = useCallback(() => getData(page, limit), [page])
const [{ data, total }] = useFetch(fetch, { data: [], total: 0 })
useEffect(() => {
setMaxPages(Math.ceil(total / limit))
}, [setMaxPages, total])
}
return (
<div>
<button disabled={!canPrevious} onClick={first}>
First page
</button>
<button disabled={!canPrevious} onClick={previous}>
Previous page
</button>
<span>
Page {page} of {maxPages}
</span>
<button disabled={!canNext} onClick={next}>
Next page
</button>
<button disabled={!canNext} onClick={last}>
Last page
</button>
</div>
)
usePagination(maxPage: number, initialValue = 1): IPagination
maxPage: number
- The maximum page
initialValue?: number
- The initial page (default 1).
An object containing:
-
page: number
- the current page -
goTo: React.Dispatch<React.SetStateAction<number>>
- a function to go to a specific page -
first: () => void
- a function to go to the first page -
previous: () => void
- a function to go to the previous page -
next: () => void
- a function to go to the next page -
last: () => void
- a function to go to the last page -
canPrevious: boolean
- indicates weither the navigation to the previous page is possible -
canNext: boolean
- indicates weither the navigation to the next page is possible
useForm
provides helpers to handle forms.
import { useForm } from '@saramorillon/hooks'
type Data {
name: string
}
function MyComponent({ data }: { data: Data }) {
const save = useCallback((values: Data) => {
console.log(values)
}, [])
const { values, submit, onChange, reset } = useForm(save, data)
return (
<form onSubmit={submit} onReset={reset}>
<input value={values.name} onChange={(e) => onChange('name', e.target.value)} />
<button type='submit'>Submit</button>
<button type='reset'>Reset</button>
</form>
)
}
useForm<T>(props: IFormProps<T>): IForm<T>
save: (values: T) => void
- The save action.
initialValues: T
- The initial values.
An object containing:
-
values: T
- the form values -
onChange: <K extends keyof T>(name: K, value: T[K]) => void
- a function to change the values of the form -
submit: (e: FormEvent) => void
- a function for submitting a form -
reset: () => void
- a function to reset the form to its initial values -
loading: boolean
- indicates when the form is being submitted -
error?: unknown
- contains a potential error thrown by the save function
useCopy
provides helpers to handle clipboard copy.
import { useCopy } from '@saramorillon/hooks'
function MyComponent() {
const [authorized, status, copy] = useCopy()
if (status.loading) return <Spinner />
if (status.error) return <Error />
return (
<button onClick={() => copy('Something')} disabled={!authorized}>
Copy
</button>
)
}
useCopy(): [boolean, ICopyStatus, () => void]
None
An array containing:
-
authorized: boolean
- indicates weither the user authorized clipboard actions on their brower -
ICopyStatus
- an object containing-
loading: boolean
- indicates weither the copy is currently pending or not -
error: unknown
- the error potentially generated by the copy
-
-
(data: string) => void
- the copy function
useDrag
provides helpers to help dragging elements.
import { useDrag, useDrop } from '@saramorillon/hooks'
function MyList() {
const [isDragged, events] = useDrag()
const onDrop = useCallback((source: string, target: string) => {
console.log(`Dragged ${source} on ${target}`)
}, [])
return (
<ul>
<MyListSlot item="Item1" onDrop={onDrop} />
<MyListSlot item="Item2" onDrop={onDrop} />
<MyListSlot item="Item3" onDrop={onDrop} />
</ul>
)
}
function MyListSlot({ item, onDrop }: { item: string; onDrop: (source: string, target: string) => void }) {
const onMove = useCallback((source: string) => onDrop(source, item), [item])
const [isOver, events] = useDrop(onMove)
return (
<div {...events} style={{ backgroundColor: isOver ? 'yellow' : 'transparent' }}>
<MyListItem item={item} />
</div>
)
}
function MyListItem({ item }: { item: string }) {
const [isDragged, events] = useDrag(item)
return (
<div draggable {...events} style={{ color: isDragged ? 'red' : 'blue' }}>
{item}
</div>
)
}
useDrag(source: string): [boolean, IDragEvents]
useDrop(onDrop: (source: string) => void): [boolean, IDropEvents]
source: string
- The source element. Don't forget to memoize stringified JSON objects.
onDrop: (source: string) => void
- The drop action.
An array containing:
-
isDragged: boolean
- indicates weither the item is currently dragged -
IDragEvents
- an object containing events to attach to the draggable item
An array containing:
-
isOver: boolean
- indicates weither a dragged item is currently over the target -
IDropEvents
- an object containing events to attach to the target
useDialog
provides helpers to show and hide native HTML dialogs.
import { useDialog } from '@saramorillon/hooks'
function MyComponent() {
const { ref, visible, show, hide } = useDialog()
return (
<>
<button onClick={show}>Show dialog</button>
<dialog ref={ref} onClick={hide}>
{visible && (
<div onClick={(e) => e.stopPropagation()}>
<button onClick={hide}>Hide dialog</button>
</div>
)}
</dialog>
</>
)
}
useDialog(): IDialog
None
An object containing:
-
ref: RefObject<HTMLDialogElement>
- the dialog ref -
visible: boolean
- indicates weither the dialog is visible or not -
show: () => void
- a function to show the dialog -
hide: () => void
- a function to hide the dialog
Any PR is welcomed! Please be sure to meet following requirements when posting a PR:
- Unit tests pass and code coverage is over 90%
- Code conventions have been respected (
yarn lint
can help) - Code is properly formatted (
yarn format
can help)