Query Doesn't Re-run after navigation - javascript

I have a screen where I am using a query like this:
export const AllFriends: React.FunctionComponent = () => {
const navigation = useNavigation();
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo')
},
});
Every time I visit this page from the home-page, I think the query re-runs as I always see the hellolog. Similarly, from this page, I visit another screen like this:
<Text
onPress={() => navigation.navigate('PendingRequests')}>
Pending Requests (
{data
? data.me.pendingUserRelationsRequestsReceived.totalCount
: ''}
)
</Text>
Every time I visit this screen, I see the hellooo from pending again. This screen looks like this:
export const ReceivedPendingRequests: React.FunctionComponent = () => {
const navigation = useNavigation();
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo from pending')
},
});
return (
<SafeAreaView style={styles.safeView}>
<Container style={styles.container}>
<Text
style={styles.backText}
onPress={() => navigation.navigate('AllFriends')}>
Zurück
</Text>
</Container>
</SafeAreaView>
);
};
Now the problem is that when I navigate back to AllFriends, the query should re-run and I should see the hello log again just like I see when I come from the Homepage to AllFriends. But I don't.
However, if I come back from AllFriends to PendingRequests, I see the log hellooo from pending again.
Edit:
useFocusEffect(()=>{
getMyProfile()
},[]);
const getMyProfile = () => {
const { data, error } = useGetMyProfileQuery({
onCompleted: () => {
console.log('hellooo')
},
//fetchPolicy: 'network-only',
});
}

You have to call refetch it will re-run the query for you. You can pass it to other screens aswell.
You can get it like this:
const {loading, data, refetch} = useQuery(Query, {})
Now in you useFocuseEffect just call this:
useFocusEffect(()=>{
React.useCallback(() => {
refetch();
}, [])
},[]);

when using navigation.navigate the screen doesn't "unmount", so it will not be reinitialized as fresh from the start as the screen does when you're using navigation.replace
Try using navigation.replace instead of navigation.navigate. I hope it helps, if not, let me know.

Related

react native state is updated but functions still using the initial state set at the time of mounting

In my react native functional component, the state gets updated but when I want to use this state inside a function (for e.g, to send data to API), it uses the initial state only.
imports...
const Component = ({ navigation }) => {
const [ids, setIds] = useState([1,2]);
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => <HeaderRight
onPress={() =>
console.log(ids); // this logs initial state, i.e, [1,2]
updateIdsToServerViaAPI(); // and therefore I'm unable to update ids using this method
}
/>
});
}, [navigation]);
const updateIdsToServerViaAPI = async () => {} // function that takes updated ids from state.
const onPress = async () => {
const newIds = [...ids, 3, 4];
setIds(newIds);
}
const onPressInsideComp = () => {
console.log(ids);
// here updated ids gets logged.
}
return (
<View>
<Button onPress={onPress} />
{ids.map(id => (
<Text key={id}>{id}</Text> {\* Here you will see 4 texts after pressing button, that means state gets updated*\}
)}
<Button onPress={onPressInsideComp} />
</View>
);
}
Seems like this issue happens only when functions are called inside useLayoutEffect or useEffect but when I call onPressInsideComp from the button inside the component, it logs properly!
I am badly stuck on this weird issue !!
You have only provided the navigation prop in the dependency array of your useLayoutEffect wrapper, thus the function is not recreated if the ids state changes.
You might want to create a different function, wrapped inside a useCallback which gets the ids state as a dependency and provide this function in your useLayoutEffect.
const doSomething = useCallback(() => {
console.log(ids);
updateIdsToServerViaAPI();
}, [ids])
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => <HeaderRight
onPress={() =>
doSomething(ids)
}
/>
});
}, [navigation, ids, doSomething]);
In your code, the console.log(ids) is resolved at the moment of the function definition, and not at execution time, so it takes the reference you get in the definition const [ids, setIds} = useState([1,2]).
Maybe just try to get your ids with a function of state instead of using a variable that has been defined before:
const [ids, setIds] = useState([1,2]);
const get_ids = () => this.state.ids;
useLayoutEffect(() => {
navigation.setOptions({
headerRight: () => <HeaderRight
onPress={() =>
console.log(get_ids());
updateIdsToServerViaAPI();
}
/>
});
}, [navigation]);

why does useCallback return an empty array

in react native app,
i'm trying to get data from async function which will bring me back Promise<AlarmType[] | undefined>
Q1. so, in getAlarms.then() function, the undefined case is filtered and an empty array is printed in my console.
and after saving code in vscode, the console prints an array with proper data
Q2.the reason why i use useLayoutEffect and useEffect separately is
i just wanna separate the data fetching code from the react navigation header setOption code
but i'm not sure if it is a good practice
Is there any better ways to do this?
edit: i’m using react-native-simple-alarm
const [alarms, setAlarms] = useState<AlarmType[]>([]);
const fetchData = useCallback(() => {
getAlarms().then(response => {
if (response) setAlarms(response);
else console.log('undefined | empty array returned');
});
}, []);
useLayoutEffect(() => {
fetchData();
const willFocusSubscription = navigation.addListener('focus', () => {
fetchData();
});
console.log(alarms) // here, this function is called twice, and return empty array
return willFocusSubscription;
}, []);
useEffect(() => {
navigation.setOptions({
headerLeft: () => <Icon name="trash-can-outline" size={30}
onPress={() => {
deleteAllAlarms();
fetchData();
}}/>,
headerTitle: 'Alarm',
headerRight: () =><Icon name="plus" size={30} onPress={() => navigation.navigate('ModalStackView')}/>,
});
}, []);
in getAlarms.ts
export const getAlarms = async () => {
try {
return await RNGetAlarms();
} catch (error) {
console.log('setting call error' + error);
}
};
The useLayoutEffect is called before the render cycle of React which means before rendering the JSX content in your code this hook is being called.
So, If there is any requirement before JSX render like change header name, show header left or right buttons, etc.
and the useEffect is called after the initial render cycle is completed. when the JSX code is done with the rendering UI part.
So, I think your code should look like below:
const [alarms, setAlarms] = useState<AlarmType[]>([]);
const fetchData = useCallback(() => {
getAlarms().then(response => {
if (response) setAlarms(response);
else console.log('undefined | empty array returned');
});
}, []);
useEffect(() => {
const willFocusSubscription = navigation.addListener('focus', () => {
fetchData();
});
return willFocusSubscription;
}, [fetchData, navigation]);
useLayoutEffect(() => {
navigation.setOptions({
headerLeft: () => <Icon name="trash-can-outline" size={30}
onPress={() => {
deleteAllAlarms();
fetchData();
}}/>,
headerTitle: 'Alarm',
headerRight: () =><Icon name="plus" size={30} onPress={() => navigation.navigate('ModalStackView')}/>,
});
}, [deleteAllAlarms, fetchData, navigation]);

How to make a Modal Toggle when using Route and Navigation?

I have four pages, "ReadyScreen" "RunningScreen" "ListScreen" and "CheckScreen"
To start a run, the user navigates from the "ReadyScreen" to the "ListScreen" to the "CheckScreen" and lastly to the "RunningScreen"
ReadyScreen -> ListScreen -> CheckScreen -> RunningScreen
At the end of the user's run, they are navigated back to the "ReadyScreen" from the "RunningScreen"
I want a Modal to toggle with their running info, when the user navigates from the RunningScreen to the ReadyScreen. I have been told I can do this using a route, but I have been having trouble properly setting it up. Here is what I have so far:
function RunningScreen({navigation, route}){
const checkFinish = () => {
onPress: () => navigation.navigate('ReadyScreen, {
didComeFromRunningScreen: true
})
}
...
useFocusEffect(
React.useCallback(()=>{
....
if(didComeFromRunningScreen){
toggleModal()
}
}, [])
}
I am also stuck on how to toggle this in the ReadyScreen
If you are going back to the previous screen you cant pass params like that. or if you forcefully push the previous screen to open again it will create a new stack. You have to pass a callback function from the ready screen to the running screen and in the running screen when your check the finish button press you will call your callback function.
Here is the code example:
ReadyScreen
const ReadyScreen = ({ navigation }) => {
const toggleModal = () => {
// Your modal method to open modal
};
// callback func
const openModalCallBack = () => {
toggleModal();
};
const gotoRunningScreen = () => {
navigation.navigate("RunningScreen", { openModalCB: openModalCallBack }); // passing callback func
};
return (
<View>
<Button onPress={gotoRunningScreen} />
</View>
);
};
export default ReadyScreen;
RunningScreen
const RunningScreen = ({ navigation, route }) => {
const checkFinish = () => {
const { openModalCB } = route?.params;
openModalCB(); // Here you calling the callback func
navigation.goBack(); // or pop()
};
return (
<View>
<Button onPress={checkFinish} />
</View>
);
};
export default RunningScreen;

How to pass data back to previous screen in react native navigation v5?

I just updated to react native navigation version 5. Now I am trying to send data back to previous screen on goBack() call.
I push next view with
const onSelectCountry = item => {
console.log(item);
};
navigation.navigate('SelectionScreen', {
onSelect: onSelectCountry});
And making move back after selecting item from FlatList with call:
function onSelectedItem(item) {
route.params.onSelect(item);
navigation.goBack();
}
But by sending function over with params I get a warning: Non-serializable valuse were found in the navigation state...
Can someone please tell me correct way to do this.
heres is an implementaion
scereen A
const Screen1 = ({navigation, route}) => {
const [item, setItem] = useState(null);
useEffect(() => {
navigation.addListener('focus', () => {
console.log(route.params)
})
}, [])
const onPress = () => {
navigation.navigate('Screen2', {onReturn: (item) => {
setItem(item)
}})
}
return (
// Components
)
}
Screen2:
const Screen2 = ({navigation, route}) => {
useEffect(() => {
navigation.addListener('focus', () => {
console.log(route.params)
})
}, [])
// back Press
const onPress = () => {
route.params.onReturn(item);
navigation.goBack()
}
return (
// Components
)
}
navigation send data to screens.
onPress={() => {
// Pass params back to home screen
navigation.navigate('Home', { post: postText });
follow official documentation React Native
I visited this post because I wanted to use the same common component in 2 stacks. How to know how to go back and pass params?
I solved it by passing first a parameter to go there, which will identify where the component is accessed from.
Then, instead of using goBack(), which doesn't accept parameters, I navigate to the previous route and pass the parameter.
//in Stack1
navigation.navigate('commonComponent', isStack1: true)
//in Stack2
navigation.navigate('commonComponent', isStack1: false)
//in CommonComponent, instead of goback(), use navivation.navigate
function CommonComponent({ navigation, route }) {
const {isStack1} = route.params
const customRoute = isStack1 ? 'routeNameInStack1' : 'routeNameInStack2'
return (
<Button title="Go back" onPress={() => navigation.navigate('customRoute', {myParams: 'blabla...') />
)
}

Lazy load Flatlist Firebase Realtime Database in React Native

I want to make the FlatList to Load more when scrolling to end also when I go back to top I need the items ready
I tried to use startAfter() method but it shows like it is not working with real time only with Firestore so the query not retrieve data when scrolling to bottom also when I use startAt() with the last key it shows error said: you should use a unique key.
see the code below :
componentDidMount = () => {
this.retrieveData();
}
retrieveData = async () => {
try {
let services = await firebase.database().ref('services').orderByKey().limitToLast(5)
services.on('value', snapshot => {
let arrayOfKeys = Object.values(snapshot.val())
referenceToOldestKey = arrayOfKeys[arrayOfKeys.length - 1].serviceKey;
this.setState({
dataSource: arrayOfKeys,
lastVisible: referenceToOldestKey,
});
});
}
retrieveMore = async () => {
try {
this.setState({
refreshing: true,
});
let services = await firebase.database().ref('services').orderByKey().endAt(this.state.lastVisible).limitToLast(6)
services.on('value', snapshot => {
let arrayOfKeys = Object.values(snapshot.val())
.sort()
.reverse()
.slice(1);
referenceToOldestKey = arrayOfKeys[arrayOfKeys.length - 1].serviceKey;
this.setState({
dataSource: arrayOfKeys,
lastVisible: referenceToOldestKey,
refreshing: false,
});
});
}
render() {
return (
<View style={styles.container}>
<FlatList
data={this.state.dataSource}
renderItem={({ item }) => (
<View style={styles.itemContainer}>
<Text>{item.serviceName}</Text>
</View>
)}
keyExtractor={(item) => item.serviceKey.toString()}
ListFooterComponent={this.renderFooter}
onEndReached={this.retrieveMore}
onEndReachedThreshold={0}
refreshing={this.state.refreshing}
/>
)
}

Categories

Resources