Adding Search Bar into Flatlist using Hook, Undefined - react-native

i try to create flatlist inside modal with search bar functionality that can filter the result based on user input in the search bar.
For the flatlist everything show up accordingly,
problem i can't filter the data,
i try using .filter from the original full data (list) but result is always undefined is not a data.
Btw data is populate from local .json file to state using useEffect.
Here is the local country.json data :
[
"Japan",
"Korea",
"Thailand",
"Indonesia" ]
Here is part of the source code :
import dataCountry from '../../assets/json/country.json';
const NameScreen = ({ navigation }) => {
// hook
const [list, setList] = useState([]);
const [modalBirthplace, setModalBirthplace] = useState(false);
const [searchText, setSearchText] = useState('');
useEffect(() => {
setList({ dataCountry });
console.log('check List : ', list);
}, [])
const renderItem = ({ item }) => (
<Item title={item.title} />
);
const ListItem = ({ title }) => (
<View>
<TouchableOpacity onPress={() => console.log("ok")}>
<Text style={styles.cityList}>{title}</Text>
</TouchableOpacity>
</View>
);
const searchFilterFunction = searchText => {
setSearchText(searchText);
console.log(searchText)
const newData = list.filter((item) => { // error trigger from this list.filter undefined is not a function
const itemData = item.toUpperCase();
const textData = searchText.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setList(newData);
};
return (
<View style={styles.container}>
<Modal
animationType="slide"
transparent={true}
visible={modalBirthplace}
onRequestClose={() => {
Alert.alert('Modal has been closed.');
}}>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text style={styles.modalText}>Choose your country location :</Text>
<TextInput
placeholder="Try japan maybe?"
onChangeText={searchText => searchFilterFunction(searchText)}
value={searchText}
/>
<FlatList
data={list.dataCountry}
renderItem={({ item }) => (
<ListItem
title={item}
/>
)}
keyExtractor={item => item}
/>
<TouchableHighlight
style={{ ...styles.openButton, backgroundColor: '#E15C72' }}
onPress={() => {
setModalBirthplace(!modalBirthplace);
}}>
<Text style={styles.textStyle}>Close Selection</Text>
</TouchableHighlight>
</View>
</View>
</Modal>
</View>
)
}
Anybody know why i can't filter the state?
Thanks a lot before

the problem is your state is an JSON object, not an array:
setList({ dataCountry });
// so list is:
{
dataCountry: [
...
]
}
so, you need to change here
const newData = list.dataCountry.filter((item) => { // here
const itemData = item.toUpperCase();
const textData = searchText.toUpperCase();
return itemData.indexOf(textData) > -1;
});
setList({dataCountry: newData}); // and here

maybe your json like this,
const list = {
dataCountry : [
'UK',
'US'
]
}
List is an object you can't use the filter with an object.
Instead of using array placeholder you can use spread operator like this,
const newData = [...list.dataCountry].filter((item) => {
const itemData = item.toUpperCase();
const textData = searchText.toUpperCase();
return itemData.indexOf(textData) > -1;
});

Related

Why my virtualized list does not re-render when the data passed as prop actualizes?

I am having a problem that I can't solve by my own. I am making an app for making lists in React Native, and in my main screen, that shows the session initiated by the user, I have to render all the lists that he had saved previously. Here is the code of my session component.
export default function Session({navigation,route}){
const {user} = useContext(myContext)
const [modalVisible, setModalVisible] = useState(false)
const [lists, setLists] = useState(route.params.lists)
let keyListCounter = 0
const handleButton = async () => {
await AsyncStorage.removeItem("token")
navigation.navigate("Login")
}
const updateList = (title,newElement) => {
axios.put(`http://${REACT_APP_BACK_URI}/api/lists/add-list-element`, {nickname: user,title,element: newElement})
.then(res => {
if (res.status == 200) {
setLists(res.data.userLists)
}
})
.catch(err => console.log(err))
}
useEffect(() => {
navigation.setOptions({
title: user,
headerTitleAlign: "left",
headerRight: () => (
<TouchableWithoutFeedback onPress={() => handleButton()}>
<Text style={styles.logoutText}>Logout</Text>
</TouchableWithoutFeedback>
)
})
},[navigation,user])
return (
<View style={styles.mainContainerView}>
<ScrollView style={styles.mainContainerScrollView}>
<View style={styles.textListContainer}>
<Text style={styles.listsText}>LISTAS ACTIVAS: </Text>
<Text style={styles.numberListsText}>{lists.length}</Text>
</View>
{lists.map(elem => <List key={keyListCounter++} list={elem} updateList={updateList}/>)}
</ScrollView>
<Pressable style={styles.newListPressable} onPressIn={() => setModalVisible(true)}>
<Text style={styles.newListText}>+</Text>
</Pressable>
<View style={styles.centeredView}>
<Modal
visible={modalVisible}
animationType="slide"
transparent={true}
>
<View style={styles.centeredView}>
<View style={styles.modalView}>
<Text>MODAL</Text>
</View>
</View>
</Modal>
</View>
</View>
)
}
My question is why after I actualize the state of "lists", whose elements are passed as props to the List component, the virtualized list that I have in the List component does not re-renderizes automaticaly.
Here I show also the code of the List component.
export default function List({list,updateList}){
const {elements, title} = list
let elementId = 0
const virtualizedList = useRef()
const [showVirtualizedList, setShowVirtualizedList] = useState("none")
const [showDownArrow, setShowDownArrow] = useState(true)
const [showUpArrow, setShowUpArrow] = useState(false)
let [newElementArray, setNewElementArray] = useState([])
let [listElements, setListElements] = useState(elements)
const getItem = (item) => ({
id: elementId++,
title: item
});
//List Pressable Events
const handlePressIn = () => {
if (showVirtualizedList == "none") setShowVirtualizedList("flex")
else setShowVirtualizedList("none")
setShowDownArrow(!showDownArrow)
setShowUpArrow(!showUpArrow)
}
//New element Pressable Events
const handleNewElement = () => {
setNewElementArray([...newElementArray,uuid.v4()])
}
//NewListItem TouchableWithoutFeedback Events
const deleteElementInput = newItemID => {
const elementsArray = newElementArray.filter(elem => elem != newItemID)
setNewElementArray(elementsArray)
}
const addListElement = (newElement,newItemID) => {
updateList(title,newElement)
deleteElementInput(newItemID)
}
useEffect(() => {
virtualizedList.current.setNativeProps({display: showVirtualizedList})
LogBox.ignoreLogs(['VirtualizedLists should never be nested']);
},[showVirtualizedList, virtualizedList])
return (
<ScrollView style={styles.mainContainer}>
<Pressable
style={styles.listElement}
onPressIn={() => handlePressIn()}
>
<View style={styles.titleContainer}>
<Text style={styles.listElementText}>{title} </Text>
<Text style={styles.listElementQuantity}>({listElements.length})</Text>
</View>
<View>
<DownArrow show={showDownArrow}/>
<UpArrow show={showUpArrow}/>
</View>
</Pressable>
<View>
<VirtualizedList
data={listElements}
initialNumToRender={10}
getItemCount={() => listElements.length}
renderItem={({item}) => <ListItem item={item}/>}
getItem={() => getItem(listElements[elementId])}
ref={virtualizedList}
/>
</View>
{newElementArray.length > 0 ? newElementArray.map(elem => {
return (
<NewListItem
key={elem}
id={elem}
newElementArray={newElementArray}
deleteElementInput={deleteElementInput}
addListElement={addListElement}
/>
)
}) : ""
}
<Pressable style={styles.newElementPressable} onPressIn={() => handleNewElement()}>
<Text style={styles.newElementText}>+</Text>
</Pressable>
</ScrollView>
)
}
UPDATE: I solve the problem using a FlatList instead of a VirtualizedList. For some reason the FlatList re-renders when the Item is updated and the VirtualizedList no. I don't know why.....

how do I update useState immediately?

I'm trying to add and remove items from my movies favlist but I am unable to render things immediately with useState. I also trying to update favoritesFilm in UseEffect but my page crashed for continuing re-render.
This is my fav component:
export default function FavouriteBox() {
const navigation = useNavigation<NavigationProps>()
const [favoritesFilm, setFavorite] = useState<Movie[]>([])
const [isLoadingFav, setIsLoadingFav] = useState(true)
useEffect(() => {
getFav()
}, [])
useEffect(() => {
console.log(favoritesFilm)
}, [favoritesFilm])
async function removeMovie() {
const removed = StorageResources.storageRemove('favmovies')
setFavorite(favoritesFilm)
}
async function getFav() {
const favoriteMovies = await StorageResources.storageGet('favmovies')
setFavorite(favoriteMovies)
setIsLoadingFav(false)
}
const renderItemFav = ({ item }: any) => (
<FavMovie name={item.name} title={item.title} poster_path={item.poster_path} id={item.id} />
)
const FavMovie = ({ title, poster_path, name, id }: any) => (
<View style={styles.wrap}>
<Image
style={styles.image}
source={{
uri: `https://image.tmdb.org/t/p/w500/${poster_path}`,
}}
/>
{title && <Text style={styles.fav}>{title}</Text>}
{!title && <Text style={styles.fav}>{name}</Text>}
<MaterialCommunityIcons onPress={() => removeMovie()} name="bookmark-minus-outline" style={styles.book} />
</View>
)
return (
<View style={styles.container}>
<Text style={styles.title}>Preferiti</Text>
{isLoadingFav && <LoaderBox />}
{!isLoadingFav && (
<FlatList
data={favoritesFilm}
keyExtractor={(item) => item.id}
renderItem={renderItemFav}
horizontal
></FlatList>
)}
</View>
)
}
In my home component I use this function to add to fav:
const addToFavorites = async (item: Movie) => {
if (favorites.includes(item)) return null
StorageResources.storageSave('favmovies', [...favorites, item])
setFavorites([...favorites, item])
}
I would like to understand why it doesn't work and why every time I want to show movies in the favmovies component I have to refresh. (I used AsyncStorage for getItem and removeItem)

Change state of RenderItem on screenLeave

Does anyone know how I can change the state of a renderItem when it leaves screen? Below I have the Flatlist with uses an Item, I want to change the state of the item once it exits the renderview.
const Item = memo(({content}) => {
const [outOfView, setOutOfView] = useState(false)
const onScroll= () => {
if (!outOfView) setOutOfView(true) //Trying to get this to work
}
return (
<View style={styles.item} onScroll={onScroll}>
<Text>{content.title}</Text>
</View>
)
})
const Slider = props => {
const flatList = useRef()
const _renderItem = ({ item, index }) => <Item content={item} />
return (
<View style={styles.container} >
{props.header ? <AppText style={styles.header} text={props.header} /> : null}
<FlatList
data={props.data}
horizontal
pagingEnabled
renderItem={_renderItem}
keyExtractor={item => item._id}
ref={flatList}
/>
</View>
)
}
YOu can do something like this
import { useIsFocused } from '#react-navigation/native';
const Item = memo(({content}) => {
const [outOfView, setOutOfView] = useState(false)
const onScroll= () => {
if (!outOfView) setOutOfView(true) //Trying to get this to work
}
const isFocused = useIsFocused();
return (
<View style={styles.item} onScroll={onScroll}>
<Text>{isFocused?content.title:"Offline"}</Text>
</View>
)
})
hope it helps. feel free for doubts

How to re-render a flatlist

I'm making a mobile app that shows a list of movies, but when I search for a movie FlatList won't update, how can I fix it?
I tried too many things but it still does not work, my objective is to update the list when the button is pressed, the API gives me the data correctly but the list does not update.
This is my code:
export const Home = () => {
let { peliculasList, loadPeliculas } = peliculasPaginated();
const [name, setName] = useState('');
const [year, setYear] = useState('');
const [buscado, setBuscado] = useState(false);
const handleClick = async () => {
const resp = await peliculasApi.get<SimplePelicula[]>(`http://www.omdbapi.com/?t=${name}&y=${year}&plot=full&apikey=d713e8aa`);
setBuscado(!buscado);
peliculasList = resp.data
}
return (
<>
<View
style={{
alignItems: 'center',
height: 760
}}
>
<Text style={{
...style.title,
...style.globalMargin,
top: 0,
marginBottom: 0
}}>Movies</Text>
<TextInput
placeholder='Movie Name'
style={styles.input}
onChangeText={(val) => setName(val)}
/>
<TextInput
placeholder='Year'
style={styles.inputMovie}
onChangeText={(val) => setYear(val)}
/>
<TouchableOpacity onPress={() => handleClick()}>
<ButtonSr></ButtonSr>
</TouchableOpacity>
<FlatList
data={ peliculasList }
keyExtractor={ (pelicula) => pelicula.imdbID }
showsVerticalScrollIndicator={ false }
extraData={ buscado }
renderItem={({ item }) => ( <PeliculasCard pelicula={item} ></PeliculasCard> )}
/>
</View>
</>
)
}
Try to save your resp.data within the state and declare that state in your Flatlist's data prop may solve the issue.
Try this out just change the 'MOVIENAME' to a response from the api such as the movie name and refrence it as the item.API object of your choice
import React, { useState } from 'react'
import { View, Text, TextInput, FlatList, Button } from 'react-native'
export default function App() {
const [FetchedData, setFetchedData] = useState([])
const [SearchTerm, setSearchTerm] = useState('')
const [Data, setData] = useState(FetchedData)
const [ArrayHolder, setArrayHolder] = useState(FetchedData)
const FetchMovies = () => {
fetch('url')
.then(res => res.json())
.then(res => setFetchedData(res))
}
FetchMovies()
function dataSearch(text) {
const newData = ArrayHolder.filter(item => {
const itemData = item.MOVIENAME.toUpperCase()
const textData = text.toUpperCase()
return itemData.indexOf(textData) > -1
})
setData(newData)
}
return (
<View>
<Button title='Press' onPress={() => dataSearch(SearchTerm)} />
<TextInput
onChangeText={(text) => setSearchTerm(text)}
placeholder="Search Here"
/>
<FlatList
data={Data}
renderItem={({ item }) => <Text>{item.MOVIENAME}</Text>}
/>
</View>
)
}

React Native Hooks - Trying to refactor from class Component to Function Component to use hooks

I'm new in react native and I'm tryng to refactor this code below, and I think I'm doing some thing wrong here "setSentences(item)", because it's not updating.
Do you know what i'm doing wrong here?
this.state = {
sentences: [],
};
{this.state.sentences.map((item) => {
return(
<TouchableOpacity
onPress={() => {
item.selected = false;
this.setState(item);
}}>
</TouchableOpacity>
)}}
Refactored:
const [sentences, setSentences] = useState([]);
{sentences.map((item) => {
return (
<TouchableOpacity
onPress={() => {
item.selected = false;
setSentences(item);
}}>
</TouchableOpacity>
sentences is an array but when you show list array setSentences update array sentences. Something wrong here
const [sentences, setSentences] = useState([]);
{sentences.map((item) => {
return (
<TouchableOpacity
onPress={() => {
item.selected = false;
setSentences(item);
}}>
</TouchableOpacity>
I think you can use useRef to store array sentences and another state to set item
const sentences = useRef([]);
const [itemSentence, setItemSentence] = useState('');
<Text>{itemSentence}</Text>
{sentences.current.map((item) => {
return (
<TouchableOpacity
onPress={() => {
item.selected = false;
setItemSentence(item);
}}>
</TouchableOpacity>
For clear solution you have to create one method like this :
const onItemPress = (itemIndex) => {
const temp = [].concat(sentences);
temp[itemIndex].selected = false;
setSentences(temp);
}
Now, call this method on TouchableOpacity onPress method and pass current index to this method to modify that perticular item state as below :
{sentences.map((item, itemIndex) => {
return (
<TouchableOpacity
onPress={() => onItemPress(itemIndex)}>
</TouchableOpacity>
)
}}
And note, your state should be :
const [sentences, setSentences] = useState([]);
i think you need clone array sentences and get index inside
const [sentences, setSentences] = useState([]);
sentences.map((item:any,index:number) => {
return (
<TouchableOpacity
onPress={() => {
const cloneSentences =clone(sentences);
cloneSentences[index].selected = false;
setSentences(cloneSentences);
}}>
</TouchableOpacity>)
})