i have a problem with following function;
const handleSave = (task, description, priorityState, taskID) => {
changeLabel(task, taskID);
changeDescription(description, taskID);
changePriority(priorityState, taskID);
navigation.goBack();
};
the problem is thats only change the last used function:
if i'll change the Label and the Description its only save the Description for example. <
i call the function while clicking a Button (TouchableOpacity)
<Button
title={language.edit.save}
color={theme.secondary}
onPress={() => {
handleSave(task, description, priorityState, taskID);
}}
/>
any advise?
what did i try?
delay the functions:
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
const handleSave = (task, description, priorityState, taskID) => {
changeLabel(task, taskID);
sleep(10);
changeDescription(description, taskID);
sleep(10); ...
};
i would like to thank any kind of help <3
full code here.
Your 3 custom functions changeLabel, changeDescription and changePriority all try to update the same tasks state:
Repo source
const [tasks, setTasks] = useState([]);
const changePriority = (color, taskID) => {
const task = tasks.map(/* etc. */);
setTasks(task);
};
const changeDescription = (description, taskID) => {
const task = tasks.map(/* etc. */);
setTasks(task);
};
const changeLabel = (value, taskID) => {
const task = tasks.map((/* etc. */);
setTasks(task);
};
When you successively call these functions, each one reads the current tasks state, which has not been updated yet by the previous call to setTasks SetStateAction. See also The useState set method is not reflecting a change immediately
You can easily solve this by using the functional update form of the SetStateAction:
If the new state is computed using the previous state, you can pass a function to setState. The function will receive the previous value, and return an updated value.
In your case, change your 3 functions to get the previous state as a parameter:
const changePriority = (color, taskID) => {
setTasks(
// Use previous tasks received as argument
// instead of directly tasks which may be
// an old value.
(previousTasks) => previousTasks.map(/* etc. */)
);
};
// Same for the other 2 functions.
The solution from #ghybs worked. I appreciate your work 👍 Thx
i changed the function from:
const markTask = (taskID) => {
const task = tasks.map((item) => {
if (item.id == taskID) {
return { ...item, completed: true };
}
return item;
});
setTasks(task);
};
to this:
const changeLabel = (value, taskID) => {
setTasks((previousTasks) =>
previousTasks.map((item) => {
if (item.id == taskID) {
return { ...item, label: value };
}
return item;
})
);
};
snack with the issue
Related
I'm going through to update my code for the Firebase version 9 modular form. I am now using onValue. From what I'm reading it returns a function that removes the listener. But I'm still not doing it right because although it functions well at first, when I change the database on the backend with the app open I get the "can't perform a react state update on an unmounted component" error when I'm in a different app screen. See old and new code below please.
OLD CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
let testData = [];
let searchData = [];
db.ref('deals').once('value', (snapshot) =>{
snapshot.forEach((child)=>{
testData.push({
id: child.key,
title: child.val().hasOwnProperty('title') ? child.val().title : 'NA',
})
searchData.push(
child.val().title
)
})
})
.then(()=>{
checkMessages(testData);
setLoading(false);
})
.catch((error) => Sentry.Native.captureException('Error MessagesScreen function loadListings 1 ' + error));
}
NEW CODE:
useEffect(() => {
loadListings();
},[]);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
After receiving answer below I changed the useEffect to this instead and now it works:
useFocusEffect(
React.useCallback( () => {
async function fetchData() {
// You can await here
const response = await loadListings();
// ...
return () => response();
}
fetchData();
}, [])
);
You mentioned the unsubscribe function returned from onValue. In order to call it, I think you'll want to grab it from the invocation and then call it on some navigation state change.
Assuming you're using React Navigation, it might look something like this (using the useFocusEffect
import { useFocusEffect } from '#react-navigation/native';
function YourComponent() {
const [loading, setLoading] = React.useState(false)
useFocusEffect(
React.useCallback(async () => {
const unsubscribe = await loadListings();
return () => unsubscribe();
}, [])
);
const loadListings = async () => {
setLoading(true);
updateInput('');
const dbRef = ref(db, 'deals');
return onValue(dbRef , (snapshot) => {
let testData = [];
let searchData = [];
let storeData = filterStores;
snapshot.forEach((childSnapshot)=>{
testData.push({
id: childSnapshot.key,
title: childSnapshot.val().hasOwnProperty('title') ? childSnapshot.val().title : 'NA',
})
})
checkMessages(testData);
setLoading(false);
})
}
return <View />;
}
Also don't forget to either use async/await for your asynchronous loadListings function, or use .then(). Otherwise you'll be working with an unresolved promise.
I also found a related StackOverflow question that helped me get to this answer. Maybe that'll be of some use to you.
Here is my code:
let delayTimeout = null;
const delayExecution = mls => {
console.log('Delaying for', mls);
return new Promise(resolve => {
delayTimeout = setTimeout(() => resolve('ok'), mls);
})
}
const main = async () => {
axios.post('URL', {data})
.then(response => {
if(response passes some condition){
clearTimeout(delayTimeout);
}
})
const res = await delayExecution(30000)
console.log("DONE!")
}
main();
After the axios call, I may want to terminate the delayExecution by clearing the timeout inside it. How do I clearTimeout inside my delayExecution function but still resolve the Promise?
In essence, I'm trying to finish delayExecution before its time, but still resolve the promise inside it.
Based on your edit, I'll just leave another response. Note that I haven't tested it, my mind is currently focused on my code I'm writing alongside this hehe
let delayTimeout = null;
let resolveHandler = null;
const delayExecution = mls => {
console.log('Delaying for', mls);
return new Promise(resolve => {
resolveHandler = resolve;
delayTimeout = setTimeout(() => resolve('ok'), mls);
})
}
const main = async () => {
axios.post('URL', {data})
.then(response => {
if(response passes some condition){
resolveHandler('ok');
clearTimeout(delayTimeout);
}
})
const res = await delayExecution(30000)
console.log("DONE!")
}
main();
The idea is just to assign the resolve function to another auxiliary variable which you can then use elsewhere :)
doneFunc should have the clearTimeout within it, so after the function is complete the timeout is cleared.
Also, for the first setTimeout parameter, you can just pass the name of the function.
Actually for timeout, you don't need the clearTimeout since it will only be ran ONCE compared to interval which is continuing run.
const doneFunc = () => {console.log('Finished job');clearTimeout(f);}
const f = setTimeout(doneFunc, 100);
If you want to run the function independently from the timeout, just declare the function outside of it, then call it whenever you want. You have most of the code done
const doneFunc = () => console.log('Finished job');
const f = setTimeout(() => doneFunc(), 10000);
/* Seome logic here */
if (condition to run before timeout) {
clearTimeout(f);
doneFunc();
}
/* end of logic */
I have imagined that :
const runOnDelay = function( fct, delay )
{
let obj = {}
, isDone = false
, refTim = setTimeout(()=>
{
isDone = true
fct()
}, delay)
;
obj.stop = () =>
{
clearTimeout(refTim)
if (!isDone)
fct()
isDone = true
}
return obj
}
usage:
const doneFunc = () => console.log('Finished job')
let myBoy = runOnDelay(doneFunc, 1000)
//...
myBoy.stop()
I have a situation when I need 2 Redux Actions to be run consecutively.
The context is a user clicks on a Preview button, and I want to display a loader until the puzzle is done generating.
function mapDispatchToProps(dispatch) {
return {
onPreview: () => {
dispatch(generatePreview());
},
};
}
In order to do it, I use the middleware redux-thunk and the action I want to be executed first returns a Promise.resolve() and my second action is in the then():
export function generatingPreview() {
return dispatch => {
dispatch({
type: GENERATING_PREVIEW,
});
return Promise.resolve();
};
}
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview()).then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
try {
const newPuzzle = Wordfind.newPuzzleLax(wordList, conf);
dispatch(generatePreviewSuccess(newPuzzle));
} catch (err) {
dispatch(generatePreviewError(err.message));
}
});
};
}
export function generatePreviewError(error) {
return {
type: GENERATE_PREVIEW_ERROR,
error,
};
}
export function generatePreviewSuccess(payload) {
return {
type: GENERATE_PREVIEW_SUCCESS,
payload,
};
}
Unfortunately, the loader never appears. I console.logged the state setting the loading to true when my component renders, and it changes! I can see the log but not the loader, the component doesn't really re-render until the actions generatePreviewSuccess() or generatePreviewError() are dispatched. And it's not an issue from the loader, if I replace the newPuzzleLax function by a loop in order to make enough time to see it, I can see it!
My theory is this function Wordfind.newPuzzleLax(wordList, conf) that I use to generate the puzzle is blocking the queue of actions because on the Chrome Redux Tools I an see the first action appearing at the same time that the second one:
Link to the function.
If I add a 1-microsecond delay between the dispatch of the two actions, the loader appears... but I would really like to understand what is happening. Thank you in advance. If it's any help, I use the react-boilerplate
I also tried to transform the function generating the puzzle as an async one by doing this:
const wordFindAsync = async (wordList, conf) =>
Wordfind.newPuzzleLax(wordList, conf);
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview())
.then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
wordFindAsync(wordList, conf);
})
.then(res => dispatch(generatePreviewSuccess(res)))
.catch(err => {
dispatch(generatePreviewError(err.message));
});
};
}
In your second version you're not returning the Promise from wordFindAsync(wordList, conf) back into your original Promise chain, and so its not being resolved/waited on by then next then.
export function generatePreview() {
return (dispatch, getState) => {
dispatch(generatingPreview())
.then(() => {
const state = getState();
const conf = state.getIn(['login', 'conf']).toJS();
const wordList = state.getIn(['login', 'wordList']);
return wordFindAsync(wordList, conf); // 🌟 return your promise here
})
.then(res => dispatch(generatePreviewSuccess(res)))
.catch(err => {
dispatch(generatePreviewError(err.message));
});
};
}
Here's a simple example demoing the behavior I'm refering to.
This one will only wait 1 second until logging "done":
const waitOneSec = () =>
new Promise(resolve => {
console.log("waiting 1 secoond");
setTimeout(resolve, 1000);
});
waitOneSec()
.then(() => {
waitOneSec(); // Promise not returned
})
.then(() => console.log("done"));
Whereas this one will wait full 2 seconds until logging "done":
const waitOneSec = () =>
new Promise(resolve => {
console.log("waiting 1 secoond");
setTimeout(resolve, 1000);
});
waitOneSec()
.then(() => {
return waitOneSec(); // 🌟 Promise returned
})
.then(() => console.log("done"));
Hope that helps.
I'm building a simple testing site. How it works: A user accepts a task (assignment), sends an answer, which is then pushed to an array called answers.
What I need to do, is check if the answers array in the assignment object is defined. If it is it means user has submitted at least one answer and he's good to submit.
The problem is the async. I'm not sure how to make the code wait for the query to finish.
Here's my code:
export default class FinishAnswerButton extends React.Component {
constructor(props) {
super(props);
this.state = {};
this.handleFinishAssignment = this.handleFinishAssignment.bind(this);
}
async handleFinishAssignment() {
var currentAssignment = firebase.database().ref('Works').child(this.props.assignmentId);
console.log('props points', this.props.points);
var outerThis = this;
await currentAssignment.on('value', snapshot => {
console.log(snapshot.val());
});
var user = firebase.database().ref('Users').child(firebase.auth().currentUser.uid);
await user.once('value', snapshot => {
var acc = snapshot.val();
var points = outerThis.props.points + acc.points;
user.child('points').set(points).catch(e => console.log(e));
// user.child('assignedWork').remove().catch(e => console.log(e));
// currentAssignment.child('state').set('Completed').catch(e => console.log(e));
// currentAssignment.child('finishTime').set(Time.generate()).catch(e => console.log('finishTime', e));
});
return <AssignmentsComponent/>
}
render() {
return (
<Button onClick={this.handleFinishAssignment}>Zakoncz rozwiazywanie zadania (Upewnij
sie ze uczen ma wszystko czego potrzebuje!)</Button>
)
}
}
I tried solving it like this:
async handleFinishAssignment() {
var currentAssignment = firebase.database().ref('Works').child(this.props.assignmentId);
console.log('props points', this.props.points);
var outerThis = this;
currentAssignment.once('value', snapshot => {
console.log(snapshot.val());
return snapshot.val();
}).then(assignment => { // here
console.log(assignment);
var user = firebase.database().ref('Users').child(firebase.auth().currentUser.uid);
if (assignment.answers.length > 0) {
console.log('if');
user.once('value', snapshot => {
var acc = snapshot.val();
var points = outerThis.props.points + acc.points;
user.child('points').set(points).catch(e => console.log(e));
// user.child('assignedWork').remove().catch(e => console.log(e));
// currentAssignment.child('state').set('Completed').catch(e => console.log(e));
// currentAssignment.child('finishTime').set(Time.generate()).catch(e => console.log('finishTime', e));
}).catch(e => console.log(e));
} else
console.log('else');
});
return <AssignmentsComponent/>
}
But it turned out, the assignment passed to the callback is a promise, not the actual snapshot.val(), which is basically what I need in the next part of the code.
Also, why does adding await not solve the issue? I thought the whole idea of await is to make the code act synchronously. Or am I using it wrong?
So to recap: I need to make this code wait. I need to use the response from the await currentAssignment.on('value', snapshot => { to be available in the next query, which is: await user.once('value', snapshot => {. How can I achieve this?
Ok I figured it out. I was on the right track, but got a mental hiccup somewhere on the way. Here's the working code:
async handleFinishAssignment() {
var currentAssignment = firebase.database().ref('Works').child(this.props.assignmentId);
var outerThis = this;
currentAssignment.once('value').then(snapshot => { // Right here
var assignment = snapshot.val();
console.log(assignment);
var user = firebase.database().ref('Users').child(firebase.auth().currentUser.uid);
if (assignment.answers !== undefined) {
console.log('if');
user.once('value', snapshot => {
var acc = snapshot.val();
var points = outerThis.props.points + acc.points;
user.child('points').set(points).catch(e => console.log(e));
// user.child('assignedWork').remove().catch(e => console.log(e));
// currentAssignment.child('state').set('Completed').catch(e => console.log(e));
// currentAssignment.child('finishTime').set(Time.generate()).catch(e => console.log('finishTime', e));
}).catch(e => console.log(e));
} else
console.log('else');
});
return <AssignmentsComponent/>
}
Hey ReactJs Community,
I am fairly new to ReactJs and have gotten my first components set up. Now I'm at a point where I would like to update all state items on a specific event. I am looping each state item via map() and am calling a asynchronous method to determine a value to be included in the state.
This method is returning GET data via the callback function.
updateItems: function() {
var items = this.state.items;
items.map(function(item, i) {
checkConfirmation(item.hash, function(confirmations) {
console.log("Item " + i + " has " + confirmations + " confirmations!");
items[i].hash = item.hash + " (" + confirmations + ")";
items[i].completed = true;
});
});
}
How can I update my state from the asynchronous callback?
I tried passing in this as the second parameter in map() and I tried calling setState after the map() function, but that cannot work since it will be called before any data is returned in the callback.
Thank you for taking time to help me with this issue!
You can make a promise for each pending request. Await them all using Promise.all. And set state when all requests are done.
updateItems: function() {
const items = this.state.items;
const pending = items.map(item => new Promise(resolve => {
checkConfirmation(item.hash, resolve)
}))
Promise.all(pending)
.then(confirmations => confirmations.map((confirmation, i) => {
const item = items[i]
// copy items mutating state is bad
return Object.assign({}, item, {
completed: true,
hash: `${item.hash}(${confirmation})`
})
}))
.then(items => this.setState({ items })
}
UPD Callbacks hackery
updateItems: function() {
const items = this.state.items;
let confirmations = []
let loaded = 0
const addConfirmation = i => confirmation => {
confirmations[i] = confirmation
loaded++;
// if all were loaded
if(loaded === items.length) allConfirmationsLoaded()
}
const allConfirmationsLoaded = () => {
const newItems = confirmations.map((confirmation, i) => {
const item = items[i]
// copy items mutating state is bad
return Object.assign({}, item, {
completed: true,
hash: `${item.hash}(${confirmation})`
})
})
this.setState({items: newItems})
}
// for each item lauch checkConfirmation
items.forEach((item, i) => {
checkConfirmation(item.hash, addConfirmation(i))
})
}