react-native Rendering/OnPress issues

Viewed 40

I'm quite new to react-native and need some help with my code. Currently I'm using expo-location, google autocomplete search, and redux to get the user location and save it. I have 2 questions with my current code.

  1. If the current location is not acquired, then I want to show the screen a screen saying "Enter your address, or hit this button to start saving!", however when I'm trying to use an if statement to do so, I get an error saying -> "Rendered more hooks than during the previous render."

  2. The GetUserLocation function is working, although I'm not sure how to call the function when doing an onPress. I've tried serval ways of calling it, and mostly get an error saying "Error: Invalid hook call. Hooks can only be called inside of the body of a function component."

Thank you in advance, greatly appreciate all the help.

export default function SettingsScreen({ navigation }) {
  const GetUserLocation = () =>  {
    console.log('hi')
  // const [location, setLocation] = useState(null);
  // const [errorMsg, setErrorMsg] = useState(null);

  const dispatch = useDispatch();
  const changeLocation = (location) =>
    dispatch({
      type: 'CHANGE_LOCATION',
      payload: location,
    });

    (async () => {
      console.log('hello')
      // if (Platform.OS === 'android' && !Constants.isDevice) {
      //   setErrorMsg(
      //     'Oops, this will not work on Snack in an Android emulator. Try it on your device!'
      //   );
      //   return;
      // }
      let { status } = await Location.requestForegroundPermissionsAsync();
      // if (status !== 'granted') {
      //   setErrorMsg('Permission to access location was denied');
      //   return;
      // }

      let location = await Location.getCurrentPositionAsync({});
      changeLocation(location.coords);
      console.log(location.coords);
    })();


  let text = 'Waiting..';
  // if (errorMsg) {
  //   text = errorMsg;
  // } else 
  // if (location) {
  //   text = JSON.stringify(location);
  // }

  // return (
  //   JSON.stringify(location)
  // );
}
  
  const isValidState = useSelector((state) => state.locationReducer.currentLocation.location[0])
  //console.log(isValidState)
  if(isValidState == null){
    return (
       <View style={styles.container}>
        {/* <LocationBar/> */}
        <View style = {styles.enterLocationText}>
          <Text style = {{fontSize: 20, fontWeight: 'bold'}}>
            Enter your address, or hit {"\n"}
            this button to start saving!
          </Text>
        </View>
        <View style={styles.searchBox}>
        <TouchableOpacity
          style={styles.location_button}
          onPress={GetUserLocation}
          
        >
          <FontAwesome5 name="location-arrow" size={22} color="#4285F4" />
        </TouchableOpacity>
      </View>
      </View>
    );
  } 

  let newLatitude = isValidState.latitude
  let newLongitude = isValidState.longitude

  const theme = useTheme();

  const initialMapState = {
    markers,
    region: {
      latitude: newLatitude,
      longitude: newLongitude,
      latitudeDelta: 0.04864195044303443,
      longitudeDelta: 0.040142817690068,
    },
  };
  
  const [state, setState] = React.useState(initialMapState);
  let mapIndex = 0;
  let mapAnimation = new Animated.Value(0);

  useEffect(() => {
    mapAnimation.addListener(({ value }) => {
      let index = Math.floor(value / CARD_WIDTH + 0.3); // animate 30% away from landing on the next item
      if (index >= state.markers.length) {
        index = state.markers.length - 1;
      }
      if (index <= 0) {
        index = 0;
      }

      clearTimeout(regionTimeout);

      const regionTimeout = setTimeout(() => {
        if( mapIndex !== index ) {
          mapIndex = index;
          const { coordinate } = state.markers[index];
          _map.current.animateToRegion(
            {
              ...coordinate,
              latitudeDelta: state.region.latitudeDelta,
              longitudeDelta: state.region.longitudeDelta,
            },
            350
          );
        }
      }, 10);
    });
  });

  const interpolations = state.markers.map((marker, index) => {
    const inputRange = [
      (index - 1) * CARD_WIDTH,
      index * CARD_WIDTH,
      ((index + 1) * CARD_WIDTH),
    ];

    const scale = mapAnimation.interpolate({
      inputRange,
      outputRange: [1, 1.5, 1],
      extrapolate: "clamp"
    });

    return { scale };
  });

  const onMarkerPress = (mapEventData) => {
    const markerID = mapEventData._targetInst.return.key;

    let x = (markerID * CARD_WIDTH) + (markerID * 20); 
    if (Platform.OS === 'ios') {
      x = x - SPACING_FOR_CARD_INSET;
    }

    _scrollView.current.scrollTo({x: x, y: 0, animated: true});
  }

  const _map = React.useRef(null);
  const _scrollView = React.useRef(null);
    return (
    <View style={styles.container}>
    <LocationBar/>
      <MapView
        ref={_map}
        initialRegion={state.region}
        region={initialMapState.region}
        style={styles.container}
        provider={PROVIDER_GOOGLE}
        
      >
        <Marker
          
          coordinate={{
            latitude: initialMapState.region.latitude,
            longitude: initialMapState.region.longitude,
          }}
          icon="https://www.robotwoods.com/dev/misc/bluecircle.png"
        />
        {state.markers.map((marker, index) => {
          const scaleStyle = {
            transform: [
              {
                scale: interpolations[index].scale,
              },
            ],
          };
          return (
            <MapView.Marker key={index} coordinate={marker.coordinate} onPress={(e)=>onMarkerPress(e)}>
              <Animated.View style={[styles.markerWrap]}>
                <Animated.Image
                  source={require('../../assets/calzone_thecozyapron_1.jpeg')}
                  style={[styles.marker, scaleStyle]}
                  resizeMode="cover"
                />
              </Animated.View>
            </MapView.Marker>
          );
        })}
      </MapView>
      <View style={styles.searchBox}>
        <TouchableOpacity
          style={styles.location_button}
          onPress={() => GetUserLocation}
          
        >
          <FontAwesome5 name="location-arrow" size={22} color="#4285F4" />
        </TouchableOpacity>
      </View>
      {/* <View style={styles.searchBox}>
        <TextInput 
          placeholder="Search here"
          placeholderTextColor="#000"
          autoCapitalize="none"
          style={{flex:1,padding:0}}
        />
        <Ionicons name="ios-search" size={20} />
      </View> */}
      <ScrollView
        horizontal
        scrollEventThrottle={1}
        showsHorizontalScrollIndicator={false}
        height={50}
        style={styles.chipsScrollView}
        contentInset={{ // iOS only
          top:0,
          left:0,
          bottom:0,
          right:20
        }}
        contentContainerStyle={{
          paddingRight: Platform.OS === 'android' ? 20 : 0
        }}
      >
      </ScrollView>
      <Animated.ScrollView
        ref={_scrollView}
        horizontal
        pagingEnabled
        scrollEventThrottle={1}
        showsHorizontalScrollIndicator={false}
        snapToInterval={CARD_WIDTH + 20}
        snapToAlignment="center"
        style={styles.scrollView}
        contentInset={{
          top: 0,
          left: SPACING_FOR_CARD_INSET,
          bottom: 0,
          right: SPACING_FOR_CARD_INSET
        }}
        contentContainerStyle={{
          paddingHorizontal: Platform.OS === 'android' ? SPACING_FOR_CARD_INSET : 0
        }}
        onScroll={Animated.event(
          [
            {
              nativeEvent: {
                contentOffset: {
                  x: mapAnimation,
                }
              },
            },
          ],
          {useNativeDriver: true}
        )}
      >
        {state.markers.map((marker, index) =>(
          <View style={styles.card} key={index}>
            <Image 
              source={marker.image}
              style={styles.cardImage}
              resizeMode="cover"
            />
            <View style={styles.textContent}>
              <Text numberOfLines={1} style={styles.cardtitle}>{marker.title}</Text>

              <Text numberOfLines={1} style={styles.cardDescription}>{marker.description}</Text>
              <View style={styles.button}>
                <TouchableOpacity
                  onPress={() => {}}
                  style={[styles.signIn, {
                    borderColor: '#FF6347',
                    borderWidth: 1
                  }]}
                >
                  <Text style={[styles.textSign, {
                    color: '#FF6347'
                  }]}>Redeem</Text>
                </TouchableOpacity>
              </View>
            </View>
          </View>
        ))}
      </Animated.ScrollView>
    </View>
  );
};

1 Answers

The first error is due to the conditional early return of your component. If your component returns at the first return, the hooks below it won't be called. React relies on an array of hooks that doesn't change in size between renders. See: https://overreacted.io/why-do-hooks-rely-on-call-order

The second error is telling you that any hooks need to be executed at the root level of the component. Instead of calling the hook in the JSX, store the output of the hook in the function body, and then return that. For example, don't do this:

// won't work, hook not called in function body
return (
  <Button onPress={() => useNavigation().navigate('home')} />
);

Do this instead:

const { navigate } = useNavigation();

return (
  <Button onPress={() => navigate('home')} />
);
Related