How do I get setState to update a value immediately? - react-native

I have to press the buttons twice to update the filter value for how I want to display the movies in my app. This is my code:
const HomeScreen = () => {
const navigation = useNavigation();
const [movies, setMovies] = useState({});
useEffect(() => {
getMovies();
},[])
useEffect(() => {
getMoviesFiltered(filter);
},[filter])
const [filter, setFilter] = useState('name');
const getMovies = async (filter) =>{
const querySnapshot = await getDocs(query(collection(db, "movies"), orderBy(filter)));
setMovies(querySnapshot.docs);
}
return (
<View>
<View>
<TouchableOpacity onPress = {() => {setFilter('name'); getMovies(filter)}}>
<Text>Title</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('runtime'); getMovies(filter)}}>
<Text>Runtime</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('year'); getMovies(filter)}}>
<Text>Year</Text>
</TouchableOpacity>
</View>
</View>
<FlatList
data = {movies}
renderItem = {({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('Modal', item.data())}>
<Image source={{uri: item.data().pic}}/>
</TouchableOpacity>
)
}
/>
</View>
)
}
export default HomeScreen
I know that setState is asynchronous and that that is the reason it happens, but I'm kind of stuck on not knowing how to change it properly, so I'd appreaciate the help. Thank you.

useEffect will run when the values within dependency array change.
So, you don't need to add function getMovies after you changed your filter. Just simply move that to useEffect.
const HomeScreen = () => {
const navigation = useNavigation();
const [movies, setMovies] = useState({});
const [filter, setFilter] = useState('name');
useEffect(() => {
//when page is initialize, run this
getMovies(filter);
},[])
useEffect(() => {
//when filter is changed, use latest value to run this
getMovies(filter);
},[filter]);
const getMovies = async (filter) =>{
const querySnapshot = await getDocs(query(collection(db, "movies"), orderBy(filter)));
setMovies(querySnapshot.docs);
}
return (
<View>
<View>
<TouchableOpacity onPress = {() => {setFilter('name');}}>
<Text>Title</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('runtime');}}>
<Text>Runtime</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('year');}}>
<Text>Year</Text>
</TouchableOpacity>
</View>
<FlatList
data = {movies}
renderItem = {({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('Modal', item.data())}>
<Image source={{uri: item.data().pic}}/>
</TouchableOpacity>
)
}
/>
</View>
)
}
export default HomeScreen

Related

How do I pass a string to React Native function?

I have three buttons which should pass a string value to a function that sorts the movies I have in my app based on the word (it starts a query).
const HomeScreen = () => {
const navigation = useNavigation();
const [movies, setMovies] = useState({});
useEffect(() => {
getMovies();
},[])
useEffect(() => {
getMoviesFiltered();
},[])
const [filter, setFilter] = useState('');
const getMoviesFiltered = async (filter) =>{
const querySnapshot = await getDocs(query(collection(db, "movies"), orderBy(filter)));
setMovies(querySnapshot.docs);
}
const getMovies = async () =>{
const querySnapshot = await getDocs(query(collection(db, "movies"), orderBy('name')));
setMovies(querySnapshot.docs);
}
return (
<View>
<View>
<TouchableOpacity onPress = {() => {setFilter('name')}}>
<Text>Title</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('runtime')}}>
<Text>Runtime</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('year')}}>
<Text>Year</Text>
</TouchableOpacity>
</View>
</View>
<FlatList
data = {movies}
renderItem = {({item}) => (
<TouchableOpacity onPress={() => navigation.navigate('Modal', item.data())}>
<Image source={{uri: item.data().pic}}/>
</TouchableOpacity>
)
}
/>
</View>
)
}
export default HomeScreen
I can see that it updates the filter value when I press the button twice (I've read that setState is an asynchronous operation, so I get why that happens), but I'm not sure how to properly pass that string into the getMovies1 function.
Could someone please help me out?
It looks like you are trying to pass the filter state value to the getMoviesFiltered function when it is called. However, you are not passing any arguments to the getMoviesFiltered function when you call it in the TouchableOpacity onPress event handlers.
To fix this, you can pass the filter state value as an argument to the getMoviesFiltered function in the onPress event handlers like this:
<TouchableOpacity onPress = {() => {setFilter('name');
getMoviesFiltered(filter)}}>
<Text>Title</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('runtime');
getMoviesFiltered(filter)}}>
<Text>Runtime</Text>
</TouchableOpacity>
<TouchableOpacity onPress = {() => {setFilter('year');
getMoviesFiltered(filter)}}>
<Text>Year</Text>
</TouchableOpacity>
You may also want to consider moving the useEffect hook that calls getMoviesFiltered to depend on the filter state value. This will ensure that getMoviesFiltered is only called when the filter state value changes. You can do this like this:
useEffect(() => {
getMoviesFiltered(filter);
},[filter])

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.....

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 access one functional component's state from another functional component in react-native?

I want to access ModalView's state from MarkerView component. Actually i want to see ModalView when i click Get info. button which is in MarkerView. I want to set { setVisiblity(true)} from MarkerView component. How can i do it?
ModalView.js
const ModalView = () => {
const [visiblity, setVisiblity] = useState(false);
return (
<Modal transparent={false} visible={visiblity} >
<TouchableOpacity onPress={() => { setVisiblity(false)}}>
<Text> Submit </Text>
</TouchableOpacity>
</Modal>
)
}
MarkerView.js
const MarkerView = () => {
return (
// i want to set visiblity true from here
<View>
<TouchableOpacity onPress={() => { setVisiblity(true) }}>
<Text>Get info.</Text>
</TouchableOpacity>
</View>
)
}
App.js
import ModalVIew from './components/ModalView';
import Marker from './components/MarkerView';
const App = () => {
return (
<View>
<Marker/>
<ModalVIew/>
</View>
)
}
export default App;
you can use state management like contextAPI or redux, or you can put your state on your higher order component but this will result in some prop drilling.
App.js
const App = () => {
const [visiblity, setVisiblity] = useState(false);
return (
<View>
<Marker visiblity={visiblity} onChangeVisiblity={(val) => setVisiblity(val)}/>
<ModalVIew visiblity={visiblity} onChangeVisiblity={(val) => setVisiblity(val)}/>
</View>
)
}
MarkerView.js
const MarkerView = ({visiblity, onChangeVisiblity: changeVisiblity}) => {
return (
<View>
<TouchableOpacity onPress={() => changeVisiblity(true)}>
<Text>Get info.</Text>
</TouchableOpacity>
</View>
)
}
ModalView.js
const ModalView = ({visiblity, onChangeVisiblity:changeVisiblity}) => {
const [visiblity, setVisiblity] = useState(false);
return (
<Modal transparent={false} visible={visiblity} >
<TouchableOpacity onPress={() => changeVisiblity(false)}>
<Text> Submit </Text>
</TouchableOpacity>
</Modal>
)
}

React Native Function Component ASYNC / AWAIT Problem

sorry for bad English.
My function component not waiting API Function, I'm write async and await and not working again..
"Object are not valid as a React child" error screen...
Please help me :'/
const NormalCarousel = async (props) => {
const navigation = useNavigation();
const [ResponseData, setResponseData] = useState('');
const ComponentAPI = props.api;
const API = await axios.get(ComponentAPI).catch((error) => {alert(error)});
await setResponseData(API);
return(
<ScrollView horizontal={true} showsHorizontalScrollIndicator={false}>
{
ResponseData.map(item => (
<TouchableOpacity style={styles.CarouselTouchable} onPress={() => navigation.navigate("Ürün", {id: item.item_id})}>
<Image
style={styles.CarouselImage}
source={{uri: item?.item_avatar}}
/>
<View style={styles.CarouselView}>
<Text style={styles.CarouselTitle}>{item?.item_name}</Text>
<Text style={styles.CarouselSubtitle}>{item?.item_stock_code}</Text>
</View>
</TouchableOpacity>
))
}
</ScrollView>
)
}
The error is happening because the parent component trying to render this component is actually rendering a Promise which resolves into a component. That's not possible.
You should instead call the function to load the data once on component mount (useEffect). You'll also need to replace useState('') with useState([]) since you're trying to map over this data when rendering.
const NormalCarousel = (props) => {
const { api } = props;
const navigation = useNavigation();
const [responseData, setResponseData] = useState([]);
useEffect(() => {
getAPI();
}, []);
async function getAPI() {
const API = await axios.get(api).catch((error) => alert(error));
setResponseData(API);
}
return(
<ScrollView horizontal showsHorizontalScrollIndicator={false}>
{
responseData.map(item => (
<TouchableOpacity style={styles.CarouselTouchable} onPress={() => navigation.navigate("Ürün", {id: item.item_id})}>
<Image
style={styles.CarouselImage}
source={{uri: item.item_avatar}}
/>
<View style={styles.CarouselView}>
<Text style={styles.CarouselTitle}>{item.item_name}</Text>
<Text style={styles.CarouselSubtitle}>{item.item_stock_code}</Text>
</View>
</TouchableOpacity>
))
}
</ScrollView>
)
}