OiO.lk Blog javascript Vertical scrolling not working in Expo React Native
javascript

Vertical scrolling not working in Expo React Native


I have an issue with this screen. The elements overflow and I can’t display the sidebar to scroll the content. The strange thing is that in all the other screens I use my components <AppContainer> and <SafeScreen>, and everything works fine. I haven’t included the code for each individual component because the scrolling doesn’t work on any slide.
I’m actually testing on web version only.

import React, { useState, useEffect, useRef } from 'react';
import { View, Text, TouchableOpacity, StyleSheet, Alert, Platform, TextInput, ScrollView, Dimensions, KeyboardAvoidingView } from 'react-native';
import { Ionicons } from '@expo/vector-icons';
import AppContainer from '../AppContainer';
import SafeScreen from '../SafeScreen';
import AsyncStorage from '@react-native-async-storage/async-storage';
import RicercaScuolaComponent from '../RicercaScuolaComponent';
import RicercaPartecipanti from '../RicercaPartecipanti';
import Animated, { useSharedValue, useAnimatedStyle, withTiming } from 'react-native-reanimated';
import loadGoogleMapsAPI from '../webMapComponent';
import PianoViaggiSummary from './PianoViaggiSummary';

import { getAuth } from 'firebase/auth';
import { getFirestore, collection, doc, setDoc } from 'firebase/firestore';

let MapView, Marker, Geocoder, AutocompleteService, PlacesService;;

const NuovoPianoViaggiScreen = ({  }) => {
  const [currentSlide, setCurrentSlide] = useState(0);
  const [partecipanti, setPartecipanti] = useState([]);
  const [scuolaSelezionata, setScuolaSelezionata] = useState(null);
  const [googleMapsLoaded, setGoogleMapsLoaded] = useState(false);
  const [meetingPlace, setMeetingPlace] = useState(null);
  const [searchQuery, setSearchQuery] = useState('');
  const [meetingAddress, setMeetingAddress] = useState('');
  //const [scuolaDestinazione, setScuolaDestinazione] = useState(null);
  const [lastSavedSlide, setLastSavedSlide] = useState(-1);

  const [meetingName, setMeetingName] = useState('');
  const [suggestions, setSuggestions] = useState([]);

  const handleScuolaSelezionata = (scuola) => {
    console.log('Scuola selezionata:', scuola);
    setScuolaSelezionata(scuola);
    
    // Aggiorna il pianoViaggi direttamente
    setPianoViaggi(prev => ({
      ...prev,
      scuola: scuola
    }));
  };


  const [pianoViaggi, setPianoViaggi] = useState({
    luogo: null,
    scuola: null,
    partecipanti: []
  });
  const [region, setRegion] = useState({
    latitude: 41.9028,
    longitude: 12.4964,
    latitudeDelta: 0.0922,
    longitudeDelta: 0.0421,
  });

  const mapRef = useRef(null);
  const opacity = useSharedValue(1);


  useEffect(() => {
   {/*...*/}    }
);

// custom alert
const customAlert = (title, message, buttons) => {
  if (Platform.OS === 'web') {
    if (window.confirm(`${title}\n\n${message}`)) {
      buttons.find(b => b.style === 'default').onPress();
    } else {
      buttons.find(b => b.style === 'cancel').onPress();
    }
  } else {
    Alert.alert(title, message, buttons);
  }
};

const salvaProgressoLocale = async () => {
{/*...*/}
};


const caricaProgressoLocale = async () => {
 {/*...*/}
  }
};
const salvaSuFirestore = async () => {
 {/*...*/}
};

  

  const handleMapPress = (event) => {
   {/*...*/}
  };

  const handleSearch = () => {
   {/*...*/}
  };
  
  const handleSearchInputChange = (text) => {
{/*...*/}
  };

  const renderSuggestion = ({ item }) => (
{/*...*/}
  );

  const renderMap = () => {
    if (!googleMapsLoaded || !MapView || !AutocompleteService || !PlacesService) {
      return <Text>Caricamento della mappa...</Text>;
    }
  
    return (
      <View style={styles.mapContainer}>
  <View style={styles.searchContainer}>
    <TextInput
      style={styles.searchInput}
      value={searchQuery}
      onChangeText={handleSearchInputChange}
      placeholder="Cerca un luogo o selezionalo sulla mappa"
    />
    <TouchableOpacity style={styles.searchButton} onPress={handleSearch}>
      <Ionicons name="search" size={24} color="#fff" />
    </TouchableOpacity>
  </View>
  <MapView
    style={styles.map}
    region={region}
    onRegionChangeComplete={setRegion}
    onPress={handleMapPress}
  >
    {meetingPlace && (
      <Marker
        coordinate={meetingPlace}
        title="Punto di Ritrovo"
        description="Luogo di incontro selezionato"
      />
    )}
  </MapView>
  
  {/* Input per il nome personalizzato del luogo */}
  <TextInput
    style={styles.locationNameInput}
    value={meetingName}
    onChangeText={setMeetingName}  // Cambia il nome personalizzato
    placeholder="Assegna un nome personalizzato al luogo"
  />
</View>

    );
  };
  

  const fadeOutIn = (nextSlide) => {
    opacity.value = withTiming(0, { duration: 300 }, () => {
      setCurrentSlide(nextSlide);
      opacity.value = withTiming(1, { duration: 300 });
    });
  };
  const passaAllaSlideSuccessiva = async () => {
    console.log(`Inizio passaAllaSlideSuccessiva. Slide corrente: ${currentSlide}`);
    let canProceed = true;
  
    if (currentSlide === 0) {
      console.log('Slide 0: Verifica dati del luogo');
      console.log(`meetingPlace: ${JSON.stringify(meetingPlace)}`);
      console.log(`meetingAddress: ${meetingAddress}`);
      console.log(`meetingName: ${meetingName}`);
  
      if (!meetingPlace || !meetingAddress || !meetingName) {
        console.log('Errore: Dati del luogo mancanti');
        customAlert("Errore", "Per favore, seleziona un punto di ritrovo e inserisci un nome per il luogo prima di procedere.");
        canProceed = false;
      } else {
        console.log('Aggiornamento pianoViaggi con i dati del luogo');
        setPianoViaggi(prev => ({
{/*...*/}
          }
        }));
      }
    } else if (currentSlide === 1) {
      console.log('Slide 1: Verifica selezione scuola');
      console.log(`scuolaSelezionata: ${JSON.stringify(scuolaSelezionata)}`);
    
      if (!scuolaSelezionata) {
        console.log('Errore: Scuola non selezionata');
        customAlert("Errore", "Per favore, seleziona una scuola dalla lista prima di procedere.");
        canProceed = false;
      } else {
        console.log('Aggiornamento pianoViaggi con la scuola selezionata');
        // Aggiorna anche il pianoViaggi con la destinazione
        setPianoViaggi(prev => ({
          ...prev, 
          scuola: scuolaSelezionata // Aggiungi qui
        }));
      }
  
    }
  
    if (canProceed) {
    {/*...*/}
  };
  

  const passaAllaSlidePrecedente = () => {
    fadeOutIn(currentSlide - 1);
  };

  const animatedStyle = useAnimatedStyle(() => {
    return {
      opacity: opacity.value,
    };
  });

  const renderSlide = () => {
    const slideContent = (() => {
      switch (currentSlide) {
        case 0:
          return (
            <>
              <Text style={styles.title}>Seleziona il Punto di Ritrovo</Text>
              {renderMap()}
            </>
          );
        case 1:
          return (
            <>
              <Text style={styles.title}>Scuola di Destinazione</Text>
              <RicercaScuolaComponent 
                onScuolaSelezionata={setScuolaSelezionata} 
                scuolaSelezionata={scuolaSelezionata} 
              />
            </>
          );
        case 2:
          return (
            <>
              <Text style={styles.title}>Aggiungi Partecipanti</Text>
              <RicercaPartecipanti 
                scuolaSelezionata={scuolaSelezionata} 
                onPartecipanteSelezionato={(partecipante) => 
                  setPartecipanti([...partecipanti, partecipante])} 
              />
            </>
          );
        case 3:
          return (
            <>
              <Text style={styles.title}>Riepilogo del piano viaggi</Text>
              <PianoViaggiSummary 
                pianoViaggi={pianoViaggi}
                partecipanti={partecipanti}
              />
            </>
          );
        default:
          return null;
      }
    })();
    
    return (
      <AppContainer>
    <SafeScreen style={styles.container}>
      <Animated.View style={[styles.slideContainer, animatedStyle]}>
        {slideContent}
      </Animated.View>
      </SafeScreen>
      </AppContainer>
    );
  };
  return (

      <KeyboardAvoidingView 
        behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
        style={styles.container}
      >
          {renderSlide()}
        <View style={styles.navigationContainer}>
          {currentSlide > 0 && (
            <TouchableOpacity 
              style={styles.navButton} 
              onPress={passaAllaSlidePrecedente}
            >
              <Ionicons name="arrow-back" size={24} color="#fff" />
              <Text style={styles.navButtonText}>Indietro</Text>
            </TouchableOpacity>
          )}
          {currentSlide < 3 && (
            <TouchableOpacity 
              style={styles.navButton} 
              onPress={passaAllaSlideSuccessiva}
            >
              <Text style={styles.navButtonText}>Avanti</Text>
              <Ionicons name="arrow-forward" size={24} color="#fff" />
            </TouchableOpacity>
          )}
        </View>
      </KeyboardAvoidingView>

  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    justifyContent: 'center',
    backgroundColor: '#f4f4f9',
  },
  
  contentContainer: {
    flexGrow: 1,
    padding:20,
    paddingBottom:100,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 20,
    textAlign: 'center',
  },
  subtitle: {
    fontSize: 22,
    fontWeight: 'bold',
    marginTop: 20,
    marginBottom: 10,
    color: '#333',
  },
  input: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 15,
    borderRadius: 10,
    marginBottom: 20,
    backgroundColor: '#fff',
    fontSize: 16,
  },
  suggestionList: {
    flex: 1,
    marginBottom: 20,
  },
  suggestionItem: {
    padding: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#ddd',
    backgroundColor: '#f9f9f9',
    borderRadius: 8,
    marginBottom: 5,
  },
  suggestionText: {
    color: '#333',
    fontSize: 16,
    fontWeight: 'bold',
  },
  addManuallyButton: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'center',
    marginTop: 10,
    marginBottom: 20,
    padding: 10,
    backgroundColor: '#f0e6ff',
    borderRadius: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    marginBottom: 20,
    textAlign: 'center',
  },
  addManuallyText: {
    color: '#6200EE',
    marginLeft: 10,
    fontSize: 16,
  },
  participantList: {
    flex: 1,
  },
  participantItem: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
    padding: 15,
    borderBottomWidth: 1,
    borderBottomColor: '#ddd',
    backgroundColor: '#f9f9f9',
    borderRadius: 8,
    marginBottom: 5,
  },
  participantText: {
    color: '#333',
    fontSize: 16,
  },
  scrollView: {
    flexGrow: 1,
  },
  navButton: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  navButtonText: {
    color: '#fff',
    marginHorizontal: 8,
  },
  searchContainer: {
    flexDirection: 'row',
    marginBottom: 10,
  },
  searchInput: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
    marginRight: 10,
    backgroundColor: '#fff',
  },
  searchButton: {
    backgroundColor: '#6200EE',
    padding: 10,
    borderRadius: 5,
    justifyContent: 'center',
    alignItems: 'center',
  },
  scrollContent: {
    flexGrow: 1,
    paddingBottom: 80, // Aggiungi spazio per la navigation bar
  },
  navigationContainer: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: '#000',
  },
  navigation: {
    flexDirection: 'row',
    justifyContent: 'space-between',
    padding: 16,
  },
  mapContainer: {
    height: 400, // Altezza fissa per la mappa
    marginVertical: 20,
  },
  map: {
    flex: 1,
  },
  locationNameInput: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
    marginTop: 10,
    backgroundColor: '#fff',
  },
  safeArea: {
    flex: 1,
    backgroundColor: '#f4f4f9',
  },
  slideContainer: {
    flex:1,
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#333',
    marginBottom: 20,
    textAlign: 'center',
  },
  mapContainer: {
    height: 400,
    marginVertical: 20,
    width: '100%',
  },
  map: {
    flex: 1,
  },
  searchContainer: {
    flexDirection: 'row',
    marginBottom: 10,
    width: '100%',
  },
  searchInput: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
    marginRight: 10,
    backgroundColor: '#fff',
  },
  searchButton: {
    backgroundColor: '#6200EE',
    padding: 10,
    borderRadius: 5,
    justifyContent: 'center',
    alignItems: 'center',
  },
  locationNameInput: {
    borderWidth: 1,
    borderColor: '#ddd',
    padding: 10,
    borderRadius: 5,
    marginTop: 10,
    backgroundColor: '#fff',
    width: '100%',
  },
  navigationContainer: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: '#6200EE',
    padding: 16,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  navButton: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 10,
  },
  navButtonText: {
    color: '#fff',
    marginHorizontal: 8,
    fontSize: 16,
  },
});

export default NewScreen;
import React from 'react';
import { View, StyleSheet, useWindowDimensions, ScrollView } from 'react-native';

const AppContainer = ({ children }) => {
  const { width } = useWindowDimensions();
  const isDesktop = width > 768;

  return (
    <ScrollView 
      style={styles.scrollView} 
      contentContainerStyle={styles.scrollViewContent}
      keyboardShouldPersistTaps="handled"
    >
      <View style={[styles.container, isDesktop && styles.desktopContainer]}>
        <View style={[styles.content, isDesktop && styles.desktopContent]}>
          {children}
        </View>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
    scrollView: {
      flex: 1,
      padding: 5,
      backgroundColor: '#f5f5f5',
    },
    scrollViewContent: {
      flexGrow: 1,
    },
    container: {
      flex: 1,
      alignItems: 'center',
    },
    desktopContainer: {
      paddingHorizontal: 16,
    },
    content: {
      flex: 1,
      width: '100%',
      maxWidth: 768,
      backgroundColor: '#f5f5f5',
    },
    desktopContent: {
      elevation: 5,
    },
  });

export default AppContainer;
import React from 'react';
import { SafeAreaView, StyleSheet, Platform, View, ScrollView, StatusBar } from 'react-native';
import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';
import { useNavigationState } from '@react-navigation/native';

const SafeScreen = ({ children, style, scrollable = false }) => {
  const navigationState = useNavigationState(state => state);
  
  let tabBarHeight = 0;
  let isInBottomTab = false;

  if (navigationState) {
    const route = navigationState.routes[navigationState.index];
    isInBottomTab = route.state && route.state.type === 'tab';
  }

  if (isInBottomTab) {
    try {
      tabBarHeight = useBottomTabBarHeight();
    } catch (error) {
      console.warn('Failed to get bottom tab bar height:', error);
    }
  }

  const content = (
    <View
      style={[
        styles.content,
        { paddingBottom: Platform.OS === 'android' && isInBottomTab ? tabBarHeight : 0 }
      ]}
    >
      {children}
    </View>
  );

  return (
    <SafeAreaView style={[styles.container, style]}>
      <StatusBar barStyle="dark-content" backgroundColor="white" />
      {content}
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: 'white',
    paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : 0,
  },
  content: {
    flex: 1,
  },
  scrollViewContent: {
    flexGrow: 1,
  },
});

export default SafeScreen;



You need to sign in to view this answers

Exit mobile version