Reusing async function in react native - react-native

I am building a page to fetch data from API when loaded, but encounter waring an effect function must not return anything besides a function which is used for clean-up when trying to reuse the function for fetching data
const dispatch = useDispatch();
useEffect(() => {
// this way does not work as I expected, my page does not show data I fetched
const getData = async () => {
const result = await dispatch(actions.getList());
setState(result);
};
getData();
},[isFirstLoaded]);
But I get the warning when trying below
const dispatch = useDispatch();
const getData = async () => {
const result = await dispatch(actions.getList());
setState(result);
};
useEffect(async() => {
// this way gives me the data but with a warning
await getData();
},[isFirstLoaded]);
How should I reuse the getData function? I did not update the state if I am not using the async and await here. When I use async and await here, I get the warning. Thanks.

overall, you are heading in the right direction. For fetching data, you'd wanna use use Effect and pass [] as a second argument to make sure it fires only on initial mount.
I believe you could benefit from decoupling fetching function and making it more generic, as such:
const fetchJson = async (url) => {
const response = await fetch(url);
return response.json();
};
const Fetch = () => {
const [data, setData] = useState(null);
useEffect(() => {
fetchJson("https://api.coindesk.com/v1/bpi/currentprice.json")
.then(({ disclaimer }) => setData(disclaimer));
}, []);
return <Text>{data}</Text>;
};

Related

React Native undefined is not an object while fetching data

I want to print a document that uses data that I need to fetch first but it always gets 'undefined is not an object (evaluating 'items.username')' error since the items are still in the fetch state while the HTML variable is created
here's an example of my code
const [items, setItems] = React.useState();
const [loading, setLoading] = React.useState(true)
const getData = async () => {
const response = await axiosInstance.get(`/userData/get`)
if (response?.data?.success) {
const result = response.data?.result[0] || [];
setItems(result);
}
}
React.useEffect(() => {
(async () => {
setLoading(true);
try {
await getData();
} catch (err) {
Alert.alert("", err?.response?.data?.message || err?.message);
}
setLoading(false);
})();
}, []);
const userHTML = `<h1>${items.username}</h1>`
const printHTML = async () => {
await Print.printAsync({
userHTML,
});
};
is there a way to delay the userHTML creation so it will be generated after the fetching is done or maybe there is a best practice to do this kind of thing?
note: the print function will be on the same page as where the userData is being fetched since the data always changed so it will not be possible to fetch data and store it first then move to the page with the print function
You can set the useHTML like this:
const userHTML = `<h1>${!!items ? items.username : ''}</h1>`

Jest does not continue after async method

I have an async method triggered by a click event where I make a call to an API and then process the response, like this:
async confirmName () {
const {name, description} = this.form;
const [data, error] = await Pipelines.createPipeline({name, description});
if (error) {
console.error(error);
this.serviceError = true;
return false;
}
this.idPipelineCreated = data.pipeline_id;
return true;
}
The test looks like this:
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
pipelinesApi.createPipeline().then(data => {
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
}).catch(() => {})
})
A basic class mock:
export default class Pipelines {
constructor () {}
createPipeline () {}
}
I'm testing a success API call and I mock the API call returning a resolved promised. The problem is the coverage only covers the first two lines of the method, not the part where I assign the response of the API call. Is this the correct approach?
Edit:
Screenshot of coverage report:
Don't mix up await and then/catch. Prefer using await unless you have very special cases (see this answer):
test("API success", async () => {
const ConfirmNameBtn = wrapper.find(".form__submit-name");
await ConfirmNameBtn.vm.$emit("click");
const pipelinesApi = new Pipelines();
jest.spyOn(pipelinesApi, "createPipeline").mockResolvedValue({pipeline_id: 100});
const {name, description} = wrapper.vm.form;
const data = await pipelinesApi.createPipeline();
expect(wrapper.vm.pipelineNameServiceError).toBe(false);
wrapper.setData({
idPipelineCreated: data.pipeline_id
});
expect(wrapper.vm.idPipelineCreated).toBe(data.pipeline_id)
expect(wrapper.vm.serviceError).toBe(false);
})

Setting state using hooks resulting in null value in subsequent function call

First I call this where result is an object
const _onSaveEvent = async (result) => {
console.log(result);
setSignature(result);
followUp();
};
Then I invoke,
const handleActualSubmission = async () => {
console.log(signature);
const { encoded, pathName } = signature;
const extension = pathName.split('.').pop();
const path = `${RNFS.DocumentDirectoryPath}/${uuidv4()}.${extension}`;
await RNFS.writeFile(path, encoded, 'base64');
stops.forEach(async (stopForPhotoConf) => {
await saveDeliveryProof(driverRoute, stopForPhotoConf, path, extension, 'signature');
});
close();
};
The resulting error is signature being null on line 1 of handleActualSubmission where const [signature, setSignature] = useState(null);
I have verifeid that the _onSaveEvent function always returns a truthy value. This issue does not appear every time this sequence of functions runs.
This code should work:
useEffect(() => {
if (signature) {
processSavedSignature();
}
}, [signature]);

React native fetch from URL every X seconds

In the page I have two things to do, first I fetch some content from API and display it. After that, I will fetch another API every 5 seconds to see whether the status of the content displayed has been changed or not.
In my MyScreen.js
const MyScreen = props => {
const dispatch = useDispatch();
const onConfirmHandler = async () => {
try {
//get content from API and display on the screen
const response1 = await dispatch(action1(param1,param2));
if(response1==='success'){
// I want to check the result of response2 every 5 seconds, how can I do this?
const response2 = await dispatch(action2(param3));
}
}catch {...}
}
return (
<Button title='confirm' onPress={onConfirmHandler}}>
)
}
The actions I fetch the API in actions.js:
export default action1 = (param1,param2) ={
return async (dispatch, getState) => {
// To call the API, I need to used token I got when login
let = getState().login.token;
const response = await fetch(url,body);
const resData = await response.json();
if (resData==='success'){
return param3
}
}
export default action2 = (param3) ={
return async (dispatch, getState) => {
// To call the API, I need to used token I got when login
let = getState().login.token;
const response = await fetch(url,body);
const resData = await response.json();
if (resData==='success'){
// if the content status changed, I change the view in MyScreen.js
return 'changed';
}
}
I have also met this problem already. Here you can't use the timeout function. Instead, you can use react-native-socketio or react-native-background-fetch packages. I prefer react-native-background-fetch. So you can fetch results at a specific interval and update the state on change of content.

How to outsource asyncData to Vuex Store?

I'm currently loading some data from firebase I wan't to be server side rendered so it can be indexed for SEO in asyncData on a page.
asyncData() {
return firebase.firestore().collection('Programms').get().then((querySnapshot) => {
const programms = [];
querySnapshot.forEach((doc) => {
const programm = doc.data();
programm.id = doc.id;
programms.push(programm)
})
return { programms: programms};
})
However I would like to convert this to my vuex store.
I know I could do this:
const actions = {
async nuxtServerInit({ commit }) {
firebase.firestore().collection('Programms').onSnapshot((querySnapshot) => {
const programms = [];
querySnapshot.forEach((doc) => {
const programm = doc.data();
programm.id = doc.id;
programms.push(programm)
})
console.log('loaded Programms', programms)
commit('setProgramms', programms);
})
},
}
But this way the data will be loaded for every route in my app. I wan't to load this data only in some pages where I also display it, so I don't load it unnecessary.
How could I do this in Vuex?
As #aldarund says, the fetch method is precisely what you want.
fetch ({ store, params }) {
return firebase.firestore().collection('Programms').get().then((querySnapshot) => {
const programms = [];
querySnapshot.forEach((doc) => {
const programm = doc.data();
programm.id = doc.id;
programms.push(programm)
})
.then(() => {
store.commit('setPrograms', programms)
})
}
See the docs here.