Skip to content

berasumitdev/react-native-copilot-demo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-native-copilot — Complete Developer Guide

A deep-dive reference for interviews and production projects. Covers installation, all APIs, real bugs encountered, fixes, and best practices.


Table of Contents

  1. What is react-native-copilot?
  2. Installation
  3. Core Architecture & How It Works
  4. CopilotProvider — The Root Setup
  5. CopilotStep — Marking Elements
  6. walkthroughable — Making Components Highlightable
  7. useCopilot Hook — Full API
  8. Overlays: view vs svg
  9. Custom Tooltip Component
  10. Custom Step Number Component
  11. Navigation Functions Explained
  12. Event System (copilotEvents)
  13. Styling & Customization
  14. Custom SVG Mask Path
  15. ScrollView Support
  16. i18n / Custom Labels
  17. Real Bugs & Fixes (From This Project)
  18. Complete Working Example
  19. Interview Q&A Cheat Sheet

1. What is react-native-copilot?

react-native-copilot is a React Native library that lets you build step-by-step onboarding tours — the kind where a dark overlay appears and a tooltip highlights specific UI elements one at a time.

Real-world use cases:

  • First-time user onboarding
  • Feature discovery after an update
  • Contextual help / guided walkthroughs
  • Accessibility-friendly tutorials

2. Installation

# Install the main package
yarn add react-native-copilot
# or
npm install --save react-native-copilot

# Install SVG support (recommended for smooth animation)
expo install react-native-svg
# or for bare React Native:
yarn add react-native-svg
cd ios && pod install

Package versions used in this project:

{
  "react-native-copilot": "^3.3.3",
  "react-native-svg": "15.12.1"
}

3. Core Architecture & How It Works

Understanding the internals helps you debug and customize effectively.

CopilotProvider (Context)
    │
    ├── Registers all CopilotSteps (by order number)
    ├── Manages current step state
    ├── Controls overlay rendering
    └── Exposes useCopilot() hook to all children

CopilotStep (wrapper)
    │
    ├── Registers itself with Provider on mount
    ├── Measures its own position on screen (onLayout)
    └── Passes { ref, onLayout } as `copilot` prop to child

walkthroughable(Component)
    │
    └── HOC that spreads the `copilot` prop onto the root element
        so the library can measure where to draw the overlay mask

useCopilot()
    │
    └── Returns: start, stop, goToNext, goToPrev, goToNth,
                 currentStep, isFirstStep, isLastStep, copilotEvents

Flow when start() is called:

  1. Library sorts all registered CopilotSteps by order
  2. Measures position of step 1's element on screen
  3. Renders the overlay with a "hole" cut out around that element
  4. Renders the tooltip near the hole
  5. User taps Next → moves to step 2, re-measures, re-renders overlay

4. CopilotProvider — The Root Setup

Wrap your entire app (or the relevant screen tree) with CopilotProvider.

// _layout.tsx (Expo Router) or App.tsx
import { CopilotProvider } from "react-native-copilot";

export default function RootLayout() {
  return (
    <CopilotProvider
      overlay="svg"              // "svg" | "view"
      verticalOffset={36}        // Compensates for status bar height
      backdropColor="rgba(0,0,0,0.6)"
      arrowColor="#007AFF"
      tooltipStyle={{ borderRadius: 12 }}
      labels={{
        previous: "Back",
        next: "Next",
        skip: "Skip",
        finish: "Done",
      }}
    >
      <Stack>
        <Stack.Screen name="index" options={{ headerShown: false }} />
      </Stack>
    </CopilotProvider>
  );
}

All CopilotProvider Props

Prop Type Default Description
overlay "svg" | "view" auto-detected Which overlay renderer to use
verticalOffset number 0 Shifts tooltip position vertically — use 36 to fix status bar misalignment
backdropColor string "rgba(0,0,0,0.4)" The dark overlay color
arrowColor string "#fff" Tooltip arrow color
tooltipStyle StyleProp<ViewStyle> {} Extra styles on the tooltip wrapper
tooltipComponent React.ComponentType built-in Your custom tooltip component
stepNumberComponent React.ComponentType built-in Your custom step number badge
labels object English strings Custom button labels
svgMaskPath function rectangular Custom SVG cutout shape
animationDuration number 300 Overlay animation speed (ms)
stopOnOutsideClick boolean false Stop tour when user taps outside tooltip
active boolean true Whether the provider is active

5. CopilotStep — Marking Elements

CopilotStep is a wrapper that registers a UI element as a tour step.

import { CopilotStep } from "react-native-copilot";

<CopilotStep
  text="This button submits your form."
  order={1}
  name="submit-button"
  active={true}          // optional — set false to skip this step
>
  <WalkthroughableTouchableOpacity style={styles.button}>
    <Text>Submit</Text>
  </WalkthroughableTouchableOpacity>
</CopilotStep>

CopilotStep Props

Prop Type Required Description
name string Unique identifier for this step
order number Position in the tour sequence (1-indexed)
text string Description shown in the tooltip
active boolean false skips this step entirely

Important rules:

  • order values must be unique across all steps
  • Steps are always sorted by order, not by render order in JSX
  • All CopilotStep components must be mounted before calling start()

6. walkthroughable — Making Components Highlightable

walkthroughable is a Higher-Order Component (HOC) that makes any React Native component work with the copilot overlay system.

import { walkthroughable } from "react-native-copilot";
import { View, Text, TouchableOpacity, Image } from "react-native";

// Create walkthroughable versions of any built-in component
const WalkthroughableView = walkthroughable(View);
const WalkthroughableText = walkthroughable(Text);
const WalkthroughableTouchableOpacity = walkthroughable(TouchableOpacity);
const WalkthroughableImage = walkthroughable(Image);

// Usage — exactly like the original component
<CopilotStep text="..." order={1} name="...">
  <WalkthroughableView style={styles.card}>
    <Text>Content inside</Text>
  </WalkthroughableView>
</CopilotStep>

Using Custom Components

If you have your own custom component, spread the copilot prop onto its outermost element:

import { CopilotStep } from "react-native-copilot";

// Your custom component must accept and spread the copilot prop
const MyCard = ({ copilot, children }) => (
  <View {...copilot} style={styles.card}>  {/* <-- spread here */}
    {children}
  </View>
);

// Usage
<CopilotStep text="This is a card" order={1} name="card">
  <MyCard>
    <Text>Hello</Text>
  </MyCard>
</CopilotStep>

The copilot prop contains { ref, onLayout } — both are needed for position measurement.


7. useCopilot Hook — Full API

import { useCopilot } from "react-native-copilot";

const MyComponent = () => {
  const {
    start,          // () => void — starts the tour from step 1
    stop,           // () => void — stops/ends the tour
    goToNext,       // () => void — moves to next step
    goToPrev,       // () => void — moves to previous step
    goToNth,        // (n: number) => void — jump to step N (1-indexed)
    currentStep,    // Step | undefined — the currently active step object
    isFirstStep,    // boolean
    isLastStep,     // boolean
    copilotEvents,  // EventEmitter — for start/stop/stepChange events
  } = useCopilot();
};

start()

// Basic — starts from step 1
start();

// With a ScrollView ref — library will auto-scroll to each step
start(undefined, scrollViewRef);

// From a specific step name
start("search");  // starts from the step named "search"

goToNth()

Jumps to any step by its order number (1-indexed):

// Jump to step 3
goToNth(3);

// Useful for "pausing" the tour for user input:
goToNth(5);   // skip to step 5 after user completes an action

8. Overlays: view vs svg

SVG Overlay (recommended)

<CopilotProvider overlay="svg">
  • Uses an SVG <Path> to draw the cutout
  • Smooth animation as mask moves between steps
  • Requires react-native-svg
  • Best for iOS and Android

View Overlay (fallback)

<CopilotProvider overlay="view">
  • Uses 4 <View> rectangles around the target element
  • No animation (or sluggish on some Android devices)
  • No extra dependencies
  • Use only when SVG isn't available

Auto-detection: If you don't specify overlay, the library checks if react-native-svg is installed and uses SVG if available, otherwise falls back to view.


9. Custom Tooltip Component

Replace the built-in tooltip with your own branded design:

import { useCopilot } from "react-native-copilot";
import { View, Text, TouchableOpacity, StyleSheet } from "react-native";

const CustomTooltip = () => {
  const {
    isFirstStep,
    isLastStep,
    goToNext,
    goToPrev,
    goToNth,
    stop,
    currentStep,
  } = useCopilot();

  return (
    <View style={styles.tooltip}>
      {/* Step description */}
      <Text style={styles.tooltipText}>{currentStep?.text}</Text>

      {/* Navigation row */}
      <View style={styles.buttonRow}>
        <TouchableOpacity onPress={stop}>
          <Text style={styles.skipText}>Skip</Text>
        </TouchableOpacity>

        <View style={styles.navButtons}>
          {!isFirstStep && (
            <TouchableOpacity onPress={goToPrev} style={styles.btn}>
              <Text>← Back</Text>
            </TouchableOpacity>
          )}

          <TouchableOpacity
            onPress={isLastStep ? stop : goToNext}
            style={[styles.btn, styles.primaryBtn]}
          >
            <Text style={styles.primaryText}>
              {isLastStep ? "Finish ✓" : "Next →"}
            </Text>
          </TouchableOpacity>
        </View>
      </View>
    </View>
  );
};

const styles = StyleSheet.create({
  tooltip: {
    backgroundColor: "#1C1C1E",
    borderRadius: 14,
    padding: 16,
    maxWidth: 300,
  },
  tooltipText: { color: "#fff", fontSize: 15, marginBottom: 12 },
  buttonRow: { flexDirection: "row", justifyContent: "space-between", alignItems: "center" },
  skipText: { color: "#8E8E93", fontSize: 13 },
  navButtons: { flexDirection: "row", gap: 8 },
  btn: { paddingHorizontal: 14, paddingVertical: 8, borderRadius: 8, backgroundColor: "#2C2C2E" },
  primaryBtn: { backgroundColor: "#007AFF" },
  primaryText: { color: "#fff", fontWeight: "600" },
});

// Register it
<CopilotProvider tooltipComponent={CustomTooltip}>
  <App />
</CopilotProvider>

10. Custom Step Number Component

import { useCopilot } from "react-native-copilot";

const StepNumber = () => {
  const { currentStep } = useCopilot();

  return (
    <View style={{
      width: 30, height: 30, borderRadius: 15,
      backgroundColor: "#007AFF", justifyContent: "center", alignItems: "center"
    }}>
      <Text style={{ color: "#fff", fontWeight: "700" }}>
        {currentStep?.order}
      </Text>
    </View>
  );
};

<CopilotProvider stepNumberComponent={StepNumber}>
  <App />
</CopilotProvider>

11. Navigation Functions Explained

Function What it does Notes
start() Begin tour from step 1 All steps must be mounted first
stop() End the tour immediately Fires the stop event
goToNext() Advance one step Does nothing if on last step
goToPrev() Go back one step Does nothing if on first step
goToNth(n) Jump to step n n matches order prop, 1-indexed

Pause Pattern (for user interaction mid-tour)

const { goToNth, stop } = useCopilot();

// Tour is on step 3 (a form field)
// User fills in the form, then you resume:
const handleFormComplete = () => {
  goToNth(4); // jump to step 4 to continue the tour
};

12. Event System (copilotEvents)

The library uses mitt (a tiny event emitter) under the hood.

import { useCopilot } from "react-native-copilot";
import { useEffect } from "react";

const HomeScreen = () => {
  const { copilotEvents } = useCopilot();

  useEffect(() => {
    // Tour started
    const onStart = () => console.log("Tour started");

    // Tour ended or skipped
    const onStop = () => {
      console.log("Tour finished");
      // e.g. save to AsyncStorage that user completed onboarding
    };

    // Step changed — receives the new Step object
    const onStepChange = (step) => {
      console.log("Now on step:", step.name, step.order);
    };

    copilotEvents.on("start", onStart);
    copilotEvents.on("stop", onStop);
    copilotEvents.on("stepChange", onStepChange);

    // Always clean up listeners
    return () => {
      copilotEvents.off("start", onStart);
      copilotEvents.off("stop", onStop);
      copilotEvents.off("stepChange", onStepChange);
    };
  }, []);
};

Available Events

Event When it fires Argument
start Tour begins none
stop Tour ends or is skipped none
stepChange Moving to a new step Step object

Step Object Shape

type Step = {
  name: string;       // The name prop you gave CopilotStep
  order: number;      // The order prop
  text: string;       // The text prop
  target: any;        // ref to the measured element
  wrapper: any;       // ref to the CopilotStep wrapper
};

13. Styling & Customization

Tooltip Style

<CopilotProvider
  tooltipStyle={{
    backgroundColor: "#1C1C1E",
    borderRadius: 14,
    paddingTop: 8,
    // Fixed width (override dynamic calculation):
    width: Dimensions.get("window").width - 32,
    maxWidth: Dimensions.get("window").width - 32,
    left: 16,
  }}
>

Arrow Color

<CopilotProvider arrowColor="#007AFF">

Backdrop / Overlay Color

<CopilotProvider backdropColor="rgba(0, 0, 50, 0.85)">

Vertical Offset

Critical for apps with status bars or custom headers:

<CopilotProvider verticalOffset={36}>
  {/* 36 works well for most iOS/Android status bar heights */}

14. Custom SVG Mask Path

By default the mask cuts a rectangle around the target. You can change it to any shape.

Circle Mask

const circleMaskPath = ({ position, canvasSize }) =>
  `M0,0H${canvasSize.x}V${canvasSize.y}H0V0Z` +
  `M${position.x._value},${position.y._value}` +
  `Za50 50 0 1 0 100 0 50 50 0 1 0-100 0`;

<CopilotProvider svgMaskPath={circleMaskPath}>

Per-Step Custom Shape

const customPath = ({ position, size, canvasSize, step }) => {
  if (step?.name === "avatar") {
    // Circle for the avatar step
    return `M0,0H${canvasSize.x}V${canvasSize.y}H0V0Z` +
           `M${position.x._value},${position.y._value}Za50 50 0 1 0 100 0 50 50 0 1 0-100 0`;
  }
  // Default rectangle for all other steps
  return `M0,0H${canvasSize.x}V${canvasSize.y}H0V0Z` +
         `M${position.x._value},${position.y._value}` +
         `H${position.x._value + size.x._value}` +
         `V${position.y._value + size.y._value}` +
         `H${position.x._value}V${position.y._value}Z`;
};

<CopilotProvider svgMaskPath={customPath}>

svgMaskPath Function Signature

type SvgMaskPathFn = (args: {
  size: Animated.ValueXY;      // width/height of the highlighted element
  position: Animated.ValueXY;  // x/y position on screen
  canvasSize: { x: number; y: number }; // full screen dimensions
  step: Step;                  // the current step object
}) => string;                  // must return a valid SVG path string

15. ScrollView Support

When your tour steps span a scrollable list, pass the ScrollView ref to start():

import { useRef } from "react";
import { ScrollView } from "react-native";
import { useCopilot } from "react-native-copilot";

const HomeScreen = () => {
  const { start } = useCopilot();
  const scrollRef = useRef(null);

  useEffect(() => {
    const timer = setTimeout(() => {
      start(undefined, scrollRef); // pass ref as second argument
    }, 1000);
    return () => clearTimeout(timer);
  }, []);

  return (
    <ScrollView ref={scrollRef}>
      {/* CopilotSteps here */}
    </ScrollView>
  );
};

The library will automatically scroll to bring each highlighted element into view.


16. i18n / Custom Labels

<CopilotProvider
  labels={{
    previous: "Zurück",      // German
    next: "Weiter",
    skip: "Überspringen",
    finish: "Beenden",
  }}
>

// Or Hindi:
labels={{
  previous: "पिछला",
  next: "अगला",
  skip: "छोड़ें",
  finish: "समाप्त",
}}

17. Real Bugs & Fixes (From This Project)

These are real bugs encountered during development of this demo, documented so you never hit them again.


Bug 1: App renders nothing — early return;

Symptom: Blank white screen, no errors.

Cause: JavaScript's Automatic Semicolon Insertion (ASI). When return is on its own line, JS treats it as return undefined and the JSX below it is dead code.

// ❌ BROKEN — returns undefined, JSX never executes
export default function RootLayout() {
  return;
  <CopilotProvider overlay="svg">
    <Stack />
  </CopilotProvider>;
}

// ✅ FIXED — parentheses attach the JSX to the return
export default function RootLayout() {
  return (
    <CopilotProvider overlay="svg">
      <Stack />
    </CopilotProvider>
  );
}

Rule: Any time JSX starts on a new line after return, always wrap in ().


Bug 2: Tour never shows — Infinite loop

Symptom: Tour starts, then immediately restarts in a loop (you can see the tooltip flickering).

Cause: start from useCopilot() is a new function reference on every render. Putting it in useEffect's dependency array tells React "re-run this effect when start changes" — which is every render.

// ❌ BROKEN — infinite loop
useEffect(() => {
  const timer = setTimeout(() => start(), 600);
  return () => clearTimeout(timer);
}, [start]); // <-- start changes every render

// ✅ FIXED — empty deps, runs once on mount
useEffect(() => {
  const timer = setTimeout(() => start(), 1000);
  return () => clearTimeout(timer);
}, []); // eslint-disable-line react-hooks/exhaustive-deps

Bug 3: Tour doesn't start at all — Strict Mode + useRef guard

Symptom: After the loop fix with useRef, the tour still doesn't start in development.

Cause: React Strict Mode (enabled by default in Expo/development) intentionally double-mounts components to help detect side effects. The sequence is:

1st mount  → hasStarted.current = false → set true, schedule timer
unmount    → cleanup fires, timer CLEARED
2nd mount  → hasStarted.current = true  → skips entirely ❌

The useRef persists across the remount so the guard is already true on the second mount.

// ❌ BROKEN in development (Strict Mode)
const hasStarted = useRef(false);
useEffect(() => {
  if (hasStarted.current) return;
  hasStarted.current = true;
  const timer = setTimeout(() => start(), 600);
  return () => clearTimeout(timer);
}, []);

// ✅ FIXED — let cleanup handle it, use longer delay for safety
useEffect(() => {
  const timer = setTimeout(() => start(), 1000);
  return () => clearTimeout(timer);
}, []);
// Strict Mode: 1st timer clears, 2nd mount starts a fresh timer ✓
// Production: runs once normally ✓

Bug 4: Tooltip appears at wrong position

Symptom: The overlay highlight is correct but the tooltip box floats at the wrong Y position on screen.

Fix: Add verticalOffset to CopilotProvider to compensate for the status bar height:

<CopilotProvider overlay="svg" verticalOffset={36}>

18. Complete Working Example

_layout.tsx

import { Stack } from "expo-router";
import { CopilotProvider } from "react-native-copilot";

export default function RootLayout() {
  return (
    <CopilotProvider overlay="svg" verticalOffset={36}>
      <Stack>
        <Stack.Screen name="index" options={{ headerShown: false }} />
      </Stack>
    </CopilotProvider>
  );
}

index.tsx

import React, { useEffect } from "react";
import {
  StyleSheet, Text, View, TouchableOpacity,
  SafeAreaView, ScrollView, StatusBar,
} from "react-native";
import { CopilotStep, walkthroughable, useCopilot } from "react-native-copilot";

const WalkthroughableView = walkthroughable(View);
const WalkthroughableTouchableOpacity = walkthroughable(TouchableOpacity);

export default function HomeScreen() {
  const { start } = useCopilot();

  useEffect(() => {
    // Empty deps [] = runs once. 1000ms delay ensures all CopilotSteps are mounted.
    const timer = setTimeout(() => start(), 1000);
    return () => clearTimeout(timer);
  }, []);

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle="dark-content" />
      <ScrollView contentContainerStyle={styles.scroll} showsVerticalScrollIndicator={false}>

        {/* STEP 1 — Header */}
        <CopilotStep text="Your personalized greeting and profile avatar." order={1} name="header">
          <WalkthroughableView style={styles.header}>
            <View>
              <Text style={styles.greeting}>Good morning 👋</Text>
              <Text style={styles.username}>Alex Johnson</Text>
            </View>
            <View style={styles.avatarContainer}>
              <Text style={styles.avatarText}>AJ</Text>
              <View style={styles.notificationDot} />
            </View>
          </WalkthroughableView>
        </CopilotStep>

        {/* STEP 2 — Search */}
        <CopilotStep text="Search for anything inside the app." order={2} name="search">
          <WalkthroughableTouchableOpacity style={styles.searchBar}>
            <Text style={styles.searchIcon}>🔍</Text>
            <Text style={styles.searchPlaceholder}>Search anything...</Text>
          </WalkthroughableTouchableOpacity>
        </CopilotStep>

        {/* STEP 3 — Stats */}
        <CopilotStep text="Your key metrics at a glance." order={3} name="stats">
          <WalkthroughableView style={styles.statsRow}>
            <View style={[styles.statCard, { borderLeftColor: "#007AFF" }]}>
              <Text style={styles.statValue}>24</Text>
              <Text style={styles.statLabel}>Tasks Done</Text>
            </View>
            <View style={[styles.statCard, { borderLeftColor: "#FF9500" }]}>
              <Text style={styles.statValue}>8</Text>
              <Text style={styles.statLabel}>In Progress</Text>
            </View>
            <View style={[styles.statCard, { borderLeftColor: "#FF3B30" }]}>
              <Text style={styles.statValue}>🔥 12</Text>
              <Text style={styles.statLabel}>Day Streak</Text>
            </View>
          </WalkthroughableView>
        </CopilotStep>

        {/* STEP 4 — Quick Actions */}
        <Text style={styles.sectionTitle}>Quick Actions</Text>
        <CopilotStep text="Shortcuts to the most-used features." order={4} name="actions">
          <WalkthroughableView style={styles.quickActionsGrid}>
            {[
              { icon: "➕", label: "New Task" },
              { icon: "📷", label: "Scan Doc" },
              { icon: "💬", label: "Chat" },
              { icon: "⏰", label: "Reminder" },
            ].map((a) => (
              <TouchableOpacity key={a.label} style={styles.quickAction}>
                <View style={styles.quickActionIcon}>
                  <Text style={{ fontSize: 26 }}>{a.icon}</Text>
                </View>
                <Text style={styles.quickActionLabel}>{a.label}</Text>
              </TouchableOpacity>
            ))}
          </WalkthroughableView>
        </CopilotStep>

        {/* STEP 5 — Activity */}
        <Text style={styles.sectionTitle}>Recent Activity</Text>
        <CopilotStep text="Pick up right where you left off." order={5} name="activity">
          <WalkthroughableView style={styles.activityCard}>
            {[
              { icon: "✅", title: "Completed 'Design Review'", time: "2h ago" },
              { icon: "📝", title: "Added note to Project X", time: "5h ago" },
              { icon: "📅", title: "Meeting scheduled", time: "Yesterday" },
            ].map((item, i, arr) => (
              <View key={item.title}>
                <View style={styles.activityRow}>
                  <Text style={{ fontSize: 22, marginRight: 12 }}>{item.icon}</Text>
                  <View style={{ flex: 1 }}>
                    <Text style={styles.activityTitle}>{item.title}</Text>
                    <Text style={styles.activityTime}>{item.time}</Text>
                  </View>
                </View>
                {i < arr.length - 1 && <View style={styles.divider} />}
              </View>
            ))}
          </WalkthroughableView>
        </CopilotStep>

        {/* STEP 6 — Bottom Nav */}
        <CopilotStep text="Navigate between sections using these tabs." order={6} name="navbar">
          <WalkthroughableView style={styles.bottomNav}>
            {[
              { icon: "🏠", label: "Home", active: true },
              { icon: "📊", label: "Stats", active: false },
              { icon: "🔔", label: "Alerts", active: false },
              { icon: "⚙️", label: "Settings", active: false },
            ].map((item) => (
              <TouchableOpacity key={item.label} style={styles.navItem}>
                <Text style={{ fontSize: 22 }}>{item.icon}</Text>
                <Text style={[styles.navLabel, item.active && styles.navLabelActive]}>
                  {item.label}
                </Text>
              </TouchableOpacity>
            ))}
          </WalkthroughableView>
        </CopilotStep>

      </ScrollView>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1, backgroundColor: "#F2F2F7" },
  scroll: { paddingHorizontal: 20, paddingBottom: 20 },
  header: { flexDirection: "row", justifyContent: "space-between", alignItems: "center", marginTop: 16, marginBottom: 20 },
  greeting: { fontSize: 14, color: "#8E8E93" },
  username: { fontSize: 22, fontWeight: "700", color: "#1C1C1E" },
  avatarContainer: { position: "relative" },
  avatarText: { width: 46, height: 46, borderRadius: 23, backgroundColor: "#007AFF", color: "#fff", fontWeight: "700", fontSize: 16, textAlign: "center", lineHeight: 46 },
  notificationDot: { position: "absolute", top: 0, right: 0, width: 12, height: 12, borderRadius: 6, backgroundColor: "#FF3B30", borderWidth: 2, borderColor: "#F2F2F7" },
  searchBar: { flexDirection: "row", alignItems: "center", backgroundColor: "#fff", borderRadius: 12, paddingHorizontal: 14, paddingVertical: 12, marginBottom: 20, elevation: 2 },
  searchIcon: { fontSize: 16, marginRight: 8 },
  searchPlaceholder: { color: "#8E8E93", fontSize: 15 },
  statsRow: { flexDirection: "row", gap: 10, marginBottom: 24 },
  statCard: { flex: 1, backgroundColor: "#fff", borderRadius: 12, padding: 14, borderLeftWidth: 4, elevation: 2 },
  statValue: { fontSize: 20, fontWeight: "800", color: "#1C1C1E", marginBottom: 4 },
  statLabel: { fontSize: 11, color: "#8E8E93" },
  sectionTitle: { fontSize: 17, fontWeight: "600", color: "#1C1C1E", marginBottom: 12 },
  quickActionsGrid: { flexDirection: "row", justifyContent: "space-between", marginBottom: 28 },
  quickAction: { alignItems: "center", gap: 6 },
  quickActionIcon: { width: 60, height: 60, borderRadius: 16, backgroundColor: "#007AFF20", justifyContent: "center", alignItems: "center" },
  quickActionLabel: { fontSize: 11, color: "#3C3C43", fontWeight: "500" },
  activityCard: { backgroundColor: "#fff", borderRadius: 16, padding: 16, marginBottom: 28, elevation: 2 },
  activityRow: { flexDirection: "row", alignItems: "center", paddingVertical: 10 },
  activityTitle: { fontSize: 14, fontWeight: "500", color: "#1C1C1E" },
  activityTime: { fontSize: 12, color: "#8E8E93", marginTop: 2 },
  divider: { height: 1, backgroundColor: "#F2F2F7" },
  bottomNav: { flexDirection: "row", backgroundColor: "#fff", borderRadius: 20, paddingVertical: 12, paddingHorizontal: 10, elevation: 4 },
  navItem: { flex: 1, alignItems: "center", gap: 4 },
  navLabel: { fontSize: 10, color: "#8E8E93", fontWeight: "500" },
  navLabelActive: { color: "#007AFF", fontWeight: "700" },
});

19. Interview Q&A Cheat Sheet

Q: What is react-native-copilot and when would you use it? A: It's a library for building guided onboarding tours in React Native. You use it when you want to highlight specific UI elements with an overlay and tooltip, typically for first-time user onboarding or feature discovery after updates.

Q: How does walkthroughable work internally? A: It's a Higher-Order Component that wraps any React Native component and spreads a copilot prop (containing ref and onLayout) onto the root element. This allows the library to measure the element's screen position and size to draw the overlay cutout accurately.

Q: Why must CopilotStep elements be mounted before calling start()? A: Because start() sorts and measures all registered steps immediately. If a step isn't mounted yet, it won't be in the registry, so it will be skipped. A setTimeout delay (500–1000ms) ensures the render cycle completes before the tour begins.

Q: What causes the infinite loop bug with useEffect and start? A: The start function from useCopilot() is recreated on every render. Listing it in useEffect's dependency array causes the effect to re-run every time the component renders, which calls start() again, which causes another render — an infinite loop. The fix is an empty [] dependency array.

Q: How does React Strict Mode affect copilot tours? A: Strict Mode double-mounts components in development. If you use a useRef guard (hasStarted.current), the ref persists across the remount — so the second (real) mount sees hasStarted = true and skips the tour entirely. The fix is to rely on cleanup (clearTimeout) instead of a ref guard.

Q: What's the difference between view and svg overlays? A: The view overlay uses 4 <View> rectangles placed around the target element — no animation and no extra dependencies. The svg overlay uses an SVG Path to draw the cutout — smooth animated transitions between steps, but requires react-native-svg.

Q: How do you pause a tour for user interaction? A: Call stop() to hide the overlay, let the user interact, then call goToNth(n) with the next step's order number to resume. Do not call start() again as it restarts from step 1.

Q: What does verticalOffset do? A: It shifts the tooltip's vertical position to compensate for the status bar or header height. Without it, the tooltip can appear shifted upward or misaligned relative to the highlighted element. A value of 36 works for most iOS/Android configurations.

Q: How do you listen for when the tour ends? A: Use copilotEvents.on("stop", callback) from the useCopilot() hook. Always clean up with copilotEvents.off("stop", callback) in the useEffect cleanup function to prevent memory leaks.

Q: Can you highlight only some steps conditionally? A: Yes. Use the active prop on CopilotStep. Setting active={false} causes that step to be skipped in the tour sequence while the component still renders normally.


Guide written based on react-native-copilot v3.3.3 with Expo SDK 54 / React Native 0.81

About

react-native-copilot — Complete Developer Guide

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors