스크롤 뷰 & 드롭다운 오류 => PickerSelect

dev bourgeois·2024년 8월 15일

React-Native 개발

목록 보기
12/16
post-thumbnail
npm install react-native-dropdown-picker
npm install react-native-picker-select

이 라이브러리들을 사용해서 드롭다운을 사용했는데

VirtualizedLists 경고로 FlatList, SectionList, VirtualizedList를 ScrollView 내부에 같은 방향으로 중첩해서 사용하면 오류가 발생한다.

VirtualizedLists는 자체적으로 성능을 최적화하기 위해 가상화된 리스트(즉, 화면에 보이는 항목만 렌더링하는 방식)를 사용한다.

'하지만, ScrollView 내부에 같은 방향(예: 수직 스크롤)으로 VirtualizedLists를 넣으면, 이 가상화 기능이 제대로 동작하지 않을 수 있다. 이로 인해 성능 문제나 의도치 않은 버그가 발생한 것이다.


❇️ 해결 방법

1. ScrollView 를 제거한다.

그런데 내가 구현한 화면은 ScrollView가 필요해서 다른 방법을 찾았다. 드롭다운 대신 다른 라이브러리를 사용하는 것이다.

2. 드롭 다운 대신 PickerSelect 라이브러리를 사용한다.

npm install @react-native-picker/picker
import RNPickerSelect from 'react-native-picker-select';

 return (
    <View style={styles.selectContainer}>
          <RNPickerSelect
            onValueChange={handleValueChange}
            items={items}
            placeholder={{label: '앨범을 선택하세요', value: null}}
            style={pickerSelectStyles}
            useNativeAndroidPickerStyle={false}
          />
        </View>
   
   );

...

const pickerSelectStyles = StyleSheet.create({
    inputIOS: {
        fontSize: 16,
        height: 50, 
        width: 300, 
        color: '#000000',
        borderColor: '#000000', 
        borderWidth: 1, 
        borderRadius: 12,
        padding: 10
    },
    inputAndroid: {
        fontSize: 16,
        height: 50, 
        width: 300, 
        color: '#000000',
        borderColor: '#000000', 
        borderWidth: 1, 
        borderRadius: 12,
        padding: 10
    },
});

inputIOS, inputAndroid 스타일링을 각각 해줄 수 있다.

PickerSelect가 사용된 전체 코드

import React, {useState, useEffect} from 'react';
import {
  View,
  Text,
  StyleSheet,
  Image,
  TouchableOpacity,
  ScrollView,
  Alert,
} from 'react-native';
import RNPickerSelect from 'react-native-picker-select';
import {useSelector, useDispatch} from 'react-redux';
import {useNavigation, useRoute} from '@react-navigation/native';
import AlbumPlus from '../../components/Modal/AlbumPlus';
import {GetAlbumList} from '../../api/GetAlbumList';
import {AlbumSave} from '../../api/AlbumSave';

const Filter5 = () => {
  const navigation = useNavigation();
  const [selectedOption, setSelectedOption] = useState(null);
  const [plusVisible, setPlusVisible] = useState(false);
  const [selectedGroupIds, setSelectedGroupIds] = useState([]);
  const route = useRoute();
  const {groupedImages, imageIds} = route.params;
  const [items, setItems] = useState([]);
  const albumList = useSelector(state => state.AlbumListReducer.albumList);
  const dispatch = useDispatch();

  const handleNavigation = () => {
    navigation.navigate('Filter');
  };

  useEffect(() => {
    if (!albumList.last && albumList.first) {
      dispatch(GetAlbumList(null, 50));
    }
  }, [albumList, dispatch]);

  useEffect(() => {
    const newItems = albumList.map(album => ({
      label: album.searchedAlbumName,
      value: album.albumId,
    }));

    const addNewAlbumItem = {label: '새 앨범 추가하기', value: 'add_new_album'};
    if (!newItems.some(item => item.value === 'add_new_album')) {
      newItems.push(addNewAlbumItem);
    }
    setItems(newItems);
  }, [albumList]);

  const handleValueChange = async value => {
    console.log('Selected value:', value);
    if (value === 'add_new_album') {
      setPlusVisible(true);
    } else if (value) {
      setSelectedOption(value);
      console.log('Selected Group IDs:', selectedGroupIds);
      if (selectedGroupIds.length > 0) {
        const selectedImageIds = selectedGroupIds.flatMap(
          groupId => imageIds[groupId] || [],
        );
        await handleAlbumSelection(value, selectedImageIds);
      } else {
        console.log('No group selected');
        Alert.alert('Error', '그룹을 먼저 선택해주세요.');
      }
    }
  };

  const handleAddNewAlbum = albumName => {
    if (albumName) {
      setItems(prevItems => {
        const newAlbumItem = {label: albumName, value: albumName};
        const updatedItems = [
          ...prevItems.filter(item => item.value !== 'add_new_album'),
          newAlbumItem,
          {label: '새 앨범 추가하기', value: 'add_new_album'},
        ];
        console.log('New album added:', newAlbumItem);
        return updatedItems;
      });
      setSelectedOption(albumName);
    }
    setPlusVisible(false);
  };

  const handleImageSelect = id => {
    console.log('Image group selected:', id);
    setSelectedGroupIds(prevSelected => {
      if (prevSelected.includes(id)) {
        return prevSelected.filter(groupId => groupId !== id);
      } else {
        return [...prevSelected, id];
      }
    });
  };

  const getImageContainerStyle = id => {
    const isSelected = selectedGroupIds.includes(id);
    return {
      ...styles.imageContainer,
      borderColor: isSelected ? '#A9BB89' : 'white',
      borderWidth: isSelected ? 5.5 : 0,
      opacity: isSelected ? 0.5 : 1,
      borderRadius: 10,
      backgroundColor: isSelected ? 'rgba(0, 0, 0, 0.5)' : 'white',
    };
  };

  const handleAlbumSelection = async (albumId, selectedImageIds) => {
    console.log('Album selection started:', {albumId, selectedImageIds});
    if (!albumId || selectedImageIds.length === 0) {
      Alert.alert('Error', '앨범 ID와 선택한 그룹을 확인해주세요.');
      return;
    }

    try {
      const requestBody = {
        updateAlbumIdRequestList: [
          {
            albumId: albumId,
            imageIds: selectedImageIds,
          },
        ],
      };
      console.log('Request body:', JSON.stringify(requestBody, null, 2));
      const data = await AlbumSave(requestBody);
      console.log('Response from AlbumSave:', data);
      Alert.alert('앨범에 선택한 그룹을 저장했습니다');
    } catch (error) {
      console.error('Failed to save images:', error);
      Alert.alert(
        'Error',
        `앨범에 이미지 저장 중 오류가 발생했습니다: ${error.message}`,
      );
    }
  };

  return (
    <View style={styles.outerContainer}>
      <ScrollView contentContainerStyle={styles.container}>
        <View style={styles.header}>
          <Image
            style={styles.step_4}
            source={require('../../assets/icon/step_4.png')}
            resizeMode="contain"
          />

          <Text style={styles.text}>
            그룹을 선택하고 원하는 앨범에 넣어보세요!
          </Text>
        </View>

        <View style={styles.selectContainer}>
          <RNPickerSelect
            onValueChange={handleValueChange}
            items={items}
            placeholder={{label: '앨범을 선택하세요', value: null}}
            style={pickerSelectStyles}
            useNativeAndroidPickerStyle={false}
          />
        </View>

        <View style={styles.gridContainer}>
          {groupedImages.map((group, index) => (
            <React.Fragment key={index}>
              {index % 2 === 0 && index !== 0 && (
                <View style={styles.separator} />
              )}
              <View style={getImageContainerStyle(index)}>
                <TouchableOpacity
                  style={styles.imageWrapper}
                  onPress={() => handleImageSelect(index)}>
                  <Image source={{uri: group[0]}} style={styles.image} />
                  {selectedGroupIds.includes(index) && (
                    <View style={styles.imageOverlay} />
                  )}
                  <Text style={styles.imageCount}>{`${group.length}`}</Text>
                </TouchableOpacity>
              </View>
            </React.Fragment>
          ))}
        </View>

        <AlbumPlus
          visible={plusVisible}
          onClose={() => setPlusVisible(false)}
          onAddAlbum={handleAddNewAlbum}
        />
      </ScrollView>
      <View style={styles.fixedFooter}>
        <TouchableOpacity onPress={handleNavigation}>
          <Image
            style={styles.done}
            source={require('../../assets/icon/done2.png')}
            resizeMode="contain"
          />
        </TouchableOpacity>
      </View>
    </View>
  );
};

충돌나는 요소가 있으면 둘 중 하나를 비슷한 기능을 할 수 있는 다른 라이브러리 혹은 컴포넌트를 사용하면 된다!

참고
https://taehoon95.tistory.com/117

0개의 댓글