I am new to React Native and Apollo-Client. In my Screen there are three tabs and individual tabs is calling its own data. But due to useQuery UI is completely freezing and giving a very bad experience.
Is there any other way that I can call the useQuery in async manner or in any background task?
EDIT
function PlayerList(props) {
const [loading , setloading] = useState(true)
const [data, setData] = useState()
const [loadData, { tempLoading, tempError, tempData }] = useLazyQuery(query);
async function getData(){
loadData()
}
useEffect(() => {
if (tempData == undefined) {
getData()
}
})
if(tempLoading){
return
<View >....</View>
}
if(tempData) {
return ( <View> ... </View>) }
}
Please find the above code for better understanding.
Apollo Client uses fetch which is always asynchronous. The issue is that you are creating an infinite loop.
The object returned by the useLazyQuery hook you are destructuring does not have properties named tempLoading, tempError or tempData. See here for how to correctly rename variables when using destructuring syntax.
Because tempData is always undefined, your useEffect hook's callback will be called on every render.
Calling loadData on every render triggers another render.
By using useLazyQuery, you are unnecessarily complicating your code -- you should be using useQuery instead.
const { data, loading } = useQuery(query)
if (loading) {
return <View>....</View>
}
return <View>...</View>
Related
hi everyone i'm just learning react native and i have a question. How do I wait for an asynchronous method to finish and then pass the result to another screen?
My code is:
export default class AAA extends Component {
constructor(props){
super(props);
this.state={
comments: [],
datetime: []
}
}
//getPost is a network call which gets and store the result in the state of the class
async getPost(){
const utils=new Utils();
const responseJson = await utils.getPost("ok","yes")
const comment = (responseJson?.posts ?? []).map((data) => data.comment)
this.setState({comments:comment})
console.log("now i change state with new value")
}
componentDidMount(){
this.getPost()
}
render(){
return(
<NextScreen
comment={this.state.comments}
/>
)
}
}
I want the getPost () method to finish and then go to the other screen, passing the result of the getPost () method. I tried to use the componentWillMount method as well but it is deprecated. How can I do?
You can use conditional rendering in this case as follows:
render() {
if (this.state.comments.length === 0) {
return null;
}
return (
<NextScreen
comment={this.state.comments}
/>
)
}
The initial state comments contains an empty array, thus it is never undefined. However, its length is equal to zero until the async call returns with its result.
You can use react-router params
if you are fetching data in the splash screen, you want to pass it to the next page.
in then() or after await expression
you can learn about this in React router, pass data when navigating programmatically?
i have a useEffect function where a redux action is called and data is written to prop. My Problem is that useEffect loop many times and flooded the server with requests.
const { loescherData, navigation } = props;
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
setData(props.loescherData);
}
});
}, [loescherData]);
if i leave it blank the rendering is finished before receiving data and the content would not updated.
is there another way to work with this function?
loescherData won't be available right after calling your redux-action fetchLoescherDetails ... and changing component by setData will cause an infinite rendering cause your current useEffect has a dependency on loescherData
So I'd suggest you exec your redux-action onComponentDidMount by passing an empty-deps [] to your effect ... and then consume the output of you action in a different effect
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
// setData(props.loescherData);
}
});
}, []);
useEffect(() => {
if (loescherData) {
// do some with loescherData like setState
}
}, [loescherData]);
I have a react-native functional component that uses UseEffect to dispatch an action to the Redux store to update a field. After the field is updated, I would like the component to use the data to decide whether to show the data or navigate away.
const myScreen = props => {
const fieldFromStore = useSelector(state=> state.mystore.myfield)
useEffect(
()=>{
dispatch(actionThatWillUpdateMyField)
if (fieldFromStore == Something){
props.navigation.navigate('NextPage')
} //else just render the rest of the screen
}, [])
return (
<View>
<Text> {fieldfromStore}</Text>
<View>)
The problem is the fieldFromStore in useEffect will always be null under effect as during that render of useEffect the store has not been updated yet.
Am I violating some sort of best practice here? How can I dispatch an action to update Store and then use that data to then determine how the page is rendered?
Thank you very much for the help.
Use a 2nd useEffect() block to handle the field change. The 2nd block should have fieldFromStore as a dependancy, so it will react to changes in the field:
const myScreen = props => {
const fieldFromStore = useSelector(state => state.mystore.myfield)
useEffect(() => {
dispatch(actionThatWillUpdateMyField)
}, [])
useEffect(() => {
if (fieldFromStore === Something) {
props.navigation.navigate('NextPage')
} //else just render the rest of the screen
}, [fieldFromStore, props.navigation])
// ...
}
You can use the dependency array of useEffect to control which selectors will cause your function to run again. So, instead of the empty array at the tail of your useEffect, use [ fieldFromStore ]. Full function below for clarity's sake
useEffect(()=> {
dispatch(actionThatWillUpdateMyField)
if (fieldFromStore == Something) {
props.navigation.navigate('NextPage')
} //else just render the rest of the screen
}, [ fieldFromStore ]);
The 'best practices' this might violate is that it will dispatch your action again when the selector changes. One way around this would be to dispatch the action before you navigate to the component you're on, and then a re-render here would be cleaner.
As the docs https://reactnavigation.org/docs/en/next/use-focus-effect.html,
"Sometimes we want to run side-effects when a screen is focused. A side effect may involve things like adding an event listener, fetching data, updating document title, etc."
I'm trying to use useFocusEffect to fetch data everytime that the user go to that page.
on my component I have a function which dispatch an action with redux to fetch the data:
const fetchData = ()=>{
dispatch(companyJobsFetch(userDetails.companyId));
};
Actually I'm using useEffect hook to call fetchData(), but I'd like to fetch data everytime that the user go to that page and not only when rendered the first time.
It's not clear from the documentation how to use useFocusEffect and I'm not having success on how to do it.
Any help?
The docs show you how to do it. You need to replace API.subscribe with your own thing:
useFocusEffect(
React.useCallback(() => {
dispatch(companyJobsFetch(userDetails.companyId));
}, [dispatch, companyJobsFetch, userDetails.companyId])
);
For version react navigation 4.x, you can use addEvent listener
useEffect(() => {
if (navigation.isFocused()) {
resetReviews(); // replace with your function
}
}, [navigation.isFocused()]);
OR
useEffect(() => {
const focusListener = navigation.addListener('didFocus', () => {
// The screen is focused
// Call any action
_getBusiness({id: business?.id}); // replace with your function
});
return () => {
// clean up event listener
focusListener.remove();
};
}, []);
For later version 5.x, you can use hooks to achieve this
import { useIsFocused } from '#react-navigation/native';
// ...
function Profile() {
const isFocused = useIsFocused();
return <Text>{isFocused ? 'focused' : 'unfocused'}</Text>;
}
I'm using React Native's Navigator. Is there anyway to refresh the component so when I pop back to it, it'll make a new API call and grab the updated data to display in the component. I found a few similar questions, but no good answer...
Adding Api Call in callBack using a subscription. sovles the issue
componentDidMount() {
this.props.fetchData();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
}
You can send a callback function to nextscene from previous one as a prop.
this.props.navigator.push({
name: *nextscene*,
passProps: {
text: response,
callBack: this.callback
});
async callback(){
await ....//make new api request grab the udpated data
}
Then in your nextscene you call callback method and then pop. You can also send parameters
this.props.callBack()
this.props.navigator.pop()
When pop () to refresh before a page is not a good idea
You can try DeviceEventEmitter object
previous page DeviceEventEmitter.addListener('xxx', callback) in componentDidMount
current page DeviceEventEmitter.emit('xxx', anythingInCallback...) before pop()
ps:previous pageDeviceEventEmitter.removeAllListeners('xxx') in componentWillUnmount
I doubt you're still looking for an answer to this, but holy crap has this kept me up tonight. I'm very new to React Native, but I finally had some success.
The React Navigation API docs have a section for adding event listeners! Check it out! I shared some of my own code below too.
This is an example event handler in a Component that is the top screen of the StackNavigator stack. It grabs the current state and saves to the backend using an API call. After completion, StackNavigator's pop is called.
handleSubmit = () => {
const { value, otherValue } = this.state
addThingToDatabase({ value, otherValue })
.then(() => this.props.navigation.pop())
}
Now over to the other Component which is the screen "underneath" in the StackNavigator stack. This is screen being shown after the "pop". Here's what I used to have in ComponentDidMount.
componentDidMount() {
const { index } = this.props.navigation.state.params
getAllThingsFromDatabase({ index })
.then(({ arrayOfThings }) => this.setState({
index,
arrayOfThings
}))
}
But the Component wouldn't update with the new thing, until addListener! Now I have pretty much the same code except it's in the constructor. I figured I only need to run it one time, and I need to store it too.
constructor(props, context) {
super(props, context)
this.state = {
index: null,
arrayOfThings: []
}
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
(payload) => {
const { index } = payload.state.params
getAllThingsFromDatabase({ index })
.then(({ arrayOfThings }) => this.setState({
index,
arrayOfThings
}))
}
)
}
Note that the docs also mention unsubscribing the event listener using the .remove() function. I put that in ComponentWillUnmount().
componentWillUnmount() {
this.willFocusSubscription.remove()
}
There are four different events to subscribe to. I went with willFocus thinking it'll update before the screen is seen.
You should save the state of the page and emit an action in componentDidMount since it is invoked immediately after a component is mounted.
References:
https://facebook.github.io/react/docs/react-component.html
https://github.com/ReactTraining/react-router
ADDED
Since your component has been already mounted you should listen ComonentWillReceiveProps instead.
The simple way is to use react native navigation resetTo function. It will replace the top item and pop to it.
If we do like this componentWillReceiveProps will call. So we can provide the API calls in that function and make it simple.
for more details https://facebook.github.io/react-native/docs/navigatorios.html#resetto