why does useCallback return an empty array - javascript

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]);

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]);

Mapped data is not shown on screen in React Native

I am getting a response from an API:
{
"data": {
// other stuff
"time_breakup": {
"break_timings": [
{
"break_in_time": "2021-11-18T05:32:35.747Z",
"break_out_time": "2021-11-18T05:32:47.871Z"
},
{
"break_in_time": "2021-11-18T06:21:35.740Z",
"break_out_time": "2021-11-18T06:21:39.909Z"
}
],
},
},
"success": true
}
I am using the below function to get this response:
const [shift, setShift]: any = useState();
const getShiftDetails = useCallback(() => {
ApiFunctions.get('shift/' + ID)
.then(async resp => {
if (resp) {
setShift(resp.data); // saving the response in state
// some work
} else {
Alert.alert('Error', resp);
}
})
.catch((err: any) => {
console.log(err);
});
}, []);
useEffect(() => {
getShiftDetails();
}, [getShiftDetails, ID]);
So, I have saved the response in a state shift. Now I want to map this state to display the time on screen:
<View>
{shift.time_breakup.break_timings.map((item: any, index: any) => {
console.log(item.break_in_time),
<>
<View>
<Text>{item.break_in_time}</Text>
<Text>{item.break_out_time}</Text>
</View>
</>;
})}
</View>
However, I am not able to see <Text>{item.break_in_time}</Text> on screen; and also, in the console, I am getting an infinite loop of time:
console.log:
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
2021-11-18T06:21:35.740Z
2021-11-18T05:32:35.747Z
...
I don't know what I am doing wrong.
Try adding the ID inside the getShiftDetails useCallback dependencies array.
const getShiftDetails = useCallback(() => {...}, [ID]) I believe this is what is causing the infinite loop
Put the console.log before returning the view from the map function:
<View>
{shift.time_breakup.break_timings.map((item: any, index: any) => {
console.log(item.break_in_time);
return (
<View>
<Text>{item.break_in_time}</Text>
<Text>{item.break_out_time}</Text>
</View>
);
})}
</View>
You get infinite loop because on each render your function getShiftDetails gets redefined, React creates a shallow object of it on each render cycle, you can use useCallback to memoize it and Declare ID as dependency array.

Javascript Promise.all results from fetching multiple apis not rendering in the React Native FlatList

I am using Promise.all in order to fetch multiple apis.
const ListScreen = () => {
const first = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const second = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const third = fetch('https://EXAMPLEAPI').then(resp => resp.json())
const retrieveAll = async function () {
let results = await Promise.all([first, second, third])
When console.log(results), I get all arrays of objects from apis
The problem is that when I create a FlatList, I don't get anything to be rendered on the screen(blank)
const retrieveAll = async function () {
let results = await Promise.all([first, second, third])
return (
<FlatList
keyExtractor={item => item.title}
data={results}
renderItem={({ item }) => {
return <Text>{item.title}</Text>
}}
/>
)
};
}
export default ListScreen;
What am I doing wrong?
Please help. :(
You need to re-render the component, for that you will have to use react Hooks.
This is how the component will look like
const RetrieveAll = function () {
const [ results, setResults ] = useState([])
useEffect( () => {
Promise.all([first, second, third])
.then(response => {
setResults(response)
})
}, [])
return (
<FlatList
keyExtractor={item => item.title}
data={results}
renderItem={({ item }) => {
return <Text>{item.title}</Text>
}}
/>
)
};
Usage
<RetrieveAll />
And try not to create async JSX elements.

React-Native infinite loop

I am trying to get data from my firebase-firestore I an showing a loading state to wait for the data to load however when it does load it keeps returning the firestore data infinite times. Please may someone help me.
This is my code Paper is just a custom component
import Paper from '../Components/Paper'
import firebase from 'firebase'
import { useState } from 'react'
const Home = (props) => {
const renderMealItem = (itemData) =>{
return (
<Paper
title={itemData.item.name}
serves={itemData.item.servings}
time={itemData.item.time}
image={itemData.item.imageUri}
/>
)
}
const [loading, setLoading] = useState(false)
const [all, setAll] = useState([])
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
},[])
const checkReturn = () => {
if(all !== undefined){
setLoading(false)
}
}
const getUser = async() => {
try {
await firebase.firestore()
.collection('Home')
.get()
.then(querySnapshot => {
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
});
}catch(err){
console.log(err)
}
}
return(
<View style={styles.flatContainer}>
<FlatList
data={all}
keyExtractor={(item, index) => index.toString()}
renderItem={renderMealItem}/>
</View>
)
}
useEffect without second parameter will get executes on each update.
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
})
so this will set the loading and tries to get the user. and when the data comess from server, it will get runned again.
So you should change it to : useEffect(() => {...}, []) to only get executed on mount phase(start).
Update: you should check for return on every update, not only at start. so change the code to:
useEffect(() => {
setLoading(true)
getUser()
}, [])
useEffect(() => {
checkReturn()
})
Ps: there is another issue with your code as well:
querySnapshot.docs.forEach(doc => {
setAll(JSON.stringify(doc.data()));
});
maybe it should be like :
setAll(querySnapshot.docs.map(doc => JSON.stringify(doc.data())));
Try passing an empty array as an argument to useEffect like so:
useEffect(() => {
setLoading(true)
checkReturn()
getUser()
}, [])

Using useEffect with class Components

I have a class used to render a list of users from a database
export default class Users extends React.Component {
constructor() {
super()
this.state = {
data : [] //define a state
}
}
renderUsers = () => {
useEffect(() => {
fetch('exemple.com')
.then((response) => response.json())
.then((json) => this.setState({data: json.result})) // set returned values into the data state
.catch((error) => console.error(error))
}, []);
return this.state.data.map((value,key)=>{ // map state and return some views
......
})
}
render() {
return (
<View style={{ flex: 1 }}>
{this.renderUsers()} //render results
</View>
);
}
}
The problem is this code will throw the following error :
Invalid Hook call, Hooks can be called only inside of the body
component
I think is not possible to use hooks inside class component..
If it's not possible what is the best approach to fetch data from server inside this class ?
You cannot use hooks in a class component. Use componentDidMount instead.
Hooks can be used only in functional components.
You could rewrite your component to be a functional one like so:
export default Users = () => {
const [data, setData] = useState([]);
useEffect(() => {
fetch('exemple.com')
.then((response) => response.json())
.then((json) => setData(json.result)) // set returned values into the data state
.catch((error) => console.error(error))
}, []);
const renderUsers = () => {
return data.map((value, key) => {
// DO STUFF HERE
})
}
return (
<View style={{ flex: 1 }}>
{renderUsers()} //render results
</View>
)
}

Categories

Resources