elements
lifecycle
browser
- useAudio
- useBattery
- useBluetooth
- useBreakpoints
- useBroadcastChannel
- useBrowserLocation
- useClipboard
- useCopy
- useCssVar
- useDisplayMedia
- useDocumentEvent
- useDocumentTitle
- useDocumentVisibility
- useEventListener
- useEventSource
- useEyeDropper
- useFavicon
- useFileSystemAccess
- useFps
- useFullscreen
- useGamepad
- useGeolocation
- useMeasure
- useMediaControls
- useMediaQuery
- useMemory
- useNetwork
- useObjectUrl
- useOnline
- useOtpCredential
- usePermission
- usePictureInPicture
- usePointerLock
- usePostMessage
- useRaf
- useShare
- useSpeechRecognition
- useSpeechSynthesis
- useSticky
- useVibrate
- useVirtualKeyboard
- useWakeLock
- useWebSocket
utilities
state
- useBoolean
- useControllableState
- useCookie
- useCookies
- useCounter
- useDefault
- useDisclosure
- useField
- useHash
- useList
- useLocalStorage
- useMap
- useMask
- useMergedRef
- useObject
- useOffsetPagination
- useQueue
- useRafState
- useRefState
- useSessionStorage
- useSet
- useStateHistory
- useStep
- useStorage
- useToggle
- useUrlSearchParam
- useUrlSearchParams
- useWizard
user
sensors
- useDeviceMotion
- useDeviceOrientation
- useHotkeys
- useIdle
- useInfiniteScroll
- useIntersectionObserver
- useKeyboard
- useKeyPress
- useKeysPressed
- useMouse
- useMutationObserver
- useOrientation
- usePageLeave
- useParallax
- usePerformanceObserver
- useResizeObserver
- useScroll
- useScrollIntoView
- useScrollTo
- useSwipe
- useTextSelection
- useVisibility
- useWindowEvent
- useWindowFocus
- useWindowScroll
- useWindowSize
Account settings
Update your public profile and preferences.
import type { SubmitEvent } from 'react';
import { createContext, useField } from '@siberiacancode/reactuse';
import { CheckIcon, ChevronDownIcon } from 'lucide-react';
import { memo } from 'react';
interface Profile {
bio: string;
email: string;
isPublic: boolean;
language: string;
name: string;
notifications: boolean;
}
const DEFAULT_PROFILE: Profile = {
name: 'siberiacancode',
email: 'hello@reactuse.org',
bio: 'Building open-source React hooks',
language: 'en',
notifications: true,
isPublic: false
};
const LANGUAGES = [
{ value: 'en', label: 'English' },
{ value: 'ru', label: 'Russian' },
{ value: 'de', label: 'German' }
];
const EMAIL_PATTERN = /^[^\s@]+@[^\s@][^\s.@]*\.[^\s@]+$/;
const profileContext = createContext<Profile>(DEFAULT_PROFILE);
const NameField = memo(() => {
const profile = profileContext.useSelect();
const nameField = useField(DEFAULT_PROFILE.name, { validateOnBlur: true });
return (
<div className='flex flex-col gap-1.5'>
<label className='text-foreground text-xs font-medium' htmlFor='name'>
Display name
</label>
<input
className='border-border bg-card text-foreground rounded-md border px-3 py-2 text-sm outline-none'
id='name'
placeholder='Your name'
{...nameField.register({
required: 'Name is required',
minLength: { value: 2, message: 'At least 2 characters' }
})}
onChange={(event) => {
nameField.register().onChange(event);
profile.set({ ...(profile.value as Profile), name: event.target.value });
}}
/>
{nameField.error && <span className='text-destructive text-xs'>{nameField.error}</span>}
</div>
);
});
NameField.displayName = 'NameField';
const EmailField = memo(() => {
const profile = profileContext.useSelect();
const emailField = useField(DEFAULT_PROFILE.email, { validateOnBlur: true });
return (
<div className='flex flex-col gap-1.5'>
<label className='text-foreground text-xs font-medium' htmlFor='email'>
Email
</label>
<input
className='border-border bg-card text-foreground rounded-md border px-3 py-2 text-sm outline-none'
id='email'
placeholder='you@example.com'
type='email'
{...emailField.register({
required: 'Email is required',
pattern: { value: EMAIL_PATTERN, message: 'Invalid email format' }
})}
onChange={(event) => {
emailField.register().onChange(event);
profile.set({ ...(profile.value as Profile), email: event.target.value });
}}
/>
{emailField.error && <span className='text-destructive text-xs'>{emailField.error}</span>}
</div>
);
});
EmailField.displayName = 'EmailField';
const BioField = memo(() => {
const profile = profileContext.useSelect();
const bioField = useField(DEFAULT_PROFILE.bio);
return (
<div className='flex flex-col gap-1.5'>
<label className='text-foreground text-xs font-medium' htmlFor='bio'>
Bio
</label>
<textarea
className='border-border bg-card text-foreground min-h-[72px] resize-none rounded-md border px-3 py-2 text-sm outline-none'
id='bio'
placeholder='Tell something about yourself...'
{...bioField.register()}
onChange={(event) => {
bioField.register().onChange(event);
profile.set({ ...(profile.value as Profile), bio: event.target.value });
}}
/>
</div>
);
});
BioField.displayName = 'BioField';
const NotificationsField = memo(() => {
const profile = profileContext.useSelect();
const notificationsField = useField(DEFAULT_PROFILE.notifications);
return (
<label className='flex cursor-pointer items-start justify-between gap-3'>
<div className='flex flex-col gap-0.5'>
<span className='text-foreground text-xs font-medium'>Email notifications</span>
<span className='text-muted-foreground text-[11px]'>
Receive product updates and release notes
</span>
</div>
<input
role='switch'
type='checkbox'
{...notificationsField.register()}
onChange={(event) => {
notificationsField.register().onChange(event);
profile.set({ ...(profile.value as Profile), notifications: event.target.checked });
}}
/>
</label>
);
});
NotificationsField.displayName = 'NotificationsField';
const LanguageField = memo(() => {
const profile = profileContext.useSelect();
const languageField = useField(DEFAULT_PROFILE.language);
return (
<div className='flex items-center justify-between gap-3'>
<div className='flex flex-col gap-0.5'>
<label className='text-foreground text-xs font-medium' htmlFor='language'>
Language
</label>
<span className='text-muted-foreground text-[11px]'>Choose your preferred language</span>
</div>
<div className='relative'>
<select
className='border-border bg-card text-foreground w-32 appearance-none rounded-md border py-1.5 pr-7 pl-3 text-xs outline-none'
id='language'
{...languageField.register()}
onChange={(event) => {
languageField.register().onChange(event);
profile.set({ ...(profile.value as Profile), language: event.target.value });
}}
>
{LANGUAGES.map((language) => (
<option key={language.value} value={language.value}>
{language.label}
</option>
))}
</select>
<ChevronDownIcon className='text-muted-foreground pointer-events-none absolute top-1/2 right-2 size-3.5 -translate-y-1/2' />
</div>
</div>
);
});
LanguageField.displayName = 'LanguageField';
const PublicField = memo(() => {
const profile = profileContext.useSelect();
const publicField = useField(DEFAULT_PROFILE.isPublic);
const isPublic = publicField.watch();
return (
<label className='flex cursor-pointer items-start gap-3'>
<span className='mt-0.5 flex shrink-0 items-center'>
<input
className='peer sr-only'
type='checkbox'
{...publicField.register()}
onChange={(event) => {
publicField.register().onChange(event);
profile.set({ ...(profile.value as Profile), isPublic: event.target.checked });
}}
/>
<span className='border-border peer-checked:border-foreground peer-checked:bg-foreground flex size-4 items-center justify-center rounded-[5px] border transition-colors'>
{isPublic && <CheckIcon className='text-background size-3' strokeWidth={3.5} />}
</span>
</span>
<div className='flex flex-col gap-0.5'>
<span className='text-foreground text-xs font-medium'>Make profile public</span>
<span className='text-muted-foreground text-[11px]'>
Anyone on the internet can see your profile
</span>
</div>
</label>
);
});
PublicField.displayName = 'PublicField';
const ProfileForm = () => {
const onSubmit = (event: SubmitEvent<HTMLFormElement>) => {
event.preventDefault();
};
return (
<form className='flex flex-col gap-4' onSubmit={onSubmit}>
<NameField />
<EmailField />
<BioField />
<div className='border-border flex flex-col gap-3 border-t pt-4'>
<NotificationsField />
<LanguageField />
</div>
<PublicField />
<div className='flex justify-end'>
<button type='submit'>Save changes</button>
</div>
</form>
);
};
const Demo = () => (
<profileContext.Provider initialValue={DEFAULT_PROFILE}>
<section className='flex w-full max-w-md flex-col gap-3 p-4'>
<div className='flex flex-col gap-1'>
<h2 className='text-foreground text-sm font-semibold'>Account settings</h2>
<p className='text-muted-foreground text-xs'>Update your public profile and preferences.</p>
</div>
<ProfileForm />
</section>
</profileContext.Provider>
);
export default Demo;
Installation
pnpm add @siberiacancode/reactuseUsage
const { useSelect, instance, Provider } = createContext<number>(0);Type Declarations
import type { JSX, ReactNode } from 'react';
export interface CreateContextOptions {
/** Display name for the context (useful for debugging) */
name?: string;
/** Whether to throw an error if context is used outside of Provider */
strict?: boolean;
}
export interface ContextValue<Value> {
/** The context value */
value: Value | undefined;
/** The context set function */
set: (value: Value) => void;
}
export interface ProviderProps<Value> {
/** The children */
children?: ReactNode;
/** The initial value */
initialValue?: Value;
}
export interface CreateContextReturn<Value> {
/** The context instance */
instance: React.Context<ContextValue<Value>>;
/** The selector hook */
useSelect: {
<Selected>(selector: (value: Value) => Selected): Selected;
(): ContextValue<Value>;
};
/** The provider component */
Provider: (props: ProviderProps<Value>) => JSX.Element;
}API
Parameters
| Name | Type | Default | Note |
|---|---|---|---|
| defaultValue | Value | undefined | - | - Default value for the context |
| options | CreateContextOptions<Value> | - | - Additional options for context creation |
Returns
CreateContextReturn<Value>Contributors
ddebabin
Last updated on