Setting state using hooks resulting in null value in subsequent function call - react-native

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

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

In reactnative expo I tried using secureStore from expo in redux to save token the one I get from api

I tried using redux to save token the one I get from api in react native ..its working now.
First one is for settoken and other one is for gettoken.
enter image description here
export const verifyOTP = (formValues, actions) => {
return async (dispatch) => {
dispatch(startSubmitting());
const url = `/validate-otp`;
var formdata = new FormData();
formdata.append("mobile", formValues.mobile);
formdata.append("otp", formValues.otp);
const response = await api.post(url, formdata);
dispatch({
type: "VERIFY_OTP",
payload: response,
});
dispatch(stopSubmitting());
await SecureStore.setItemAsync("userToken", response.data.access_token);
};
};
export const checkUser = () => {
return async (dispatch) => {
const token = await SecureStore.getItemAsync("userToken");
const url = `/me`;
const response = await api
.post(url, { token })
.then((res) => {
return res;
})
.catch((error) => {
return error.response;
});
dispatch({
type: "CHECK_USER",
payload: response,
});
};
};
The Problem
you are mixing two different implementations in checkUser to handle a promise which is clearly incorrect and leads to the issues.
The Solution
since your other parts of codes use the async/await so try to remove then/catch block from the response constant:
const checkUser = () => {
return async (dispatch) => {
const url = '/me';
try {
const token = await SecureStore.getItemAsycn("userToken);
const response = await api.post(url, {token})
dispatch({type: "CHECK_USER", payload: response})
} catch (error) {
// to proper action on failure case
}
}
}
Note 1: always use async/await in try/catch block. more on MDN documentation.
Optional
since you are trying to call two async actions (once for getting token and once for calling '/me' API), I encourage you to use two different try/catch blocks to handle the failure case for each async action separately. for example:
const checkUser = () => {
return async (dispatch) => {
let token = null;
try {
token = await SecureStore.getItemAsync("userToken");
} catch (err) {
// proper action in case of failure on getting the token from storage
}
// you may need to ignore API calls without the token, so:
try {
if(token){
const url = '/me';
const response = await api.post(url, {token});
dispatch({type: "CHECK_USER", payload: response});
}
} catch (err) {
// take proper action with the error response according to your applicaiton
}
}
}

How do get value from async function without then()

I know I have to use then() or in a async function to use await to get the value from other async function .
but how to I get value directly ?
I try to pass the value in normal function but not work .
there is no other way to get the value directly ?
thanks
here is ample :
load_data_normal(key){
this.load_data(key).then((ret_val)=>{
console.log(ret_val);
return ret_val;
})
}
load_data = async (key) => {
const MMM = await AsyncStorage.getItem(key);
return MMM;
}
load_data function just work with then(), but load_data_normal not work ,
I just want to get value from get_data without then ..
The simple solution is just adding async to your load_data_normal, if you want to know more read this https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
load_data_normal = async (key) => {
const ret_val = await this.load_data(key);
return ret_val;
}
load_data = async (key) => {
const MMM = await AsyncStorage.getItem(key);
return MMM;
}
Normal function doesn't work with await. You need an async function.
const asynFunction = async() => {
const results = await fetch(url);
console.log('results', results)
}
If you don't want to use async, you need to create a new promise.
const load_data_normal = (key) => {
localStorage.setItem("abc", "somevalue in abc");
load_data(key).then((resuls) => {
console.log("abc", resuls);
});
};
const load_data = (someKey) => {
return new Promise((resolve) => resolve(localStorage.getItem(someKey)));
};
load_data_normal("abc");
Sandbox

Reusing async function in 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>;
};