Criando um component <Select/> com opção de Search e Paginação para ReactNative-NativeBase
Tags: reactnative, selectcomponen, searchreact, mobile
Published: 2024-02-16 14:00:27

Se você trabalha com React Native utilizando o NativeBase e muitas outras bibliotecas, e está precisando de um componente <select/> que lhe dê a opção de pesquisa e paginação, provavelmente está decepcionado! Pois nativamente você não tem essa opção. No entanto, criei esse componente e gostaria de compartilhar com vocês. Então, vamos lá.

Nativamente o component <Select/> do NativeBase vai ser assim:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xovjkvt7tvi3ccloe6lr.png)

Um componente <Select/> simples que pode servir para muitos casos, mas não para todos! O meu componente se comporta de forma diferente:


![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u94tga2x9eiktkn62att.png)

Podemos observar que ele conta com a opção de pesquisa e também, ao chegar perto do final da lista, ele já faz a paginação, carregando um indicador de carregamento e, posteriormente, adicionando os itens da próxima página na minha lista. Por que não trazer todos os dados sem paginação? Basicamente, porque podemos ter centenas de milhares de dados e isso causaria problemas com a memória.
Esse componente foi desenvolvido para ser utilizado em vários locais da minha aplicação, então ele não é específico para uma API ou um objeto só. Por isso, você pode utilizá-lo adaptando mínimos detalhes para que ele funcione na sua aplicação.
 

```
import React, { useEffect, useState } from 'react';
import { Box, Modal, Button, VStack, Text, View } from 'native-base';
import SearchComponent from './search-fields.utils';
import { FlatList } from 'native-base';
import { HStack } from 'native-base';
import { TouchableOpacity } from 'react-native';
import { ActivityIndicator } from 'react-native';
import Icon from 'react-native-vector-icons/FontAwesome5';
import { useTheme } from 'native-base';

interface Props {
	apiUrl: any;
	searchName: string;
	valueName: string;
	placeholder?: string;
	isRequired?: boolean;
	value?: any;
	onChange: (value: any) => void;
}

function SelectPaginate(props: Props) {
	const { colors } = useTheme();
	const [modalVisible, setModalVisible] = React.useState(false);
	const [selectValue, setSelectValue] = useState('');
	const [data, setData] = useState<any[]>([]);
	const [loading, setLoading] = useState(false);
	const [page, setPage] = useState(1);
	const [searchQuery, setSearchQuery] = useState<string>('');
	const [value, setValue] = useState();
	const perPage = 10;

	const onPress = (value: any) => {
		setSelectValue(value?.[props.valueName]);
		setModalVisible(false);
		props.onChange(value);
	};

	const handleSearch = value => {
		setSearchQuery(value);
	};

	useEffect(() => {
		if (props.value !== undefined && props.value !== null) {
			setSelectValue(props.value?.[props.valueName]);
		}
	}, [props.value]);

	useEffect(() => {
		if (data.length === 0) {
			load()
		} else {
			null;
		}
	}, [data]);

	useEffect(() => {
		setPage(1)
		setData([]);
	}, [searchQuery]);

	function areArraysEqual(arr1, arr2) {
		if (arr1.length !== arr2.length) {
			return false;
		}
	
		for (let i = 0; i < arr1.length; i++) {
			if (arr1[i] !== arr2[i]) {
				return false;
			}
		}
	
		return true;
	}
	
	async function loadApi() {
		if (loading) return;

		setLoading(true);
		try {
			const response = await axios.get(`${props.apiUrl}`, {
				params: {
					page: page - 1,
					size: perPage,
					sort: 'id,DESC',
					cacheBuster: new Date().getTime(),
				},
			});
			if (!areArraysEqual(data, response.data)) {
				setData([...data, ...response.data]);
				setPage(page+1);
			} else {
				return;
			}
		} catch (error) {
			console.log(error);
			return null;
		} finally {
			setLoading(false);
		}
	}

	async function loadApiSearch() {
		
		try {
			const response = await axios.get(`${props.apiUrl}`, {
				params: {
					page: 0,
					size: perPage,
					sort: 'id,DESC',
					[props.searchName]: searchQuery,
					cacheBuster: new Date().getTime(),
				},
			});
		
			if (!areArraysEqual(data, response.data)) {
				setData([...data, ...response.data]);
				setPage(page+1);
			} else {
				return ;
			}
		} catch (error) {
			console.log(error);
			return null;
		} finally {
			setLoading(false);
		}
	}

	async function load(){
		if (uniqueData.length < 10 && uniqueData.length >0) {
			return;
		}
			if (searchQuery === '' || searchQuery === undefined || searchQuery === null) {
				
				loadApi();
				
			} else {
				setLoading(true);
				loadApiSearch();
			}
		
	}

	function FooterList({ Load }) {
		if (!Load) return null;
		return (
			<View>
				<ActivityIndicator size={25} color="#121212" />
			</View>
		);
	}
	function ListItem({ data }) {
		return (
			<Box pl={['0', '4']} pr={['0', '5']} py="2">
				<TouchableOpacity
					onPress={() => {
						onPress(data);
					}}>
					<HStack space={[2, 3]} justifyContent="space-between">
						<VStack>
							<Text
								_dark={{
									color: 'warmGray.50',
								}}
								color="coolGray.800"
								fontSize={15}
								mt={2}>
								{data?.[props.valueName]}
							</Text>
						</VStack>
					</HStack>
				</TouchableOpacity>
			</Box>
		);
	}
	function TextComponent() {
		if (selectValue === null || selectValue === '') {
			return <Text style={{ color: 'grey' }}>{props.placeholder}</Text>;
		} else {
			return <Text style={{ color: 'white' }}>{selectValue}</Text>;
		}
	}
	const uniqueData = Array.from(new Set(data.map(item => item.id)))
    .map(id => data.find(item => item.id === id));
	return (
		<>
			<Modal isOpen={modalVisible} onClose={() => setModalVisible(false)} avoidKeyboard justifyContent="flex-end" bottom="0" size="lg">
				<Modal.Content>
					<Modal.CloseButton />
					<Modal.Header>
						<SearchComponent onSearch={handleSearch} placeholder={props.placeholder} colorText={'grey'} />
					</Modal.Header>
					<FlatList
						height="500"
						data={uniqueData}
						renderItem={({ item }) => <ListItem data={item} />}
						keyExtractor={item => item.id}
						onEndReached={load}
						onEndReachedThreshold={0.1}
						ListFooterComponent={<FooterList Load={loading} />}
						ml={2}
					/>
				</Modal.Content>
			</Modal>
			<VStack space={8} alignItems="center" >
				<Button
					onPress={() => {
						setModalVisible(!modalVisible);
					}}
					style={{ borderWidth: 1, borderColor: colors.white50, width: '100%'}} h={12} >
					<TextComponent />
					<Icon size={18} name={'chevron-down'} style={{ textAlign: 'right', marginLeft: '94%', marginTop: '-6%', color: 'gray' }} />
				</Button>
			</VStack>
		</>
	);
}

export default SelectPaginate;

```
Você também vai precisar do componente de pesquisa, que é importado:

```
import React, { useEffect, useState } from 'react';
import {  Input,  Box } from 'native-base';

function SearchComponent({ onSearch, placeholder ,colorText ='white'}) {
    const [value, setValue] = useState("");
  
    useEffect(() => {
      const timer = setTimeout(() => {
        onSearch(value);
      }, 500);
  
      return () => {
        clearTimeout(timer);
      };
    }, [value, onSearch]);
  
    const handleChange = (text) => {
      setValue(text);
    };

  return (
    <Box alignItems="center">
      <Input
      color={colorText}
      value={value}
      onChangeText={handleChange}
      placeholder={placeholder}
      w="40"
    />
    </Box>
  );
}

export default SearchComponent;

```
Lembre-se de adaptar esses componentes para a sua aplicação, como a requisição para a API e o objeto. Tente entender a interface criada para passar os valores corretos. Em caso de dúvidas, basta comentar neste post que eu ajudo você.