# Incoming & Outgoing Call Component

Incoming and outgoing call components display call preview before joining.

- **Incoming call** - Shown to the callee (person being called)
- **Outgoing call** - Shown to the caller (person making the call)

## Best Practices

- **Show caller identity** - Display name and profile image clearly
- **Provide clear actions** - Make accept/reject buttons obvious and accessible
- **Handle timeouts** - Auto-dismiss after reasonable duration if no response
- **Support accessibility** - Enable VoiceOver and TalkBack for call actions
- **Test with push notifications** - Verify behavior when app is backgrounded

### Incoming call UI elements

- **User info** - Caller's name and image
- **Accept button** - Join the call
- **Reject button** - Decline the call

### Outgoing call UI elements

- **User info** - Callee's name and image
- **Hangup button** - Cancel the call
- **Media controls** - (Optional) Audio/video toggle

![Incoming call](https://getstream.io/docs-assets/images/cee5031d3c36.png)

![Outgoing call](https://getstream.io/docs-assets/images/4bc95358d57b.png)

>
> **Note:** Component visualization varies by application. This guide focuses on building principles and data source integration.
>

### User Info

Display user info (image, name) using the `useCallMembers` hook to get call members.

![Preview of the User Info Example](https://getstream.io/docs-assets/images/58a5e6bdaa88.png)

>
> **Note:** `useCallMembers` includes the connected user's info. Filter it out before displaying in the preview.
>

Example:

```tsx title="UserInfoComponent.tsx"
import React from "react";
import {
  useCallStateHooks,
  useConnectedUser,
  UserResponse,
} from "@stream-io/video-react-native-sdk";
import { Image, StyleSheet, Text, View } from "react-native";

export const UserInfoComponent = () => {
  const connectedUser = useConnectedUser();
  const { useCallMembers } = useCallStateHooks();
  const members = useCallMembers();

  const membersToShow: UserResponse[] = (members || [])
    .map(({ user }) => user)
    .filter((user) => user.id !== connectedUser?.id);

  return (
    <View style={styles.container}>
      <View style={styles.userInfo}>
        {membersToShow.map((memberToShow) => {
          return (
            <View key={memberToShow.id}>
              <Image
                style={styles.avatar}
                source={{
                  uri: memberToShow.image,
                }}
              />
              <Text style={styles.title}>{memberToShow.name}</Text>
            </View>
          );
        })}
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  userInfo: {
    flexDirection: "row",
    justifyContent: "space-evenly",
  },
  title: {
    fontSize: 20,
    color: "white",
    marginVertical: 20,
    textAlign: "center",
  },
  avatar: {
    height: 100,
    width: 100,
    borderRadius: 50,
  },
});
```

### Media Stream Management

Control audio/video mute status using `useCameraState` and `useMicrophoneState` hooks from `useCallStateHooks`. These hooks manage local device state and media streaming.

![Toggle Audio/Video Button On](https://getstream.io/docs-assets/images/0b6c6c10260c.png)

![Toggle Audio/Video Button Off](https://getstream.io/docs-assets/images/e00e41ea8e0d.png)

Example:

```tsx title="MediaStreamButtonGroup.tsx"
import React from "react";
import { Pressable, View, Text, StyleSheet } from "react-native";
import { useCall, useCallStateHooks } from "@stream-io/video-react-native-sdk";

export const MediaStreamButtonGroup = () => {
  const call = useCall();
  const { useMicrophoneState, useCameraState } = useCallStateHooks();
  const { isMute: microphoneMuted } = useMicrophoneState();
  const { isMute: cameraMuted } = useCameraState();

  const audioButtonStyles = [
    styles.button,
    {
      backgroundColor: microphoneMuted ? "#080707dd" : "white",
    },
  ];

  const videoButtonStyles = [
    styles.button,
    {
      backgroundColor: cameraMuted ? "#080707dd" : "white",
    },
  ];

  const audioButtonTextStyles = [
    styles.mediaButtonText,
    {
      color: microphoneMuted ? "white" : "#080707dd",
    },
  ];

  const videoButtonTextStyles = [
    styles.mediaButtonText,
    {
      color: cameraMuted ? "white" : "#080707dd",
    },
  ];

  const toggleAudioMuted = async () => {
    await call?.microphone.toggle();
  };

  const toggleVideoMuted = async () => {
    await call?.camera.toggle();
  };

  return (
    <View style={styles.buttonGroup}>
      <Pressable onPress={toggleAudioMuted} style={audioButtonStyles}>
        {!microphoneMuted ? (
          <Text style={audioButtonTextStyles}>Audio on</Text>
        ) : (
          <Text style={audioButtonTextStyles}>Audio off</Text>
        )}
      </Pressable>
      <Pressable onPress={toggleVideoMuted} style={videoButtonStyles}>
        {!cameraMuted ? (
          <Text style={videoButtonTextStyles}>Video on</Text>
        ) : (
          <Text style={videoButtonTextStyles}>Video off</Text>
        )}
      </Pressable>
    </View>
  );
};

const styles = StyleSheet.create({
  buttonGroup: {
    flexDirection: "row",
    justifyContent: "space-evenly",
  },
  button: {
    height: 80,
    width: 80,
    borderRadius: 40,
    justifyContent: "center",
  },
  mediaButtonText: {
    textAlign: "center",
  },
});
```

## Incoming Call Component

### Accept and Reject Call Button

See [Accepting](https://getstream.io/video/docs/react-native/v2/guides/joining-and-creating-calls/) and [Rejecting](https://getstream.io/video/docs/react-native/v2/guides/joining-and-creating-calls/) calls in the [Joining & Creating Calls](https://getstream.io/video/docs/react-native/v2/guides/joining-and-creating-calls/) guide.

Use the `useCall` hook to access the call object with accept/reject functions.

![Preview of the Accept and Reject Call Button of Incoming Call](https://getstream.io/docs-assets/images/f70fdf4066d3.png)

Example:

```tsx title="IncomingCallButtonGroup.tsx"
import React, { useCallback } from "react";
import { Pressable, Text, View, StyleSheet } from "react-native";
import {
  CallingState,
  useCall,
  useCallStateHooks,
} from "@stream-io/video-react-native-sdk";

export const IncomingCallButtonGroup = () => {
  const call = useCall();
  const { useCallCallingState } = useCallStateHooks();
  const callingState = useCallCallingState();

  const acceptCallHandler = useCallback(async () => {
    try {
      await call?.join();
    } catch (error) {
      console.log("Error accepting Call", error);
    }
  }, [call]);

  const rejectCallHandler = useCallback(async () => {
    try {
      if (callingState === CallingState.LEFT) {
        return;
      }
      await call?.leave({ reject: true, reason: "decline" });
    } catch (error) {
      console.log("Error rejecting Call", error);
    }
  }, [call, callingState]);

  return (
    <View style={styles.buttonGroup}>
      <Pressable
        style={[styles.button, styles.rejectButton]}
        onPress={rejectCallHandler}
      >
        <Text style={styles.callButtonText}>Reject</Text>
      </Pressable>
      <Pressable
        style={[styles.button, styles.acceptButton]}
        onPress={acceptCallHandler}
      >
        <Text style={styles.callButtonText}>Accept</Text>
      </Pressable>
    </View>
  );
};

const styles = StyleSheet.create({
  buttonGroup: {
    flexDirection: "row",
    justifyContent: "space-evenly",
  },
  button: {
    height: 80,
    width: 80,
    borderRadius: 40,
    justifyContent: "center",
  },
  acceptButton: {
    backgroundColor: "#20E070",
  },
  rejectButton: {
    backgroundColor: "#FF3742",
  },
  callButtonText: {
    color: "white",
    textAlign: "center",
  },
});
```

## Outgoing Call Component

### Hangup call button

See [Leave call](https://getstream.io/video/docs/react-native/v2/guides/joining-and-creating-calls/#leave-call) in the [Joining & Creating Calls](https://getstream.io/video/docs/react-native/v2/guides/joining-and-creating-calls/) guide.

Use the `useCall` hook to access the call object with hangup function.

![Preview of the Hangup call button Example](https://getstream.io/docs-assets/images/9f3f9796159c.png)

Example:

```tsx title="OutgoingCallButtonGroup.tsx"
import React, { useCallback } from "react";
import { Pressable, Text, View, StyleSheet } from "react-native";
import {
  CallingState,
  useCall,
  useCallStateHooks,
} from "@stream-io/video-react-native-sdk";

export const OutgoingCallButtonGroup = () => {
  const call = useCall();
  const { useCallCallingState } = useCallStateHooks();
  const callingState = useCallCallingState();

  const hangupCallHandler = useCallback(async () => {
    try {
      if (callingState === CallingState.LEFT) {
        return;
      }
      await call?.leave({ reject: true, reason: "cancel" });
    } catch (error) {
      console.log("Error rejecting Call", error);
    }
  }, [call, callingState]);

  return (
    <View style={styles.buttonGroup}>
      <Pressable
        style={[styles.button, styles.hangupButton]}
        onPress={hangupCallHandler}
      >
        <Text style={styles.callButtonText}>Hang up</Text>
      </Pressable>
    </View>
  );
};

const styles = StyleSheet.create({
  buttonGroup: {
    flexDirection: "row",
    justifyContent: "space-evenly",
  },
  button: {
    height: 80,
    width: 80,
    borderRadius: 40,
    justifyContent: "center",
  },
  hangupButton: {
    backgroundColor: "#FF3742",
  },
  callButtonText: {
    color: "white",
    textAlign: "center",
  },
});
```

## Assembling it all together

Assemble components or pass them to customization props of [`IncomingCall`](https://getstream.io/video/docs/react-native/v2/ui-components/call/incoming-call/#props)/[`OutgoingCall`](https://getstream.io/video/docs/react-native/v2/ui-components/call/outgoing-call/#props).

![Incoming call](https://getstream.io/docs-assets/images/c7179852037b.png)

![Outgoing call](https://getstream.io/docs-assets/images/0b8ce755a2ba.png)

```tsx
import { StyleSheet, View } from "react-native";

export const IncomingCallComponent = () => {
  return (
    <View style={[StyleSheet.absoluteFill, styles.container]}>
      <UserInfoComponent />
      <IncomingCallButtonGroup />
    </View>
  );
};

export const OutgoingCallComponent = () => {
  return (
    <View style={[StyleSheet.absoluteFill, styles.container]}>
      <UserInfoComponent />
      <MediaStreamButtonGroup />
      <OutgoingCallButtonGroup />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#272A30",
    justifyContent: "space-evenly",
  },
});
```

---

For the most recent version of this documentation, visit [https://getstream.io/video/docs/react-native/v2/ui-cookbook/incoming-and-outgoing-call/](https://getstream.io/video/docs/react-native/v2/ui-cookbook/incoming-and-outgoing-call/).