@shaquillehinds/react-native-essentials
v1.13.0
Published
Essential constants, styles and hooks for react native.
Downloads
1,389
Maintainers
Readme
react-native-essentials
API Documentation
Complete API reference for React Native Essentials.
Table of Contents
Constants
Device Constants
Access device dimensions and platform information.
Exports
import {
SCREEN_WIDTH,
SCREEN_HEIGHT,
WINDOW_WIDTH,
WINDOW_HEIGHT,
initialOrientation,
aspectRatio,
isSmallDevice,
isLargeDevice,
isIOS,
isWeb,
isTablet,
isIpad,
isAndroid,
type DeviceOrientation,
type ScaledSizeDimensions,
} from '@shaquillehinds/react-native-essentials';Values
SCREEN_WIDTH: number- Full screen widthSCREEN_HEIGHT: number- Full screen heightWINDOW_WIDTH: number- Window width (excludes system UI)WINDOW_HEIGHT: number- Window height (excludes system UI)initialOrientation: DeviceOrientation- Initial device orientation ('portrait' | 'landscape')aspectRatio: number- Screen aspect ratioisSmallDevice: boolean- True if screen is smaller than 375x750isLargeDevice: boolean- True if screen width > 1100isIOS: boolean | undefined- True if iOS platformisWeb: boolean | undefined- True if web platformisTablet: boolean- True if aspect ratio <= 1.6isIpad: boolean- True if iOS tabletisAndroid: boolean | undefined- True if Android platform
Components
Layouts
Flexible layout components with responsive spacing and styling.
Layout
Versatile container component with extensive layout options.
💡 Important: When passing numbers to
widthandheightprops, they are automatically treated as percentages of screen dimensions.width={80}means 80% of screen width,height={50}means 50% of screen height. The component internally appliesrelativeX()andrelativeY()conversions. You can also pass string values like'100%'or'50px'for standard React Native sizing.
import { Layout } from '@shaquillehinds/react-native-essentials';
<Layout
center
padding={[5]}
backgroundColor="#ffffff"
width={90} // 90% of screen width (converted internally)
height={50} // 50% of screen height (converted internally)
>
{children}
</Layout>;Props:
interface LayoutProps<Scrollable extends boolean | undefined = undefined> {
// Spacing
margin?: Spacing;
padding?: Spacing;
// Dimensions (percentages of screen or DimensionValue)
// IMPORTANT: Numbers are automatically converted to percentages
// width={80} → 80% of screen width (relativeX applied internally)
// height={50} → 50% of screen height (relativeY applied internally)
width?: DimensionValue;
height?: DimensionValue;
// Flex
flex?: [number] | [number, number] | [number, number, DimensionValue];
flexDirection?: 'row' | 'column' | 'row-reverse' | 'column-reverse';
wrap?: boolean;
// Alignment
center?: boolean; // Center both axes
centerX?: boolean; // Center horizontally
alignSelf?: FlexAlignType;
spaceEnd?: boolean;
spaceStart?: boolean;
spaceEven?: boolean;
spaceCenter?: boolean;
spaceBetween?: boolean;
// Position
absolute?: boolean;
top?: DimensionValue;
bottom?: DimensionValue;
left?: DimensionValue;
right?: DimensionValue;
// Appearance
backgroundColor?: string;
// States
loading?: LoadingIndicatorProps | boolean;
skeleton?: { colors?: string[] } | boolean;
// Scrolling
scrollable?: Scrollable;
// Standard props
style?: StyleProp<ViewStyle>;
children?: ReactNode;
}Spacing Type:
type Spaces = [number, number?, number?, number?];
type Spacing = {
/**
* This padding/margin shorthand follows the classic CSS shorthand.
* Numbers are used as percentages of screen width or height.
* - [10] = {top: 10%, right: 10%, bottom: 10%, left: 10%}
* - [10, 20] = {top: 10%, right: 20%, bottom: 10%, left: 20%}
* - [10, 20, 30] = {top: 10%, right: 20%, bottom: 30%, left: 20%}
* - [10, 20, 30, 40] = {top: 10%, right: 20%, bottom: 30%, left: 40%}
*/
padding?: Spaces;
margin?: Spaces;
};RowLayout
Horizontal layout component (shorthand for flexDirection='row').
import { RowLayout } from '@shaquillehinds/react-native-essentials';
<RowLayout spaceBetween padding={[0, 4]}>
<Text>Left</Text>
<Text>Right</Text>
</RowLayout>;ScreenLayout
Full-screen container with safe area support.
import { ScreenLayout } from '@shaquillehinds/react-native-essentials';
<ScreenLayout backgroundColor="#f5f5f5">{children}</ScreenLayout>;Props:
interface ScreenLayoutProps extends LayoutProps {
// Inherits all Layout props
}AnimatedLayout
Layout component using Reanimated's Animated.View and Animated.ScrollView instead of standard React Native views. Identical props to Layout, but renders with Reanimated components for better animation performance.
import { AnimatedLayout } from '@shaquillehinds/react-native-essentials';
import { useSharedValue, useAnimatedStyle } from 'react-native-reanimated';
function Component() {
const opacity = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
opacity: opacity.value,
}));
return (
<AnimatedLayout center padding={[5]} style={animatedStyle}>
{children}
</AnimatedLayout>
);
}Props:
interface AnimatedLayoutProps extends LayoutProps {
// Identical to LayoutProps - uses Reanimated components internally
}Typography
Text components with consistent styling.
BaseText
Foundation text component with font management.
import { BaseText } from '@shaquillehinds/react-native-essentials';
<BaseText fontSize="large" fontStyle="Bold" customColor="#000000">
Text Content
</BaseText>;Props:
interface BaseTextProps extends TextProps {
fontSize?: FontSize;
fontStyle?: FontStyle;
customColor?: string;
animate?: boolean;
animatedStyle?: AnimatedStyleProp<TextStyle>;
children?: ReactNode;
}
type FontSize =
| 'headingL'
| 'headingM'
| 'headingS'
| 'titleL'
| 'titleM'
| 'titleS'
| 'bodyL'
| 'bodyM'
| 'bodyS';
type FontStyle =
| 'Thin'
| 'Extra Light'
| 'Light'
| 'Regular'
| 'Medium'
| 'SemiBold'
| 'Bold'
| 'ExtraBold'
| 'Black';Title
Large title text component.
import { Title } from '@shaquillehinds/react-native-essentials';
<Title>Page Title</Title>;Heading
Section heading component.
import { Heading } from '@shaquillehinds/react-native-essentials';
<Heading>Section Heading</Heading>;Body
Body text component.
import { Body } from '@shaquillehinds/react-native-essentials';
<Body>Body text content</Body>;Translate
Auto-translating text component (requires LocalizationProvider).
import { Translate } from '@shaquillehinds/react-native-essentials';
<Translate>Hello World</Translate>;Buttons
BaseButton
Customizable button component with loading states and animations.
import { BaseButton } from '@shaquillehinds/react-native-essentials';
<BaseButton
buttonSize="large"
backgroundColor="#007AFF"
borderRadius="curvy"
loading={false}
disabled={false}
onPress={() => console.log('Pressed')}
leftComponent={<Icon />}
rightComponent={<Icon />}
>
Button Text
</BaseButton>;Props:
interface ButtonProps extends Omit<BaseTextProps, 'children'> {
// Size
buttonSize?: 'small' | 'medium' | 'large' | 'auto' | 'wide';
// Spacing (uses Spacing type with array syntax)
margin?: Spaces; // e.g., [5], [5, 10], [5, 10, 5, 10]
padding?: Spaces; // e.g., [5], [5, 10], [5, 10, 5, 10]
// Border
borderColor?: string;
borderWidth?: 'razor' | 'thin' | 'medium' | 'large';
borderRadius?: 'edgy' | 'sharp' | 'medium' | 'soft' | 'curvy' | 'round';
// Appearance
backgroundColor?: string;
customFontColor?: string;
alignSelf?: FlexAlignType;
// Behavior
onPress?: () => void;
disabled?: boolean;
loading?: LoadingIndicatorProps | boolean;
activeOpacity?: number;
// Components
leftComponent?: ReactNode;
rightComponent?: ReactNode;
// Animation
animate?: boolean;
// Style
style?: StyleProp<AnimatedStyle<StyleProp<ViewStyle>>>;
textStyle?: StyleProp<AnimatedStyle<StyleProp<TextStyle>>>;
fontStyle?: FontStyle;
children?: ReactNode;
}Inputs
BaseInput
Foundation input component with flexible rendering options and ref management.
import { BaseInput } from '@shaquillehinds/react-native-essentials';
import { useRef } from 'react';
function Component() {
// Option 1: Use standard TextInput ref
const inputRef = useRef<TextInput>(null);
return (
<BaseInput
backgroundColor="#ffffff"
focusedBorderColor="#007AFF"
blurredBorderColor="#cccccc"
textInputProps={{
placeholder: 'Enter text',
onChangeText: (text) => console.log(text),
}}
refTextInput={inputRef}
LeftComponent={<Icon name="search" />}
RightComponent={<Icon name="clear" />}
padding={[3]}
/>
);
}Props:
interface BaseInputProps extends LayoutProps {
backgroundColor: string;
focusedBorderColor?: string;
blurredBorderColor?: string;
textInputProps: TextInputProps;
LeftComponent?: React.ReactNode;
RightComponent?: React.ReactNode;
TextInputComponent?: (
props: TextInputProps & {
ref?: React.RefObject<TextInput>;
}
) => React.JSX.Element;
refTextInput?: React.MutableRefObject<TextInput | null>;
refStateInput?: React.Ref<StateInputRef>;
refStateInputValidator?: (text: string) => boolean;
// Inherits all LayoutProps including margin and padding
}StateTextInput
Input component with isolated state management. Changes to the input don't trigger parent re-renders. The special refStateInput ref allows you to read and update the value imperatively from the parent without causing re-renders.
import {
BaseInput,
StateTextInput,
} from '@shaquillehinds/react-native-essentials';
import { useRef } from 'react';
function OptimizedForm() {
// StateInputRef provides access to value without re-rendering parent
const emailRef = useRef<StateInputRef>(null);
const passwordRef = useRef<StateInputRef>(null);
const handleSubmit = () => {
// Access values without triggering re-renders during typing
const email = emailRef.current?.value;
const password = passwordRef.current?.value;
console.log({ email, password });
};
const handleReset = () => {
// Imperatively set values without parent re-render
emailRef.current?.setValue('');
passwordRef.current?.setValue('');
};
return (
<>
<BaseInput
backgroundColor="#ffffff"
textInputProps={{
placeholder: 'Email',
keyboardType: 'email-address',
}}
refStateInput={emailRef}
refStateInputValidator={(text) => text.length <= 100}
padding={[3]}
/>
<BaseInput
backgroundColor="#ffffff"
textInputProps={{
placeholder: 'Password',
secureTextEntry: true,
}}
refStateInput={passwordRef}
padding={[3]}
/>
<Button onPress={handleSubmit}>Submit</Button>
<Button onPress={handleReset}>Reset</Button>
</>
);
}
// Or use StateTextInput directly
function DirectUsage() {
const inputRef = useRef<StateInputRef>(null);
return (
<StateTextInput
placeholder="Direct usage"
refStateInput={inputRef}
refStateInputValidator={(text) => /^[a-zA-Z]*$/.test(text)}
onChangeText={(text) => console.log('Valid text:', text)}
/>
);
}StateInputRef Type:
type StateInputRef = {
value: string;
setValue: React.Dispatch<React.SetStateAction<string>>;
};Benefits:
- Performance: Parent component doesn't re-render on every keystroke
- Imperative Access: Read/write input value without state updates
- Validation: Built-in validator prevents invalid input from being set
- Form Optimization: Perfect for forms with many inputs
Providers
PortalProvider
Manages portal/overlay content.
import {
PortalProvider,
usePortal,
} from '@shaquillehinds/react-native-essentials';
function App() {
return (
<PortalProvider unMountBufferTimeMS={100} updateBufferTimeMS={50}>
<YourApp />
</PortalProvider>
);
}
function Component() {
const portal = usePortal();
const showModal = () => {
const key = portal.mount(
'modal-key',
<Modal onClose={() => portal.unmount('modal-key')} />
);
};
return <Button onPress={showModal}>Show Modal</Button>;
}Context Value:
interface PortalContextValue {
mount: (
key: PortalKey,
element: ReactNode,
onMount?: (key: PortalKey) => void
) => PortalKey;
update: (key: PortalKey, element: ReactNode) => void;
unmount: (key: PortalKey, onUnmount?: (key: PortalKey) => void) => void;
}LocalizationProvider
Provides app-wide translation support.
import {
LocalizationProvider,
useLocalization,
} from '@shaquillehinds/react-native-essentials';
<LocalizationProvider
sourceLanguage="en-US"
targetLanguage="es-ES"
translation={async ({ sourceLanguage, targetLanguage, text }) => {
// Your translation API call
return translatedText;
}}
initialLanguagesRecord={{
'es-ES': {
base64hash: 'translated text',
},
}}
initialLanguagesRecordRetriever={async ({
sourceLanguage,
targetLanguage,
}) => {
// Fetch saved translations
return [{ original: 'Hello', translated: 'Hola' }];
}}
>
<YourApp />
</LocalizationProvider>;Props:
interface LocalizationProviderProps {
children: ReactNode;
sourceLanguage: LanguageCode;
targetLanguage: LanguageCode;
translation: (props: {
sourceLanguage: LanguageCode;
targetLanguage: LanguageCode;
text: string;
}) => Promise<string>;
initialLanguagesRecord?: LanguagesRecord;
initialLanguagesRecordRetriever?: (props: {
sourceLanguage: LanguageCode;
targetLanguage: LanguageCode;
}) => Promise<{ original: string; translated: string }[]>;
}EventTrackersProvider
Centralized event tracking system.
import {
EventTrackersProvider,
useEventTrackers,
} from '@shaquillehinds/react-native-essentials';
<EventTrackersProvider
eventTrackers={[
{
name: 'Analytics',
trackEvent: (eventName, properties) => {
analytics.track(eventName, properties);
},
},
]}
>
<YourApp />
</EventTrackersProvider>;DataCollectionProvider
Manages centralized data collection.
import {
DataCollectionProvider,
useDataCollection,
} from '@shaquillehinds/react-native-essentials';
<DataCollectionProvider>
<YourApp />
</DataCollectionProvider>;Wrappers
Press
Enhanced pressable component.
import { Press } from '@shaquillehinds/react-native-essentials';
<Press
onPress={() => console.log('Pressed')}
activeOpacity={0.7}
disabled={false}
>
{children}
</Press>;PressableLayout
Combines Layout and Press functionality - a pressable Layout component with all Layout props.
import { PressableLayout } from '@shaquillehinds/react-native-essentials';
<PressableLayout
onPress={() => console.log('Pressed')}
center
padding={[5]}
backgroundColor="#f0f0f0"
activeOpacity={0.7}
>
{children}
</PressableLayout>;Props:
interface PressableLayoutProps extends LayoutProps {
onPress?: () => void;
activeOpacity?: number;
disabled?: boolean;
// Inherits all Layout props
}TextStream
Animated text streaming component.
import { TextStream } from '@shaquillehinds/react-native-essentials';
<TextStream
text="Text to stream"
speed={50}
onComplete={() => console.log('Done')}
/>;Indicators
LoadingIndicator
Displays loading state with spinner.
import { LoadingIndicator } from '@shaquillehinds/react-native-essentials';
<LoadingIndicator size="large" color="#007AFF" />;SkeletonViewIndicator
Skeleton loading placeholder.
import { SkeletonViewIndicator } from '@shaquillehinds/react-native-essentials';
<SkeletonViewIndicator colors={['#E1E9EE', '#F2F8FC', '#E1E9EE']} />;Injectors
ViewDimensionsInjector
Injects view dimensions into children.
import { ViewDimensionsInjector } from '@shaquillehinds/react-native-essentials';
<ViewDimensionsInjector>
{({ width, height }) => (
<Layout width={width} height={height}>
{/* Content */}
</Layout>
)}
</ViewDimensionsInjector>;Hooks
useDeviceOrientation
Hook for responsive device orientation and dimensions.
import { useDeviceOrientation } from '@shaquillehinds/react-native-essentials';
function Component() {
const orientation = useDeviceOrientation();
return (
<Layout width={80} height={40}>
<Text>
Orientation: {orientation.isPortrait ? 'Portrait' : 'Landscape'}
</Text>
</Layout>
);
}Returns:
{
orientation: 'portrait' | 'landscape';
isPortrait: boolean;
isLandscape: boolean;
screenWidth: number;
screenHeight: number;
windowWidth: number;
windowHeight: number;
relativeX: (percentage: number) => number;
relativeY: (percentage: number) => number;
relativeShort: (percentage: number) => number;
relativeLong: (percentage: number) => number;
}useImageSize
Calculate responsive image dimensions.
import { useImageSize } from '@shaquillehinds/react-native-essentials';
function ImageComponent() {
const { imageSize } = useImageSize({
image: 'https://example.com/image.jpg',
maxWidth: 300,
maxHeight: 400,
});
return (
<Image
source={{ uri: 'https://example.com/image.jpg' }}
style={imageSize}
/>
);
}Props:
interface UseImageSizeProps {
image?: string;
maxWidth: number;
maxHeight: number;
}Returns:
{
imageSize: {
width: number;
height: number;
}
calculateSize: (props: {
imageWidth: number;
imageHeight: number;
maxWidth: number;
maxHeight: number;
}) => {
displayWidth: number;
displayHeight: number;
};
}useImageSizes
Calculate dimensions for multiple images.
import { useImageSizes } from '@shaquillehinds/react-native-essentials';
const { imageSizes } = useImageSizes({
images: ['url1', 'url2', 'url3'],
maxWidth: 300,
maxHeight: 400,
});useTimer
Countdown timer hook.
import { useTimer } from '@shaquillehinds/react-native-essentials';
function Timer() {
const { seconds, minutes } = useTimer({
seconds: 120,
start: true,
onTimerEnds: () => console.log('Timer ended'),
});
return (
<Text>
{minutes}:{seconds}
</Text>
);
}Props:
interface UseTimerProps {
seconds: number;
onTimerEnds: () => void;
start: boolean;
}useDebounce
Debounce function calls with optional per-call overrides.
import { useDebounce } from '@shaquillehinds/react-native-essentials';
function SearchInput() {
const [query, setQuery] = useState('');
const { debounce, cancelDebounce } = useDebounce({
delayInMilliSecs: 500,
onTrigger: async () => {
// Perform search with current query
const results = await searchAPI(query);
setResults(results);
},
onPreTrigger: () => console.log('About to search'),
onPostTrigger: () => console.log('Search complete'),
onDebounceInvocation: () => console.log('User is typing'),
});
return (
<TextInput
value={query}
onChangeText={(text) => {
setQuery(text);
debounce(); // Uses props from hook initialization
}}
/>
);
}
// Override behavior per call
function AdvancedSearch() {
const [query, setQuery] = useState('');
const { debounce } = useDebounce({
delayInMilliSecs: 500,
onTrigger: async () => {
console.log('Default search');
},
});
const handleQuickSearch = () => {
// Override delay for this specific call
debounce({
delayInMilliSecs: 100,
onTrigger: async () => {
console.log('Quick search!');
},
});
};
return (
<>
<TextInput onChangeText={(text) => debounce()} />
<Button onPress={handleQuickSearch}>Quick Search</Button>
</>
);
}Props:
interface UseDebounceProps {
delayInMilliSecs: number;
onTrigger: () => void | Promise<void>;
onPreTrigger?: () => void | Promise<void>;
onPostTrigger?: () => void | Promise<void>;
onDebounceInvocation?: () => void | Promise<void>;
}
// Debounce function accepts optional overrides
type DebounceFunction = (
prop?: Partial<Omit<UseDebounceProps, 'onDebounceInvocation'>>
) => void;Returns:
{
debounce: DebounceFunction;
cancelDebounce: () => void;
}Note: The hook stores props in a ref, so they won't update when the component re-renders. To use updated values from component state, either pass them as parameters to debounce() or access them via closure in the callback functions.
useInterval
Interval timer hook.
import { useInterval } from '@shaquillehinds/react-native-essentials';
function Component() {
useInterval({
callback: () => console.log('Tick'),
delay: 1000,
start: true,
});
}useTranslation
Translate text using LocalizationProvider.
import { useTranslation } from '@shaquillehinds/react-native-essentials';
function Component() {
const translated = useTranslation({ text: 'Hello World' });
return <Text>{translated}</Text>;
}useScrollableItems
Infinite scroll/pagination hook.
import { useScrollableItems } from '@shaquillehinds/react-native-essentials';
function InfiniteList() {
const {
items,
loading,
onLayout,
onItemsEndReached,
onRefreshItems,
refreshingItems,
updateListItem,
removeListItem,
} = useScrollableItems({
limit: 20,
minFetchDuration: 300,
fetchCooldown: 500,
itemsFetchingFunction: async ({ limit, skip }) => {
const response = await fetch(`/api/items?limit=${limit}&skip=${skip}`);
return response.json();
},
});
return (
<FlatList
data={items}
onLayout={onLayout}
onEndReached={onItemsEndReached}
onRefresh={onRefreshItems}
refreshing={refreshingItems}
ListFooterComponent={loading ? <LoadingIndicator /> : null}
/>
);
}Props:
interface UseScrollableItemsProps<ListItem, FetchingFunctionArgs> {
limit?: number;
minFetchDuration?: number;
fetchCooldown?: number;
itemsFetchingFunction: ItemsFetchingFunction<ListItem>;
itemsFetchingFunctionArgs?: FetchingFunctionArgs;
}
type ItemsFetchingFunction<ListItem> = (props: {
limit: number;
skip: number;
}) => Promise<ListItem[]>;Returns:
{
items: ListItem[];
setItems: (items: ListItem[]) => void;
loading: boolean;
setLoading: (loading: boolean) => void;
allItemsFetched: boolean;
refreshingItems: boolean;
onLayout: () => void;
onItemsEndReached: () => void;
onRefreshItems: () => Promise<void>;
fetchItems: (props?: { refresh: boolean }) => Promise<void>;
updateListItem: (id: string, key: keyof ListItem, updatedItem: ListItem) => void;
removeListItem: (id: string, key: keyof ListItem) => void;
}useFontSizes
Access font size scale.
import { useFontSizes } from '@shaquillehinds/react-native-essentials';
function Component() {
const fontSizes = useFontSizes();
return <Text style={{ fontSize: fontSizes.large }}>Text</Text>;
}useViewDimensions
Measure view dimensions.
import { useViewDimensions } from '@shaquillehinds/react-native-essentials';
function Component() {
const { onLayout, width, height } = useViewDimensions();
return (
<Layout onLayout={onLayout}>
<Text>
Width: {width}, Height: {height}
</Text>
</Layout>
);
}useInputRef
Manage TextInput refs.
import { useInputRef } from '@shaquillehinds/react-native-essentials';
function Form() {
const { ref, focus, blur } = useInputRef();
return (
<>
<TextInput ref={ref} />
<Button onPress={focus}>Focus</Button>
</>
);
}useKeyboardListeners
Listen to keyboard events.
import { useKeyboardListeners } from '@shaquillehinds/react-native-essentials';
function Component() {
useKeyboardListeners({
onShow: (e) => console.log('Keyboard shown', e.endCoordinates.height),
onHide: () => console.log('Keyboard hidden'),
});
}Utilities
Storage Accessors
Create typed MMKV storage accessors.
import { createStorageAccessors } from '@shaquillehinds/react-native-essentials';
interface UserData {
name: string;
age: number;
}
const userStorage = createStorageAccessors<UserData>('user-key');
// Store data
userStorage.store({ name: 'John', age: 30 });
// Retrieve data
const user = userStorage.retrieve(); // UserData | undefined
// Remove data
const removedUser = userStorage.remove();
// Use hooks
function Component() {
const [user, setUser] = userStorage.useObject();
return <Text>{user?.name}</Text>;
}Layout Utilities
import {
relativeX,
relativeY,
relativeShort,
relativeLong,
normalize,
} from '@shaquillehinds/react-native-essentials';
const width = relativeX(50); // 50% of screen width
const height = relativeY(25); // 25% of screen height
const size = relativeShort(10); // 10% of shorter dimension
const fontSize = normalize(16); // Normalized font sizeArray Utilities
import {
filterMap,
advancedMap,
keepArrayUnique,
} from '@shaquillehinds/react-native-essentials';
// Filter and map in one operation
const result = filterMap(array, (item) => {
if (item.active) return item.name;
return null; // Filtered out
});
// Keep array unique
const unique = keepArrayUnique({
prevItems: existingItems,
newItems: newItems,
comparatorExtractor: (item) => item.id,
});Measurement Utilities
import { measureAsync } from '@shaquillehinds/react-native-essentials';
const measurements = await measureAsync(viewRef);
// Returns: { x, y, width, height, pageX, pageY }Snapshot Utilities
import { snapShotGestureResponderEvent } from '@shaquillehinds/react-native-essentials';
const snapshot = snapShotGestureResponderEvent(event);Wait Utility
import { wait } from '@shaquillehinds/react-native-essentials';
await wait(1000); // Wait 1 secondScheduler
import { Scheduler } from '@shaquillehinds/react-native-essentials';
// Timer (runs once after delay)
const timer = new Scheduler.Timer(() => {
console.log('Timer fired');
}, 1000);
timer.start();
timer.stop();
// Schedule (runs repeatedly)
const schedule = new Scheduler.Schedule(() => {
console.log('Tick');
}, 1000);
schedule.start();
schedule.stop();Language Utilities
import {
mergeStringRecords,
mergeLanguagesRecords,
type LanguageCode,
type LanguagesRecord,
} from '@shaquillehinds/react-native-essentials';
const merged = mergeLanguagesRecords({
obj1: languageRecords1,
obj2: languageRecords2,
});ID Generation
import { getSequantialRandomId } from '@shaquillehinds/react-native-essentials';
const id = getSequantialRandomId(); // Returns unique IDAnimations
AnimateComponent
Generic animation component wrapper using React Native's Animated API.
import { AnimateComponent } from '@shaquillehinds/react-native-essentials';
import { Animated } from 'react-native';
// Number-based animation
<AnimateComponent
initialPosition={0}
toPosition={{
type: 'timing',
toValue: 1,
duration: 500,
useNativeDriver: true,
}}
style={(value) => ({
opacity: value,
})}
autoStart
>
{children}
</AnimateComponent>
// XY position animation
<AnimateComponent
initialPosition={{ x: 0, y: 0 }}
toPosition={[
{
type: 'timing',
toValue: { x: 100, y: 100 },
duration: 500,
useNativeDriver: true,
}
]}
style={(value) => ({
transform: [{ translateX: value.x }, { translateY: value.y }],
})}
autoStart
loop={3}
>
{children}
</AnimateComponent>Props:
type AnimateComponentProps<T extends InitialValue> = {
initialPosition: T; // number | XYNumber ({ x: number, y: number })
toPosition:
| AnimateComponentAnimationConfig
| AnimateComponentAnimationConfig[];
style: (
value: T extends XY ? Animated.AnimatedValueXY : Animated.Value
) => ViewStyle;
autoStart?: boolean;
returnToStart?: boolean;
loop?: number;
ref?: React.RefObject<AnimateComponentRef<T>>;
children?: ReactNode;
};
type AnimateComponentAnimationConfig =
| (Animated.TimingAnimationConfig & { type: 'timing' })
| (Animated.SpringAnimationConfig & { type: 'spring' })
| (Animated.DecayAnimationConfig & { type: 'decay' });
type AnimateComponentRef<T extends InitialValue> = {
stop: () => void;
start: () => void;
reset: () => void;
setValue: (value: T) => void;
value: T extends XY ? Animated.AnimatedValueXY : Animated.Value;
};AnimateValueComponent
Animate using a shared Animated value.
import { AnimateValueComponent } from '@shaquillehinds/react-native-essentials';
import { Animated } from 'react-native';
import { useRef } from 'react';
function Component() {
const opacity = useRef(new Animated.Value(0)).current;
// Trigger animation
Animated.timing(opacity, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
return (
<AnimateValueComponent
initialPosition={0}
toPosition={{
type: 'timing',
toValue: 1,
duration: 500,
useNativeDriver: true,
}}
style={(value) => ({
opacity: value,
})}
>
{children}
</AnimateValueComponent>
);
}AnimateXYValueComponent
Animate X and Y coordinates using Animated.ValueXY.
import { AnimateXYValueComponent } from '@shaquillehinds/react-native-essentials';
<AnimateXYValueComponent
initialPosition={{ x: 0, y: 0 }}
toPosition={{
type: 'spring',
toValue: { x: 100, y: 100 },
useNativeDriver: true,
}}
style={(value) => ({
transform: [{ translateX: value.x }, { translateY: value.y }],
})}
autoStart
>
{children}
</AnimateXYValueComponent>;ArcSpinnerAnimation
Spinning arc loader animation.
import { ArcSpinnerAnimation } from '@shaquillehinds/react-native-essentials';
<ArcSpinnerAnimation size={40} color="#007AFF" strokeWidth={4} />;useDragAnimation
Hook for drag animations.
import { useDragAnimation } from '@shaquillehinds/react-native-essentials';
function DraggableBox() {
const { translateX, translateY, gesture } = useDragAnimation();
return (
<GestureDetector gesture={gesture}>
<Animated.View
style={[
{ width: 100, height: 100 },
{ transform: [{ translateX }, { translateY }] },
]}
/>
</GestureDetector>
);
}Gestures
Drag
Drag gesture component.
import { Drag } from '@shaquillehinds/react-native-essentials';
<Drag
onDragStart={() => console.log('Drag started')}
onDragEnd={() => console.log('Drag ended')}
onDrag={(x, y) => console.log('Dragging', x, y)}
>
{children}
</Drag>;Swipe
Swipe gesture detection.
import { Swipe } from '@shaquillehinds/react-native-essentials';
<Swipe
onSwipeLeft={() => console.log('Swiped left')}
onSwipeRight={() => console.log('Swiped right')}
onSwipeUp={() => console.log('Swiped up')}
onSwipeDown={() => console.log('Swiped down')}
>
{children}
</Swipe>;TwoFingerLongPress
Two-finger long press gesture.
import { TwoFingerLongPress } from '@shaquillehinds/react-native-essentials';
<TwoFingerLongPress
onLongPress={() => console.log('Two finger long press')}
duration={500}
>
{children}
</TwoFingerLongPress>;Styles
Shadow
Platform-agnostic shadow styling.
import { shadow } from '@shaquillehinds/react-native-essentials';
<Layout style={shadow({ elevation: 5 })}>{children}</Layout>;Spacer
Spacing utility type and transformer.
import {
type Spacing,
transformSpacing,
} from '@shaquillehinds/react-native-essentials';
const spacing: Spacing = {
all: 10,
horizontal: 15,
vertical: 20,
top: 5,
};
const style = transformSpacing({
margin: spacing,
orientation: deviceOrientation,
});Algorithms
String, number, and data manipulation utilities.
String Utilities
import {
firstLetterCap,
titleCase,
camelCase,
snakeToCamelCase,
dashToCamelCase,
toCamelCase,
snakeToTitleCase,
dashToTitleCase,
toTitleCase,
camelToUpperSnake,
} from '@shaquillehinds/react-native-essentials';
firstLetterCap('hello'); // "Hello"
titleCase('hello world'); // "Hello World"
camelCase('hello world'); // "helloWorld"
snakeToCamelCase('hello_world'); // "helloWorld"
toTitleCase('hello-world'); // "Hello World"
camelToUpperSnake('helloWorld'); // "HELLO_WORLD"Number Utilities
import {
kFormatter,
centsToDollars,
hexOpacity,
secondsToTime,
getTodayInSeconds,
} from '@shaquillehinds/react-native-essentials';
kFormatter(1500); // "1.5k"
kFormatter(500); // 500
centsToDollars({
cents: 1999,
dollarSign: true,
currency: 'USD',
}); // "$19.99 USD"
hexOpacity(50); // "80" (50% opacity in hex)
hexOpacity(0.5); // "80" (also accepts decimals)
secondsToTime(3665); // "01:01:05"
secondsToTime(3665, { hideHours: true }); // "61:05"
getTodayInSeconds(); // Current time in seconds since midnightEncoding Utilities
import { toBase64, fromBase64 } from '@shaquillehinds/react-native-essentials';
const encoded = toBase64('Hello World');
const decoded = fromBase64(encoded);Date Utilities
import { _24hrsToDate } from '@shaquillehinds/react-native-essentials';
const date = _24hrsToDate('1430'); // Date object for 14:30 (2:30 PM)Object Utilities
import {
objToQueryStr,
deepCopy,
} from '@shaquillehinds/react-native-essentials';
objToQueryStr({ page: 1, limit: 20 }); // "page=1&limit=20"
const copy = deepCopy(originalObject); // Deep cloneType Definitions
Common Types
// Font Types
type FontSize =
| 'headingL'
| 'headingM'
| 'headingS'
| 'titleL'
| 'titleM'
| 'titleS'
| 'bodyL'
| 'bodyM'
| 'bodyS';
type FontStyle =
| 'Thin'
| 'Extra Light'
| 'Light'
| 'Regular'
| 'Medium'
| 'SemiBold'
| 'Bold'
| 'ExtraBold'
| 'Black';
type LineHeight = 'short' | 'tall';
type LetterSpacing = 'wide' | 'extraWide';
// Button Types
type ButtonSize = 'small' | 'medium' | 'large' | 'auto' | 'wide';
type BorderSize = 'razor' | 'thin' | 'medium' | 'large';
type RadiusSize = 'edgy' | 'sharp' | 'medium' | 'soft' | 'curvy' | 'round';
// Device Types
type DeviceOrientation = 'portrait' | 'landscape';
// Spacing Types
type Spaces = [number, number?, number?, number?];
interface Spacing {
/**
* CSS-style padding/margin shorthand using percentages:
* - [10] = all sides 10%
* - [10, 20] = vertical 10%, horizontal 20%
* - [10, 20, 30] = top 10%, horizontal 20%, bottom 30%
* - [10, 20, 30, 40] = top 10%, right 20%, bottom 30%, left 40%
*/
padding?: Spaces;
margin?: Spaces;
}Best Practices
Performance
- Use
useDeviceOrientationfor responsive layouts instead of recalculating dimensions - Leverage
useScrollableItemsfor efficient pagination - Use
useDebouncefor expensive operations - Memoize callbacks and values when using hooks
Type Safety
Always provide type parameters for generic utilities:
createStorageAccessors<UserData>('user') useScrollableItems<Item, Args>(...)Use provided types for consistent styling:
const spacing: Spacing = { all: 10 };
Layout
Use
Layoutcomponent for consistent spacing:<Layout padding={[5]} margin={[2, 4]}>Prefer percentage-based dimensions with
relativeX/relativeYUse
normalize()for font sizes to ensure consistency across devices
Spacing Examples:
// All sides
padding={[5]} // 5% on all sides
// Vertical and horizontal
padding={[5, 10]} // 5% top/bottom, 10% left/right
// Top, horizontal, bottom
padding={[5, 10, 3]} // 5% top, 10% left/right, 3% bottom
// Individual sides
padding={[5, 10, 3, 8]} // 5% top, 10% right, 3% bottom, 8% leftLocalization
- Wrap app in
LocalizationProviderfor translation support - Use
Translatecomponent oruseTranslationhook - Provide
initialLanguagesRecordfor offline support
Migration Guide
From v1.6 to v1.7
- Updated peer dependencies to latest versions
- Added new animation components
- Improved TypeScript definitions
- Enhanced gesture system
Troubleshooting
Common Issues
Issue: Components not rendering
- Ensure all peer dependencies are installed
- Check that providers wrap your app correctly
Issue: Animations not working
- Verify
react-native-reanimatedis properly configured - Check Reanimated plugin in
babel.config.js
Issue: Storage not persisting
- Ensure
react-native-mmkvis properly linked - Check storage permissions on Android
Support
For issues, questions, or contributions:
License
MIT © Shaquille Hinds
