Sometime it's hard to write a good title for you question, btw i have an array of object like this
{"level1": [{"task1": [Array], "task2": [Array], "task3": [Array]}], "level2": [{"task1": [Array]}
and here i'm trying to get a length of each levels and increase the size of step progress everytime when we pass current task
useEffect(() => {
const getTaskLength = levelData.map((levels, i) => {
return setSteps(_.size(levels)); <---- // Get length of level tasks
});
}, [levelData, currentTaskGroupIndex]);
//Play correct answer sound
useDidMountEffect(() => {
async function playCorrectSound() {
const sound = new Audio.Sound();
try {
await sound.loadAsync(require('../assets/correct.mp3'), {
shouldPlay: true,
});
await sound.setPositionAsync(0);
await sound.playAsync();
} catch (error) {
console.error(error);
}
}
isCorrect === true && playCorrectSound();
isCorrect === true && setStep(step + 1); <--- // increase +1 step when user pass task 1 and so on
}, [isCorrect]);
Return(
<Task
step={step}
steps={steps}
height={10}
data={item}
/>
)
But the problem is, whenever I pass 2 or 3 tasks of level 1 and come back to home and press level 2 it's already passed all level 2 tasks,
in my case if level1 is 3 tasks and i pass all -> step 3/3 steps tasks and come back to level 2, and we have 1 or 2 tasks there it'l will show as passed tasks -> step 2/2 steps
Related
I've just learned about recoilJS and have been playing around with it a bit and have a question about whether what I've done is considered "correct." My code works, but it feels weird.
I've got the following React function component:
export const TimerPanel: FC = () => {
const gameState = useRecoilValue<GameState>(HeaderAtoms.gameState);
const timerCount = useRecoilValue(HeaderAtoms.timerCount);
const setTimerCounter = useSetRecoilState(HeaderAtoms.timerCounter);
useEffect(() => {
if (gameState === GameState.IN_PROGRESS && timerCount < 1000) {
window.setTimeout(() => {
setTimerCounter(timerCount + 1);
}, 1000);
}
});
return <NumberPanel num={timerCount} />;
};
where the relevant atom and selector are defined as:
export const timerCount = atom<number>({
key: 'Header.timerCount',
default: 0
});
export const timerCounter = selector<number>({
key: 'Header.timerCounter',
get: ({ get }) => {
return get(timerCount);
},
set: ({ get, set }, newCount) => {
if (get(gameState) === GameState.NEW) {
set(timerCount, 0);
} else if (get(gameState) === GameState.IN_PROGRESS) {
set(timerCount, newCount);
}
}
});
Basically, when the game starts, the TimerPanel increments the timer display by 1 every second the game is in progress. If the user resets the game (GameState.NEW), timerCount resets back to zero. If the atom/selector aren't done properly, there's a race condition in that the game state and timer count will reset, but the timer is still going and will still update the TimerPanel one more time. This is why I have the if blocks in my selector's set prop.
Basically, I'm concerned that my timerCounter selector is a glorified filter/pass-thru entity for the timerCount state and am wondering if there's a better way to handle this use case.
If your concern is the race condition, in your code, I don't see how your timer would click. setTimeout will tick once.
Anyway, you have 2 states, game state and timer state. You want to reset timer when game state changes. How about doing it in a game state selector? or move the logic into a custom hook and operate on these 2 states directly.
I'm trying to implement a counter that stops when it has reached a certain amount, but what I have at the moment doesn't stop. It seems to increment continuously; why is this? Does it have something to do with when or how many times useEffect is executed?
const [counter, setCounter] = useState(0);
useEffect(() => {
if (counter < 10) {
const interval = setInterval(() => {
setCounter(counter => counter + 1);
}, incrementRate);
return () => clearInterval(interval);
}
}, []);
return (
<View>
<Text>Circular progress bar</Text>
<Text>{`${counter}`}</Text>
</View>
);
You're using the useState functional updater inside your effect, which means it will use its previous state to increment the count, which is correct but it gives you a false perception that the effect is working correctly. Your mental model for how useEffect works needs to a shift a little, it's really common, so much so Dan Abramov has written an extensive article on useEffect with this exact scenario.
The quick fix is to add counter to your dependency array, so that you can tell React its changed, so it will no longer skip updating the effect.
useEffect(() => {
if (counter < 10) {
const interval = setInterval(() => {
setCounter((counter) => counter + 1);
}, incrementRate);
return () => {
clearInterval(interval);
};
}
}, [counter]);
It's not the cleanest approach as you'll clear the interval and create a new one each time the effect runs.
You could however use setTimeout instead and avoid the clearing of intervals altogether.
useEffect(() => {
if (counter < 10) {
setTimeout(() => {
setCounter((counter) => counter + 1);
}, incrementRate);
}
}, [counter]);
I’m trying to update the notification count in my database.
I’m doing this by creating a set, which I add a UID to when I want to add to the notification count and removes a UID from the set when I want to subtract from the notification count.
I then take the size of the set and update the notification count.
the updateNotificationCount function is triggered by a lower order component.
However I can only get the database to update when isNewMatch is true. Why won’t it update the database when isNewMatch is false?
state = {notificationSet: new Set()}
updateNotificationCount = (uid, isNewMatch) => {
if (isNewMatch) {
this.setState(({ notificationSet }) => ({
notificationSet: new Set(notificationSet).add(uid)
}));
}
else {
this.setState(({ notificationSet }) => {
const newNotificationSet = new Set(notificationSet);
newNotificationSet.delete(uid);
return {
notificationSet: newNotificationSet
};
});
};
}
You don't need to do new Set() every time because you already initialize the state with new Set() so now you just do as follow:
state = {notificationSet: new Set()}
updateNotificationCount = (uid, isNewMatch) => {
let notificationSet;
if (isNewMatch) {
notificationSet=this.state.notificationSet;
notificationSet.add(uid);
this.setState({
notificationSet: notificationSet
});
} else {
notificationSet=this.state.notificationSet;
notificationSet.delete(uid);
this.setState({
notificationSet : notificationSet
});
};
}
I'm trying to paginate my data from firebase realtime database.
Do I have to change to firestore ? Where all is explain in Google's doc (https://firebase.google.com/docs/firestore/query-data/query-cursors) or it's also possible with rtdb ?
Here is my code (i'm using vue js) :
loadConcerts ({commit}) {
commit('setLoading', true)
firebase.database().ref('concerts')
.orderByChild('expires')
.startAt(Date.now() / 1e3)
.limitToFirst(10)
.once('value')
.then(data => {
const concerts = []
data.forEach(element => {
concerts.push({
id: element.key,
title: element.val().title,
day: element.val().day,
ticketlink: element.val().ticketlink,
description: element.val().descriptio
})
})
commit('setLoadedConcerts', concerts)
commit('setLoading', false)
})
.catch(
(error) => {
console.log(error)
commit('setLoading', false)
}
)
},
I would like to add pagination after 10 results, or infinite scrolling.
I have also had similar problem with pagination. The documentation seems to be insufficient i.e they show you how to go to next page but not how to move back to the previous page. Its just frustrating really.
I am using firestore
Below is how i implemented a simple pagination. I have already configured VueFire , Firebase and BootstrapVue i'll head straight to the code.
What to do different that no one shows you.
Use VueFire programmatic binding instead of declarative binding see here
To get firstVisible item in firebase run documentSnapshots.docs[0]
<template>
<div>
<p>{{countries}}</p>
<b-button-group size="lg" class="mx-2">
<b-button :disabled="prev_btn" #click="previous" >«</b-button>
<b-button :disabled="next_btn" #click="next">»</b-button>
</b-button-group>
</div>
</template>
<script>
import firebase from 'firebase/app'
import 'firebase/auth'
import { db } from '../main'
export default {
name: 'Countries',
data () {
return {
countries: [],
limit: 2,
lastVisible: '',
firstVisible: '',
next_btn: false,
prev_btn: true
}
},
methods: {
next () {
if (!this.next_btn) {
// bind data with countries
this.$bind('countries', db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit))
// set last and first visible items
db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit).get().then(documentSnapshots => {
this.lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1]
this.firstVisible = documentSnapshots.docs[0]
}).then(() => {
// Peep on the next next query to see if it gives zero
db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit).get()
.then(snap => {
if (snap.size === 0) {
//disable button if the next peeped result gets zero
this.next_btn = true
// enable previous button
this.prev_btn = false
} else {
// enable next button if peeped result is not zero
this.next_btn = false
// enable previous button
this.prev_btn = false
}
})
})
}
},
previous () {
// Ensure previous is not zero
db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit).get().then(snap => { return snap.size })
.then(size => {
//confirm is not zero here
if (size !== 0) {
//bind the previous to countries
this.$bind('countries', db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit))
// Set last and first visible
db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit).get().then(documentSnapshots => {
this.lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1]
this.firstVisible = documentSnapshots.docs[0]
}).then(() => {
// peep the next previous query
db.collection('Countries').orderBy('createdAt').endBefore(this.firstVisible).limitToLast(this.limit).get()
.then(snap => {
if (snap.size === 0) {
//if next peeped previous button gets 0 disable
this.prev_btn = true
this.next_btn = false
} else {
//if next peeped result is does not get 0 enable buttons
this.prev_btn = false
this.next_btn = false
}
})
})
}
})
}
},
mounted () {
// run first query and bind data
this.$bind('countries', db.collection('Countries').orderBy('createdAt').limit(this.limit))
// set last and first Visible
db.collection('Countries').orderBy('createdAt').limit(this.limit).get().then(documentSnapshots => {
this.lastVisible = documentSnapshots.docs[documentSnapshots.docs.length - 1]
this.firstVisible = documentSnapshots.docs[0]
}).then(() => {
// peep to check if next should be on or off
db.collection('Countries').orderBy('createdAt').startAfter(this.lastVisible).limit(this.limit).get()
.then(snap => {
if (snap.size === 0) {
this.next_btn = true
}
})
})
}
}
</script>
We jumped into Vue, and totally loved the idea of a store, separating page state from presentation. We wrote a zippy alert component, that displayed whatever alert string was in store.pageError.
Then we wanted to add confirm(), so we gave our component a ref, and a popConfirm() method that returned a promise, and whenever we wanted to confirm something, we called...
vm.$refs.alertConfirm.confirm("Are you sure?")
.then(function(conf){if(conf) /* do it */ })
...and the component became responsible for managing its own visibility, without anything happening in the store.
This worked so well that soon lots of components started sprouting refs, so they could be called directly as methods. In the latest incarnation, we implemented a tunnel with six steps, with api calls and user actions called in parallel with Promise.all(), and it works great, but the store has taken a big step back, and more and more state is being managed directly in Vue components. These components are no longer dumb representations of store state, but increasingly little functional sequences with state managed internally.
How do we reassert the idea of a store, while keeping the convenience of calling these short functional sequences as methods?
Here is our tunnel code. This currently lives in the methods of a Vue component, where markup, state, and sequential logic are joyously mixed. This can't be good?
startTunnel(idNote) {
var rslt = {
idNote: idNote,
photoJustif: null,
date: null,
currency: "",
montant: null
}
//---------------Step 1: photo et count notes in parallel
Promise.all([
me.$refs.camera.click(),
getUrlAsJson("api/note/getNotes"),
])
//---------------Step 2: Choose note if > 1
.then(function (results) {
rslt.photoJustif = results[0];
me.$refs.loader.hide();
// if we already know the note, go straight to Step 3.
if (rslt.idNote)
return true;
// if no idNote supplied, and only one returned from server, assign it.
if (results[1].notes.length === 1 && !rslt.idNote) {
rslt.idNote = results[1].notes[0].idNote;
return true;
}
else {
return me.$refs.chooseNote.choose(results[1].notes)
// combine photoJustif from Step 1 with idNote chosen just above.
.then(function (idNoteChosen) { rslt.idNote = idNoteChosen; return true })
}
})
//--------------Step 3: OCR
.then(() => me.doOcr(rslt))
//--------------Step 4: Choose nature and retrieve card statement from server in parallel
.then(function (ocrResult) {
me.$refs.loader.hide()
if (ocrResult != null) { //Si ocr n'a pas échoué
rslt.date = ocrResult.date;
rslt.montant = ocrResult.montant;
rslt.currency = ocrResult.currency;
return Promise.all([
me.$refs.chooseNature.init(rslt.idNote, ocrResult.grpNatures),
getUrlAsJson("api/expense/relevecarte/filterpers", { IdPerson: 1, montant: ocrResult.montant })
]);
}
else return null;
})
//--------------Step 5: Choose card transaction
.then(function (natureAndFraisCartes) {
if (natureAndFraisCartes != null) {
rslt.idNature = natureAndFraisCartes[0].id;
if (rslt.montant != null && natureAndFraisCartes[1].length > 1)
return me.$refs.choixFraisCarte.init(rslt, natureAndFraisCartes[1]);
else
return null;
}
else return null;
})
//------------- Step 6: End tunnel
.then(function (fraisCarte) {
me.$refs.loader.popInstant();
me.$refs.form.idNote.value = rslt.idNote;
var jsonObject;
if (fraisCarte != null) {
me.$refs.form.action.value = 15;
jsonObject = {
"DateFrais": rslt.date,
"IdNature": rslt.idNature,
"MontantTicket": rslt.montant,
"Justificatif": rslt.photoJustif,
"idCarte": fraisCarte.id
};
}
else {
me.$refs.form.action.value = 14;
jsonObject = {
"DateFrais": rslt.date,
"IdNature": rslt.idNature,
"MontantTicket": rslt.montant,
"Justificatif": rslt.photoJustif,
"idCarte": 0
};
}
me.$refs.form.obj.value = JSON.stringify(jsonObject);
me.$refs.form.submit();
})
.catch(function (error) {
me.$refs.loader.hide();
me.active = false;
me.rslt = {
idNote: idNote,
photoJustif: null,
date: null,
montant: null
};
console.log(error);
vueStore.pageError = me.allStrings.tunnelPhoto.erreurTunnel;
})
}
It looks like the problem is that you got away from thinking declaratively and went to thinking imperatively. When you want to confirm something, you should set a confirmPrompt data item, and the component should be watching it in much the same way it watches the alert string.
There should be a data item for the confirmation response to indicate whether you're waiting for a response, or it was confirmed or it was canceled. It's all program state.
Using $refs is a code smell. It's not always wrong, but you should always think about why you're doing it. Things like me.$refs.loader.hide(); suggest program state changes that should be controlled by setting a loaderIsVisible data item, for example.