import { useIsomorphicLayoutEffect } from '~/components/primitives/hooks';
import * as Slot from '~/components/primitives/slot';
import * as React from 'react';
type ImageErrorEventData,
type ImageSourcePropType,
type NativeSyntheticEvent,
import type { FallbackProps, FallbackRef, ImageProps, ImageRef, RootProps, RootRef } from './types';
type AvatarState = 'loading' | 'error' | 'loaded';
interface IRootContext extends RootProps {
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;
<RootContext.Provider value={{ alt, status, setStatus }}>
<Component ref={ref} {...viewProps} />
Root.displayName = 'RootAvatar';
function useRootContext() {
const context = React.useContext(RootContext);
throw new Error('Avatar compound components cannot be rendered outside the Avatar component');
const Image = React.forwardRef<ImageRef, ImageProps>(
{ asChild, onLoad: onLoadProps, onError: onErrorProps, onLoadingStatusChange, ...props },
const { alt, setStatus, status } = useRootContext();
useIsomorphicLayoutEffect(() => {
if (isValidSource(props?.source)) {
const onLoad = React.useCallback(
(e: NativeSyntheticEvent<ImageLoadEventData>) => {
onLoadingStatusChange?.('loaded');
const onError = React.useCallback(
(e: NativeSyntheticEvent<ImageErrorEventData>) => {
onLoadingStatusChange?.('error');
if (status === 'error') {
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') {
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) {
// Using require() for the source returns a number
if (typeof source === 'number') {
if (Array.isArray(source)) {
return source.some((source) => !!source.uri);