I am trying to make an app that will allow me to update a textbox after receiving a change in a variable. However the variable takes a long time to update, and await does not work to wait for my variable to update probably because of the timeout function I used. How do I create a listener or something of that sort to check for any variable changes?
Below is my code snippet
const [banana, setBanana] = useState(-1);
const updateLocation = async() => {
const majorSig = scheduledScan();
setBanana(majorSig);
}
const scheduledScan = async() => {
beaconScan();
// Scans for 20 seconds
setTimeout( async()=> {
beaconStop();
await getAndUpdateUserLoc();
// console.log("Loggged: ", await getUserLoc());
// console.log("major: ", await getSignificantMajor());
currentMajor = await getSignificantMajor();
return currentMajor;
}, 20000);
}
When I run updateLocation(), my code is supposed to run for 20 second. I want it to wait until it finishes running scheduledScan() and returns a value to majorSig before it runs the setState function. However right now all it does is run scheduledScan() and update setState immediately to a wrong value. What should I do to make it behave in the way I want?
Thank you.
Firstly, in your async updateLocation function, your await statement is missing. Let's add it appropriately:
const updateLocation = async () => {
const majorSig = await scheduledScan();
setBanana(majorSig);
};
Then, It would be a good idea if you follow a promise approach in your time-limited function by using a Promise.race which lets your function either time out or successfully return a value:
const scheduledScan = async () => {
beaconScan();
return Promise.race([
async () => {
beaconStop();
await getAndUpdateUserLoc();
// console.log("Loggged: ", await getUserLoc());
// console.log("major: ", await getSignificantMajor());
currentMajor = await getSignificantMajor();
return currentMajor;
},
new Promise((_, reject) => setTimeout(() => reject(new Error('Request timed out')), 20000)),
]);
};
Related
So, from code below im getting array of udefined but to be honest idk why and how to fix it...
const promises = sessionsIds.map((id) =>
stripe.checkout.sessions
.listLineItems(id, { limit: 100 })
.then((err, lineItems) => {
return lineItems;
})
);
const usernames = await Promise.all(promises);
I have also tried with async await but the result is still the same.
Assuming that stripe.checkout.sessions.listLineItems(id, { limit: 100 }) is returning a regular, standard promise, then the problem is that you're treating is like a regular asynchronous callback with (err, value) as the functions arguments. That's not how .then() works. The first argument to .then() is a function that will be called with one argument, the resolved value. In fact, you probably don't even need a .then() here.
You can change to this:
const promises = sessionsIds.map((id) =>
stripe.checkout.sessions.listLineItems(id, { limit: 100 })
);
const usernames = await Promise.all(promises);
Or, simplifying further:
const usernames = await Promise.all(sessionsIds.map((id) =>
stripe.checkout.sessions.listLineItems(id, { limit: 100 })
));
Note, the .then() handler here is not necessary (assuming stripe.checkout.sessions.listLineItems(id, { limit: 100 }) already returns a promise). If you want to use a .then() handler, it would go like this:
const promises = sessionsIds.map((id) =>
stripe.checkout.sessions.listLineItems(id, { limit: 100 })
.then((lineItems) => {
console.log(lineItems);
return lineItems;
});
);
const usernames = await Promise.all(promises);
Where you pass it a function as the first argument and it gets one argument (the resolved value).
What I am trying to do is sync a list of attendees from an online database, and if the current user is in the list, then disable a button, else enable the button.
I am using react native hook (I am not sure if I am using the term correctly as I am fairly new to react), in order to set the value of disabling the button.
The issue that I am facing is that the value is getting initialized to false, even tho it should clearly get initialized to true.
After adding some logging I made sure that the function is executing correctly and reaching the code where it sets the value to true.
const [buttonDisabled, changeButtonState] = useState( () => {
var database = firebase.database();
var userId = firebase.auth().currentUser.uid;
const dbRef = firebase.database().ref();
var Attendees = [];
var disable = false;
dbRef.child("gameAttendees").child(gameinfo.gameID).get().then((snapshot) => {
if (snapshot.exists()) {
Attendees = snapshot.val().Attendees;
for(var i=0;i<Attendees.length;i++){
if(Attendees[i]==userId){
return true;
}
}
} else {
console.log("no value");
return false;
}
}).catch((error) => {
console.error(error);
});
});
Adding an example of an async mount effect:
const Comp = () => {
const [s, setS] = useState(); // State will be undefined for first n renders
useEffect(() => {
// Call the async function and set the component state some time in the future
someAsyncFunction().then(result => setS(result));
}, []); // An effect with no dependencies will run only once on mount
return </>;
};
const [action, setAction] = useState(true);
const sharedVal = useSharedValue(action ? 10 : 20)
useEffect(() => {
setTimeout(() => {
setAction(false)
}, 2000)
}, [])
In the above code, the value of shareVal doesn't change the value of action changes. What is the best way to update value of sharedVal after the timeout.
you'll have to run a side effect when action changes.
useEffect(() => {
sharedVal.value = action ? 10 : 20;
// this will run when action is updated
}, [action]);
or you can just do sharedVal.value = someValue wherever you want.
documentation
(...) The reference is an object with .value property, that can be accessed and modified from worklets, but also updated directly from the main JS thread.
Try this way
function Example() {
const [action, setAction] = useState(true);
React.useEffect(() => {
async function useSharedValue(number) {
const response = await fetch(....);
const json = await response.json();
setAction(true); // reset state here
}
useSharedValue(action ? 10 : 20);
}, []);
}
I have a code similar to yours. UseEffect is called every time there is a change in the alert variable, as you can see in my code bellow. If the second argument is empty [], useEffect will only be executed once when the page is loaded. As you want to call every time there is a change in the value of the action variable, just place it inside [].
useEffect(() => {
if (alert != '') setTimeout(() => { setAlert('') }, 4000);
}, [alert]);
Update
I think the problem is that the credentials array which I fill from the response of the fetchCredentials() function does not get filled quickly so the array becomes empty so no change in length occurs, is there any way to wait until the array gets filled? Also how to make the first fetch method runs always but make the UI renders only if the offeredCredentialsArraySize changes. Also I am using a flat list to list the offerred credentials so will the extraData prop help me? *
I have a problem in useEffect() and the await. I need to re render the code each time the offeredCredentialsArraySize changes, I tried everything but nothing seems to work.
I think the problem is in the await in the fetchCredentials() function, as I understand the await should make the code wait until this method finishes and returns the result and then the fetchOfferedCredentials() should get executed where the size of the array changes, followed by the console.log() but this is not the case, the console.log() prints in the beginning and does not wait for the fetch and the await to finish.
To make the problem more clear, the fetchCredentials() returns as a result an array of credentials, next I check in the fetchOfferedCredentials() function if the array returned from the first function contains any credentials in the "offered" state, if so, I then need to re render and print them. Also inside the fetchOfferedCredentials() I need to remove some of these credentials after the user accepts the offer so I do so again by checking if the state of any of the credentials which are stored in the array containing the result of the fetchCredentials() method changed to "issued" I then remove this offeredCredential from the offered array so its size changes once more and then I will also need to re render.
So to sum up I need the fetch to run all the time and whenever the size of the offeredCredentials array changes I need to re render the UI but I think there is a problem in the await so any help?
Bellow is the code.
Thank you in advance.
const [credentials, setCredentials] = React.useState([]);
const [offeredCredentials,setOfferedCredentials] = React.useState([]);
const [arraySize2, setArraySize2] = React.useState(0);
const [connectionDetailsArray, setConnectionDetailsArray] = React.useState();
const [connectionDataArray, setConnectionDataArray] = React.useState([]);
const [connectionDetailsArraySize, setConnectionDetailsArraySize] = React.useState(0);
const [count, setCount] = React.useState(0);
const [offeredCredentialsArraySize,setOfferedCredentialsArraySize] = React.useState(0);
React.useEffect(() => {
fetchCredentials();
fetchOfferedCredentials();
console.log("This should be printed after the fetch")
},[offeredCredentialsArraySize]);
async function fetchCredentials() {
const res = await fetch('https://api.streetcred.id/custodian/v1/api/' + walletID + '/credentials', {
method: 'GET',
headers: {
Authorization: 'Bearer ',
XStreetcredSubscriptionKey: '',
Accept: 'application/json',
},
});
res.json().then(res => setCredentials(res)).then(setArraySize2(credentials.length));
}
async function fetchOfferedCredentials()
{
setCount(count+1);
for (let index = 0; index < arraySize2; index++)
{
if(credentials[index].state=="Offered")
{
setOfferedCredentials(addConnectionDetails(offeredCredentials,credentials[index].credentialId, credentials[index]) );
console.log(offeredCredentials);
}
}
for (let index = 0; index < offeredCredentials.length; index++)
{
let tempConnectionID= offeredCredentials[index].connectionId;
const res = await fetch('https://api.streetcred.id/custodian/v1/api/'+walletID+'/connections/'+tempConnectionID, {
method: 'GET',
headers: {
Authorization: 'Bearer L2JBCYw6UaWWQiRZ3U_k6JHeeIkPCiKyu5aR6gxy4P8',
XStreetcredSubscriptionKey: '4ed313b114eb49abbd155ad36137df51',
Accept: 'application/json',
},
});
res.json().then(res => setConnectionDetailsArray(res));
const obj = { id: connectionDetailsArray.connectionId,credentialId:offeredCredentials[index].credentialId, title: connectionDetailsArray.name, image: connectionDetailsArray.imageUrl };
setConnectionDataArray(addConnectionDetails(connectionDataArray,obj.id,obj));
}
for (let index = 0; index < arraySize2; index++)
{
if(credentials[index].state=="Issued")
{
for (let index2 = 0; index2 < offeredCredentials.length; index2++) {
if(offeredCredentials[index2].credentialId == credentials[index].credentialId)
{
console.log("here")
offeredCredentials.splice(index2,1)
credentials.splice(index,1)
}
}
}
}
if(count<50)
setOfferedCredentialsArraySize(count+1);
if(currArraySize2!=arraySize2)
setCount(count+1);
console.log(offeredCredentials.length);
}
Because fetchCredentials is an async function, it returns a promise. Because you didn't actually return anything in that function, the promise will resolve to undefined. If you want fetchOfferedCredentials() to be called after fetchCredentials is finished, you need to await that promise returned by fetchCredentials().
You might try
React.useEffect(async () => {
await fetchCredentials();
fetchOfferedCredentials();
console.log("This should be printed after the fetch")
},[offeredCredentialsArraySize]);
However, because of 'race conditions' as stated (but not explained) in here and here, you should wrap your function in another async inside the function:
React.useEffect(() => {
const fetchAllCredentials = async() => {
await fetchCredentials();
fetchOfferedCredentials();
console.log("This should be printed after the fetch")
}
fetchAllCredentials()
},[offeredCredentialsArraySize]);
I created a recordSaga function, its target is to record what actions have been dispatched during the saga.
export const recordSaga = async (saga, initialAction, state) => {
const dispatched = [];
const done = await runSaga(
{
dispatch: action => dispatched.push(action),
getState: () => state,
},
saga,
initialAction,
).done;
return {
dispatched,
done,
};
};
so let's say my saga is this one
export function* mySaga() {
const needToSave = yield select(needToSaveDocument);
if (needToSave) {
yield put(saveDocument());
yield take(SAVE_DOCUMENT_SUCCESS);
}
yield put(doSomethingElse())
}
I want to write two tests, which I expect to be the following
describe('mySaga', async () => {
it('test 1: no need to save', async () => {
const state = { needToSave: false }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
doSomethingElse()
])
})
it('test 2: need to save', async () => {
const state = { needToSave: true }
const { dispatched } = await recordSaga(mySaga, {}, state);
expect(dispatched).toEqual([
saveDocument(),
doSomethingElse()
])
})
})
However, for the test 2 where there is a take in between, and of course jest (or its girlfriend jasmine) is yelling at me: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
I know it is because runSaga is waiting for the take(SAVE_DOCUMENT_SUCCESS), but how can I mock that up ?
The answer stdChannel().put({type, payload})
Why ?
Using stdChannel you can dispatch after the first run.
How ?
import stdChannel;
add to the first param in runSaga;
call stdChannel().put(SAVE_DOCUMENT_SUCCESS);
Example
what worked for me
I left the first test as it is the expected final result, but the solution comes on the last 2.
import { runSaga, stdchannel } from 'redux-saga'
let dispatchedActions = [];
let channel;
let fakeStore;
beforeEach(() => {
channel = stdChannel(); // you have to declare the channel to have access to it later
fakeStore = {
channel, // add it to the store in runSaga
getState: () => "initial",
dispatch: (action) => dispatchedActions.push(action),
};
});
afterEach(() => {
global.fetch.mockClear();
});
it("executes getData correctly", async () => {
await runSaga(fakeStore, getData, getAsyncData("test")).toPromise();
expect(global.fetch.mock.calls.length).toEqual(1);
expect(dispatchedActions[0]).toEqual(setData(set_value));
});
it("triggers takeLatest and call getData(), but unfortunately doesn't resolve promise", async () => {
await runSaga(fakeStore, rootSaga)// .toPromise() cannot be used here, as will throw Timeout error
channel.put(getAsyncData("test")); // if remove this line, the next 2 expects() will fail
expect(global.fetch.mock.calls.length).toEqual(1);
// expect(dispatchedActions[1]).toEqual(setData(set_value)); // will fail here, but pass on the next it()
});
it("takes the promised data from test above", () => {
expect(dispatchedActions[1]).toEqual(setData(set_value));
});
this answer (about true code, not tests) helped me
By looking at recordSaga:
export const recordSaga = async (saga, initialAction, state) => {
It seems that you should pass {type: SAVE_DOCUMENT_SUCCESS} as a second argument (i.e initialAction). That should trigger the take effect.