xcarpentier / rn-tourguide

๐ŸšฉMake an interactive step by step tour guide for your react-native app (a rewrite of react-native-copilot)
https://xcarpentier.github.io/rn-tourguide/
Other
725 stars 213 forks source link

Maximum call stack size exceeded #2

Closed skeie closed 4 years ago

skeie commented 4 years ago

Hey, Your library looks great, however I stumble on a problem. I want the tour to start when the app is mounted, so I tried your demo app, but I changed out

    React.useEffect(() => {
        eventEmitter.on('start', () => console.log('start'))
        eventEmitter.on('stop', () => console.log('stop'))
        eventEmitter.on('stepChange', () => console.log(`stepChange`))

        return () => eventEmitter.off('*', null)
    }, [])

with

    React.useEffect(() => {
        start()
    }, [])

Then start runs in an infinite loop. I printed out the values in

const start = async (fromStep) => {
        const currentStep = fromStep
            ? steps[fromStep]
            : getFirstStep();
        if (startTries > MAX_START_TRIES) {
            setStartTries(0);
            return;
        }
        console.log({fromStep, currentStep, steps});
        if (!currentStep) {
            setStartTries(startTries + 1);
            start(fromStep);
        }
        else {
            eventEmitter.emit('start');
            await setCurrentStep(currentStep);
            setVisible(true);
            setStartTries(0);
        }
    };

which is:

currentStep: null
fromStep: undefined
steps: {}

So, currentStep is always null, hence it goes in an infinite loop, it might seem like there is a bug when using it onMount? :)

RN version: 0.62.2 Full code

import * as React from 'react'
import { Image, Platform, StyleSheet, Text, TouchableOpacity, View } from 'react-native'
import {
    TourGuideProvider,
    TourGuideZone,
    TourGuideZoneByPosition,
    useTourGuideController
} from 'rn-tourguide'

const uri = 'https://pbs.twimg.com/profile_images/1223192265969016833/U8AX9Lfn_400x400.jpg'

// Add <TourGuideProvider/> at the root of you app!
function App() {
    return (
        <TourGuideProvider {...{ borderRadius: 16 }}>
            <AppContent />
        </TourGuideProvider>
    )
}

const AppContent = () => {
    const iconProps = { size: 40, color: '#888' }

    // Use Hooks to control!
    const { start, stop, eventEmitter } = useTourGuideController()

    React.useEffect(() => {
        start()
    }, [])

    return (
        <View style={styles.container}>
            {/* Use TourGuideZone only to wrap */}
            <TourGuideZone
                keepTooltipPosition
                zone={2}
                text={'A react-native-copilot remastered! ๐ŸŽ‰'}
                borderRadius={16}>
                <Text style={styles.title}>{'Welcome to the demo of\n"rn-tourguide"'}</Text>
            </TourGuideZone>
            <View style={styles.middleView}>
                <TouchableOpacity style={styles.button} onPress={() => start()}>
                    <Text style={styles.buttonText}>START THE TUTORIAL!</Text>
                </TouchableOpacity>

                <TourGuideZone zone={3} shape={'rectangle_and_keep'}>
                    <TouchableOpacity style={styles.button} onPress={() => start(4)}>
                        <Text style={styles.buttonText}>Step 4</Text>
                    </TouchableOpacity>
                </TourGuideZone>
                <TouchableOpacity style={styles.button} onPress={() => start(2)}>
                    <Text style={styles.buttonText}>Step 2</Text>
                </TouchableOpacity>
                <TouchableOpacity style={styles.button} onPress={stop}>
                    <Text style={styles.buttonText}>Stop</Text>
                </TouchableOpacity>
                <TourGuideZone
                    zone={1}
                    shape="circle"
                    text={'With animated SVG morphing with awesome flubber ๐Ÿฎ๐Ÿ’ฏ'}>
                    <Image source={{ uri }} style={styles.profilePhoto} />
                </TourGuideZone>
            </View>
        </View>
    )
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        backgroundColor: '#fff',
        alignItems: 'center',
        paddingTop: 40
    },
    title: {
        fontSize: 24,
        textAlign: 'center'
    },
    profilePhoto: {
        width: 140,
        height: 140,
        borderRadius: 70,
        marginVertical: 20
    },
    middleView: {
        flex: 1,
        alignItems: 'center'
    },
    button: {
        backgroundColor: '#2980b9',
        paddingVertical: 10,
        paddingHorizontal: 15,
        margin: 2
    },
    buttonText: {
        color: 'white',
        fontSize: 16
    },
    row: {
        width: '100%',
        padding: 15,
        flexDirection: 'row',
        justifyContent: 'space-between'
    },
    activeSwitchContainer: {
        flexDirection: 'row',
        justifyContent: 'space-between',
        marginBottom: 20,
        alignItems: 'center',
        paddingHorizontal: 40
    }
})

export default App
xcarpentier commented 4 years ago

Hi @skeie, Thanks for your report. Are you experiment the same issue even if you provide a step? ie. start(1)

xcarpentier commented 4 years ago

Hi @skeie, You can now use version v2.4.0 and add startAtMount props to TourGuideProvider Please let me know if you still see issues.

xcarpentier commented 4 years ago

Hi @skeie,

You can also use a new variable from hook: canStart

  // Can start at mount ๐ŸŽ‰
  // you need to wait until everything is registered ๐Ÿ˜
  React.useEffect(() => {
    if (canStart) {
      // ๐Ÿ‘ˆ test if you can start otherwise nothing will happen
      start()
    }
  }, [canStart]) // ๐Ÿ‘ˆ don't miss it!