Avatar Primitive
An image component featuring an alternative representation for depicting the user.
Installation
Install the component via your command line.
npx expo install @rn-primitives/avatar
Copy/paste the following code to ~/components/primitives/avatar/index.tsx
import { useIsomorphicLayoutEffect } from '~/components/primitives/hooks';import * as Slot from '~/components/primitives/slot';import * as React from 'react';import { type ImageErrorEventData, type ImageLoadEventData, type ImageSourcePropType, type NativeSyntheticEvent, Image as RNImage, View,} from 'react-native';import type { FallbackProps, FallbackRef, ImageProps, ImageRef, RootProps, RootRef } from './types';
type AvatarState = 'loading' | 'error' | 'loaded';
interface IRootContext extends RootProps { status: AvatarState; setStatus: (status: AvatarState) => void;}
const RootContext = React.createContext<IRootContext | null>(null);
const Root = React.forwardRef<RootRef, RootProps>(({ asChild, alt, ...viewProps }, ref) => { const [status, setStatus] = React.useState<AvatarState>('error'); const Component = asChild ? Slot.View : View; return ( <RootContext.Provider value={{ alt, status, setStatus }}> <Component ref={ref} {...viewProps} /> </RootContext.Provider> );});
Root.displayName = 'RootAvatar';
function useRootContext() { const context = React.useContext(RootContext); if (!context) { throw new Error('Avatar compound components cannot be rendered outside the Avatar component'); } return context;}
const Image = React.forwardRef<ImageRef, ImageProps>( ( { asChild, onLoad: onLoadProps, onError: onErrorProps, onLoadingStatusChange, ...props }, ref ) => { const { alt, setStatus, status } = useRootContext();
useIsomorphicLayoutEffect(() => { if (isValidSource(props?.source)) { setStatus('loading'); }
return () => { setStatus('error'); }; }, [props?.source]);
const onLoad = React.useCallback( (e: NativeSyntheticEvent<ImageLoadEventData>) => { setStatus('loaded'); onLoadingStatusChange?.('loaded'); onLoadProps?.(e); }, [onLoadProps] );
const onError = React.useCallback( (e: NativeSyntheticEvent<ImageErrorEventData>) => { setStatus('error'); onLoadingStatusChange?.('error'); onErrorProps?.(e); }, [onErrorProps] );
if (status === 'error') { return null; }
const Component = asChild ? Slot.Image : RNImage; return <Component ref={ref} alt={alt} onLoad={onLoad} onError={onError} {...props} />; });
Image.displayName = 'ImageAvatar';
const Fallback = React.forwardRef<FallbackRef, FallbackProps>(({ asChild, ...props }, ref) => { const { alt, status } = useRootContext();
if (status !== 'error') { return null; } const Component = asChild ? Slot.View : View; return <Component ref={ref} role={'img'} aria-label={alt} {...props} />;});
Fallback.displayName = 'FallbackAvatar';
export { Fallback, Image, Root };
function isValidSource(source?: ImageSourcePropType) { if (!source) { return false; } // Using require() for the source returns a number if (typeof source === 'number') { return true; } if (Array.isArray(source)) { return source.some((source) => !!source.uri); } return !!source.uri;}
Copy/paste the following code for types to ~/components/primitives/avatar/types.ts
import type { ComponentPropsWithAsChild, SlottableViewProps, ViewRef } from '~/components/primitives/types';import type { Image } from 'react-native';
type RootProps = SlottableViewProps & { alt: string;};
type ImageProps = Omit<ComponentPropsWithAsChild<typeof Image>, 'alt'> & { children?: React.ReactNode; onLoadingStatusChange?: (status: 'error' | 'loaded') => void;};
type FallbackProps = SlottableViewProps;
type RootRef = ViewRef;type ImageRef = React.ElementRef<typeof Image>;type FallbackRef = ViewRef;
export type { FallbackProps, FallbackRef, ImageProps, ImageRef, RootProps, RootRef };
Usage
import * as AvatarPrimitive from '@rn-primitives/avatar';
const GITHUB_AVATAR_URI = 'https://github.com/mrzachnugent.png';
function Example() { return ( <AvatarPrimitive.Root alt="Zach Nugent's Avatar"> <AvatarPrimitive.Image source={{ uri: GITHUB_AVATAR_URI }} /> <AvatarPrimitive.Fallback> <Text>ZN</Text> </AvatarPrimitive.Fallback> </AvatarPrimitive.Root> );}
Props
Root
Extends View
props
Prop | Type | Note |
---|---|---|
alt * | string | |
asChild | boolean | (optional) |
Image
Extends Image
props except alt
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |
Fallback
Extends View
props
Prop | Type | Note |
---|---|---|
asChild | boolean | (optional) |