I have a custom store that works with synchronous localStorage. I would like to use capacitorjs' preferences plugin but it is asynchronous. My code looks like this:
useStorage.js
const getItem = async (key) => {
const { value } = await Preferences.get({ key: key });
return value;
};
const setItem = async (key, value) => {
await Preferences.set({
key,
value
});
};
export function localStore(key, defaultValue) {
let serialize = JSON.stringify;
let deserialize = JSON.parse;
let storedValue;
const { subscribe, update, set } = writable(storedValue || defaultValue);
subscribe((value) => setItem(key, serialize(value)));
return {
subscribe,
async function() {
const item = deserialize(await getItem(key));
storedValue = item;
},
set
};
}
stores.js
export const name = localStore('name', 'your name');
Now what happens is updating $name to another value also updates it on localStorage. But when I reload, it reverts back to the default value since it takes time to get the storedValue. Does anyone know a workaround?
Related
I have a POST API call that I make on a button click. We have one large state object that gets sent as body for a POST call. This state object keeps getting updated based on different user interactions on the page.
function QuotePreview(props) {
const [quoteDetails, setQuoteDetails] = useState({});
const [loadingCreateQuote, setLoadingCreateQuote] = useState(false);
useEffect(() => {
if(apiResponse?.content?.quotePreview?.quoteDetails) {
setQuoteDetails(apiResponse?.content?.quotePreview?.quoteDetails);
}
}, [apiResponse]);
const onGridUpdate = (data) => {
let subTotal = data.reduce((subTotal, {extendedPrice}) => subTotal + extendedPrice, 0);
subTotal = Math.round((subTotal + Number.EPSILON) * 100) / 100
setQuoteDetails((previousQuoteDetails) => ({
...previousQuoteDetails,
subTotal: subTotal,
Currency: currencySymbol,
items: data,
}));
};
const createQuote = async () => {
try {
setLoadingCreateQuote(true);
const result = await usPost(componentProp.quickQuoteEndpoint, quoteDetails);
if (result.data?.content) {
/** TODO: next steps with quoteId & confirmationId */
console.log(result.data.content);
}
return result.data;
} catch( error ) {
return error;
} finally {
setLoadingCreateQuote(false);
}
};
const handleQuickQuote = useCallback(createQuote, [quoteDetails, loadingCreateQuote]);
const handleQuickQuoteWithoutDeals = (e) => {
e.preventDefault();
// remove deal if present
if (quoteDetails.hasOwnProperty("deal")) {
delete quoteDetails.deal;
}
handleQuickQuote();
}
const generalInfoChange = (generalInformation) =>{
setQuoteDetails((previousQuoteDetails) => (
{
...previousQuoteDetails,
tier: generalInformation.tier,
}
));
}
const endUserInfoChange = (endUserlInformation) =>{
setQuoteDetails((previousQuoteDetails) => (
{
...previousQuoteDetails,
endUser: endUserlInformation,
}
));
}
return (
<div className="cmp-quote-preview">
{/* child components [handleQuickQuote will be passed down] */}
</div>
);
}
when the handleQuickQuoteWithoutDeals function gets called, I am deleting a key from the object. But I would like to immediately call the API with the updated object. I am deleting the deal key directly here, but if I do it in an immutable way, the following API call is not considering the updated object but the previous one.
The only way I found around this was to introduce a new state and update it on click and then make use of the useEffect hook to track this state to make the API call when it changes. With this approach, it works in a weird way where it keeps calling the API on initial load as well and other weird behavior.
Is there a cleaner way to do this?
It's not clear how any children would call the handleQuickQuote callback, but if you are needing to close over in callback scope a "copy" of the quoteDetails details then I suggest the following small refactor to allow this parent component to use the raw createQuote function while children receive a memoized callback with the current quoteDetails enclosed.
Consume quoteDetails as an argument:
const createQuote = async (quoteDetails) => {
try {
setLoadingCreateQuote(true);
const result = await usPost(componentProp.quickQuoteEndpoint, quoteDetails);
if (result.data?.content) {
/** TODO: next steps with quoteId & confirmationId */
console.log(result.data.content);
}
return result.data;
} catch( error ) {
return error;
} finally {
setLoadingCreateQuote(false);
}
};
Memoize an "anonymous" callback that passes in the quoteDetails value:
const handleQuickQuote = useCallback(
() => createQuote(quoteDetails),
[quoteDetails]
);
Create a shallow copy of quoteDetails, delete the property, and call createQuote:
const handleQuickQuoteWithoutDeals = (e) => {
e.preventDefault();
const quoteDetailsCopy = { ...quoteDetails };
// remove deal if present
if (quoteDetailsCopy.hasOwnProperty("deal")) {
delete quoteDetailsCopy.deal;
}
createQuote(quoteDetailsCopy);
}
I have this function:
export const changeNotes = (userId, note, isStay)
I want to use to change a note, and sometimes I want to use it to change the value of isStay.
const handleOnChange = async e => {
await changeNotes(user.id,null ,true) // await changeNotes(user.id,10 ,null)
}
How do I send different cases?
You can pass an object as an argument to your changeNote function, and then use function argument destructuring to use properties from it. This way, order doesn't matter and only the values you send will be used (anything you don't pass will be undefined).
export const changeNotes = ({ userId, note, isStay }) => {
if (note) { // undefined
}
if (isStay) { // true
// do something
}
}
const handleOnChange = async e => {
await changeNotes({
userId: user.id,
isStay: true
})
}
I'm developing an app using React Native and Firebase RealTime database.
I want to do the following. I want to create a data tree like following.
myRoot
|
|---myKey: "myValue"
But here, I DON'T want to do it as follows.
foo = async () => {
let myRef = firebase.database().ref('myRoot');
await myRef.set({
myKey: "myValue"
})
}
Instead, I want to do something like this.
writeData = async (key, value) => {
let myRef = firebase.database().ref('myRoot');
await myRef.set({
key: value
})
}
And, then call the function to add data.
this.writeData(myKey, myValue);
For example, after I called the function as below,
this.writeData("myFirstKey", "myFirstValue");
this.writeData("mySecondKey", "mySecondValue");
this.writeData("myThirdKey", "myThirdValue");
I need the output as below.
myRoot
|
|---myFirstKey: "myFirstValue"
|---mySecondKey: "mySecondValue"
|---myThirdKey: "myThirdValue"
How to do this?
Calling set on a given object will replace it completely instead of set you can use update function like this
writeData = async (key, value) => {
let myRef = firebase.database().ref('myRoot');
await myRef.update({
key:value
})
}
Yes.. I got the point..
writeData = async (key, value) => {
let myRef = firebase.database().ref('myRoot');
await myRef.child(key).set(value);
}
I am trying to save the name that a user enters into a TextInput such that every proceeding time a user opens the app their name will still be saved.
I am trying to use react-native's Asynchronous storage to get the name inside the componentDidMount() function. I believe that the value is not being set inside the onSubmit() method when it is being called. I tried removing the await keyword in the async function. I read other the documentation and can't find where I am going wrong.
onSubmit = () => {
var that = this
var data = this.state.textValue;
nameDict['name'] = data;
_setData = async () => {
try {
const value = await AsyncStorage.setItem('name',data)
that.setState({textValue:'',name:data})
} catch (error) {
that.setState({textValue:'',name:'error'})
}
}
}
componentDidMount(){
var that = this;
var name = ''
this.watchId = navigator.geolocation.watchPosition((position)=>{
_retrieveData = async () => {
try {
const value = await AsyncStorage.getItem('name')
name = value
} catch (error) {
// Error retrieving data
}
}
}
The name variable in ComponentDidMount() is always an empty string, so either it has not been changed at all or the getItem() function is not resolving and returning the name. However, I am not sure which of the two it could be. Am I using the Async functions incorrectly?
you have to set set componentDidMount to async also
example using your data:
async componentDidMount(){
var that = this;
var name = ''
this.watchId = navigator.geolocation.watchPosition((position)=>{
_retrieveData = async () => {
try {
await AsyncStorage.getItem('name').then((value)=>name=value);
} catch (error) {
// Error retrieving data
}
}
}
I recommend making name global if you want to actually use the value.
next, this might be personal but i recommend to JSON.stringify() the values when storing data and JSON.parse() when retrieving the data
example when retrieving stringify data
async componentDidMount(){
var that = this;
var name = ''
this.watchId = navigator.geolocation.watchPosition((position)=>{
_retrieveData = async () => {
try {
await AsyncStorage.getItem('name').then((value)=>name=JSON.parse(value));
} catch (error) {
// Error retrieving data
}
}
}
if this was helpful please set as the answer if not please leave a comment.
I have an object of convos with userIDs that I need to loop through and, inside the loop, I need to make a call to Firebase to get the corresponding userName and then return an object with the convos, userNames, and userIDs.
I have tried using the async/await and the result I get from console.log is correct but my return statement directly after that, is undefined. Why is this happening? They are receiving the same object.
store.js getter snippet
getConvosObj: state => {
var convoObj = {};
var userConvos = state.userProfile.convos;
async function asyncFunction() {
for (const key in userConvos) {
if (userConvos.hasOwnProperty(key)) {
const userID = userConvos[key];
var userName;
await fire.database().ref('/users/' + userID + '/userName').once('value', async (snapshot) => {
userName = await snapshot.val();
convoObj[key] = {userName, userID}
})
}
}
console.log(convoObj); //result: correct object
return convoObj; //result: undefined
}
asyncFunction();
}
Why is this happening ?
Because you called async function synchronously.
lets make your code simpler.
getConvosObj: state => {
async function asyncFunction() {
// ...
}
asyncFunction();
}
at this point, your getConvosObj() will return nothing, because getConvosObj() ends before asyncFunction() ends.
you need to wait until your asyncFunction() ends, then your code should be like this:
getConvosObj: async state => { // <- changed here
async function asyncFunction() {
// ...
}
await asyncFunction(); // <- changed here too
}
but you should not do like this, because getters are not meant to be asynchronous by design.
this may work, but you should try a different approach.
So what should you do ?
Use actions before using getters
this is a basic approach.
async functions should be in actions.
so your store should be like this:
export default () =>
new Vuex.Store({
state: {
convoObj: null
},
mutations: {
updateConvoObj(state, payload) {
state.convoObj = payload;
}
},
actions: {
async fetchAndUpdateConvoObj({ state, commit }) {
const fetchUserData = async userId => {
const snapShot = await fire.database().ref('/users/' + userID + '/userName').once('value');
const userName = snapShot.val();
return {
userName: userName,
userID: userId
}
}
const userConvos = state.userProfile.convos;
let convoObj = {};
for (const key in userConvos) {
if (userConvos.hasOwnProperty(key)) {
const userId = userConvos[key];
const result = await fetchUserData(userId);
convoObj[key] = {
userName: result.userName,
userId: result.userId
}
}
}
commit('updateConvoObj', convoObj);
}
}
});
then call your actions before using getter in your sample.vue:
await this.$store.dispatch('fetchAndUpdateConvoObj');
convoObj = this.$store.getters('getConvoObj');
wait for db and update store, then get its state.
doesnt make sense ?
Use vuexfire to connect your store directly to Realtime Database
another approach is this.
use vuexfire, then the state of the store is always up-to-date to realtime database, so you can call getters without calling actions.
i got tired to refactor / write a code, so google some sample if you wanna use that plugin :)
i refactored the original code a lot, so there should be some typo or mistake.
plz revise is if you find one.