How to call API inside expo SplashScreen? - react-native

I'm new in react native so I can't figure out how to add an API call inside SplashScreen in react -native app. The context - I'm building a react-native app expo, which on app load should send API GET request to the backend to get order data, and based on that data I'm either displaying screen A(delivered) or B(order on it's way). I want to add this API call inside the SplashScreen when app still loads so when app is loaded there is no delay in getting API data and displaying screen A/B.
I have a simple useEffect function to call API like this:
const [data, setData] = useState{[]}
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(url);
if (response.status.code === 200 ) {
setData (response.data) // to save data in useState
}
} else if (response.status.code != 200) {
throw new Error();
}
} catch (error) {
console.log(error);
}
};
getData();
}, []);
and then in the return:
if (data.order.delivered) {
return <ScreenA />
}
else if (!data.order.delivered) {
return <ScreenB />
else {return <ScreenC />}
The issue is that sometimes if API is slow, then after splash screen app has a white screen, or ScreenC can be seen. How can I call API in the splashscreen while app is loading and have a nicer ux?

you can make a custom hook with simple UseState and put it after you've fetched your data
const [loading, setLoading] = useState(true)
...
useEffect(() => {
const getData = async () => {
try {
const response = await axios.get(url);
if (response.status.code === 200 ) {
setData (response.data)
// When data is ready you can trigger loading to false
setLoading(false)
}
...
and After that, you can use a Simple If statement on top of your app.js file
like this
if (!loaded) {
return <LoadingScreen/>; // whetever page you want to show here ;
}

you can use expo expo-splash-screen to achieve this goal:
call this hook on mount...
import * as SplashScreen from 'expo-splash-screen';
const [appIsReady, setAppIsReady] = useState(false);
useEffect(() => {
async function prepare() {
try {
// Keep the splash screen visible while we fetch resources
await SplashScreen.preventAutoHideAsync();
// Pre-load fonts, make any API calls you need to do here
await Font.loadAsync(Entypo.font);
// Artificially delay for two seconds to simulate a slow loading
// experience. Please remove this if you copy and paste the code!
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (e) {
console.warn(e);
} finally {
// Tell the application to render
setAppIsReady(true);
}
}
prepare();
}, []);
you can also check expo doc

Related

fetch returns first empty array, after that it returns response

I'm making my final degree project and now I'm involved in the part to show towns on a map using data from a backend URL.
The problem is that fetch returns at first an empty array, and I need it to stay loading until the variable is a valid JSON.
const [isLoading, setLoading] = useState(true);
const [markers, setMarkers] = useState([]);
const getTowns = async () => {
try {
const response = await fetch(`${baseUrl}/towns`);
const json = await response.json();
setMarkers(json.data);
} catch (error) {
console.error(error);
} finally {
setLoading(false);
}
};
useEffect(() => {
getTowns();
}, []);
Another question is: Why when I put a console.log('whatever') it appears more than one time on the console. Don't understand why
What I need is fetch to setLoading(false) only when response is a JSON, not an empty array
What you can do is add these 2 hooks to your code:
A state for the returned response (with initial value an empty array)
A useEffect which will set to false isLoading once the response state change value
const [response, setResponse] = useState([]);
const getTowns = async () => {
try {
setResponse(() => await fetch(`${baseUrl}/towns`));
const json = await response.json();
setMarkers(json.data);
} catch (error) {
console.error(error);
}
};
useEffect(() => {
setLoading(() => false);
}, [response]);
your code is fine you juste don't need the isLoading useState Hook. you can test with the value of markers
Why when I put a console.log('whatever') it appears more than one time on the console. Don't understand why
when the component first render getTowns runs in useEffect and since it updates the state the component renders again. learn more here about react component lifecycle
what I suggest is when you are returning your jsx you check if markers is still empty
const [markers, setMarkers] = useState('');
if(markers === ''){
return (<div>Loading...<div/>)
} else {
return(<>
.... what you need to return in you component
</>)
}

how to use hooks inside task manager function used by background fetch on expo react native

it's possible to use hooks inside task manager function triggered with background fetch of expo react native?
or another ways to update the app state inside background fetch
I'm getting this error "TaskManager: Task "background-fetch" failed:, [Error: Invalid hook call. Hooks can only be called inside of the body of a function component"
here is my code on App.js
const BACKGROUND_FETCH_TASK = 'background-fetch';
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
const [sync, setSync] = React.useState(false);
console.log(sync);
const now = Date.now();
console.log(`Got background fetch call at date: ${new Date(now).toISOString()}`);
return BackgroundFetch.BackgroundFetchResult.NewData;
});
function registerBackgroundFetchAsync() {
console.log('register');
return BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
minimumInterval: 1,
stopOnTerminate: false,
startOnBoot: true,
});
}
and inside my component I'm registering the task after the load of some resources
React.useEffect(() => {
async function loadResourcesAndDataAsync() {
try {
await SplashScreen.preventAutoHideAsync();
// Load our initial navigation state
setInitialNavigationState(await getInitialState());
// Load fonts
await fetchFonts()
} catch (e) {
console.warn(e);
} finally {
setLoadingComplete(true);
registerBackgroundFetchAsync();
await SplashScreen.hideAsync();
}
}
loadResourcesAndDataAsync();
}, []);

Making a useEffect cleanup for multiple axios calls in React Native

I am trying to make an axios call to a video game database with a URL specific to the chosen option by the user. Depending on the 'opt' chosen, it will get all the data linked to the option and place into a useState ('selectedResults') as shown below
function ViewContent() {
const [selectedOpt, setSelectedOpt] = useState('none');
const opts = ["Games", "Publishers and Developers", "Reviews", "Platform"];
const [selectedResults, setSelectedResults] = useState([]);
const getGames = (selectedOpt) => {
if(selectedOpt == 'Games'){
axios.get(apiURL.games)
.then(function (response){
results = JSON.stringify(response.data.data);
setSelectedResults(response.data.data);
})
.catch(function (error){
alert(error.message)
console.log('getGameTest', error);
})
}
else if(selectedOpt == 'Publishers and Developers'){
axios.get(apiURL.creators)
.then(function (response){
// results = JSON.stringify(response.data.data);
setSelectedResults(response.data.data);
})
.catch(function (error){
alert(error.message)
console.log('getCreatorTest', error);
})
}
else if(selectedOpt == 'Reviews'){
}
};
}
The error I get is
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in a useEffect cleanup function.
Is there a way I can use the useEffect cleanup for multiple axios calls or would I have to make one for each? I am trying to look over some examples but I am not getting any progress. If I missed anything, feel free to ask and I will update the question.
You might want to update react state when the component
is mounted.
export const useIsMounted = (): { readonly current: boolean } => {
const isMountedRef = React.useRef(false);
React.useEffect(() => {
isMountedRef.current = true;
return () => {
isMountedRef.current = false;
};
}, []);
return isMountedRef;
};
You can create a hook just like the above one or you can prefer useIsMounted hook from this source in order to detect if the component is mounted. Then in your case, simply do the logic below.
if(isMounted.current) {
yourSetStateAction()
}

React Native useEffect() re-renders too much

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

Can I use a function with async await inside ComponentDidMount in React Native?

I am using AsyncStorage.getItem in React Native.
As soon as the Application loads, I want to get the saved data in the device memory using AsyncStorage.getItem. I thought of using ComponentDidMount(). So as soon as the components are loaded, I want to run the AsyncStorage.getItem automatically and save the data to the array DATA. This way the user will not push any button to start rendering what is saved on the memory of the device.
I used the code below, but I do not see any console.log activity. But the console.log works on my other pages on same program here. It seems the ComponentDidMount() did not get executed.
Thanks!
componentDidMount(){
async () => {
try {
const HomeData = await AsyncStorage.getItem('#MyApp_Homekey')
return HomeData
} catch (e) {
console.log('There was error.')
}
if(!HomeData){
console.log('There are no Dimmer Light saved in the memory.')
}
else {
console.log('There is value in data after getItem.')
this.setState(DATA = HomeData)
}
}
As metioned in comment you should use async for componentDidMount as:-
componentDidMount = async () => {
const HomeData = await AsyncStorage.getItem('#MyApp_Homekey')
if(!HomeData){
console.log('There are no Dimmer Light saved in the memory.')
}
else {
console.log('There is value in data after getItem.')
this.setState(DATA = HomeData)
}
}