I'm using React-redux firestore for my application. I have a form, on submit ,passing the details to be saved and a function to execute success message if the firestore update is a success. receiving the following error
'Error adding document: TypeError: func is not a function'
Any help appreciated
submit function
handleSubmit = (e) => {
e.preventDefault();
let uid = this.props.auth.uid;
const { sp_License } = this.state;
let err = this.validate();
if (!err) {
this.setState({ loading: true,disChecked:false })
const Lfilename = this.state.sp_Name + '_' + new Date().getTime();
const uploadTask = storage.ref('License/' + Lfilename).put(sp_License);
uploadTask
.then(uploadTaskSnapshot => {
return uploadTaskSnapshot.ref.getDownloadURL();
})
.then(url => {
this.setState({ sp_License: url });
const stateObj = this.state;
this.props.UpdateUserDetails(uid, stateObj, this.successMessage)
});
}
}
successMessage = () => {
this.setState({
message: 'Registration Successfull.!',
open: true,
loading: false,
});
};
Action Method
export const UpdateUserDetails= (id, droneSPDetails,func) => {
console.log(droneSPDetails)
console.log(func)
return (dispatch, getState, { getFirestore }) => {
const firestore = getFirestore()
firestore.collection('users')
.doc(id)
.set({
...droneSPDetails,
sp_RegisteredOn: new Date(),
sp_Status:"pending",
sp_ActiveFlag:"1",
},{ merge: true })
.then(() => {
func();
dispatch({ type: 'CREATE_DRONESP', droneSPDetails });
})
.catch((error) => {
console.error("Error adding document: ", error);
});
}
}
This might be because of the scoping of this. I suggest you to maintain original reference, declare an extra variable at the beginning of your handleSubmit() to hold current this. Something like this:
handleSubmit = (e) => {
// maintain the reference
const self = this
e.preventDefault();
let uid = this.props.auth.uid;
const { sp_License } = this.state;
let err = this.validate();
if (!err) {
this.setState({ loading: true,disChecked:false })
const Lfilename = this.state.sp_Name + '_' + new Date().getTime();
const uploadTask = storage.ref('License/' + Lfilename).put(sp_License);
uploadTask
.then(uploadTaskSnapshot => {
return uploadTaskSnapshot.ref.getDownloadURL();
})
.then(url => {
this.setState({ sp_License: url });
const stateObj = this.state;
this.props.UpdateUserDetails(uid, stateObj, self.successMessage) // use original reference, `self` instead of `this`
});
}
}
Related
I have this working, when the user goes to this page, this line of code executes:
interface Badge {
id: string;
badge_name: string;
badge_description: string;
img64: string;
}
const [data, setData] = useState([] as any[]);
const [isPending, setisPending] = useState(true);
const [searchTerm, setSearchTerm] = useState("");
const onSearchChange = (e: any) => {
setSearchTerm(e.target.value);
};
const setDataWithImg = useCallback(async (badges: Badge[]) => {
let badgeWithImg: Badge[] = [];
const base64Flag = "data:image/png;base64,";
await Promise.all(
badges.map(async (badge: any) => {
const imgBuffer = badge.img_icon.data;
const imgBase64 = bufferToBase64(imgBuffer);
badge.imgBase64 = `${base64Flag}${imgBase64}`;
badgeWithImg.push(badge);
})
);
setData(badgeWithImg);
}, []);
const loadData = useCallback(async () => {
console.log("loadData");
try {
setisPending(true);
await BadgeService.loadData().then(
(res) => {
setDataWithImg(res.data);
setisPending(false);
},
(error) => {
setisPending(false);
}
);
} catch (err) {
console.log(err);
setisPending(false);
}
}, [setDataWithImg]);
useEffect(() => {
loadData();
}, [loadData]);
It will load the data from BadgeService.loadData and I have this function also that will search data from api, and this code will execute.
const onClickFilter = async (e: any) => {
e.preventDefault();
if (searchTerm === "") {
loadData();
} else {
try {
console.log("filterData");
setisPending(true);
await BadgeService.filterData({
badge_name: searchTerm,
}).then(
(res) => {
setDataWithImg(res.data);
setisPending(false);
},
(error) => {
setisPending(false);
}
);
} catch (err) {
console.log(err);
setisPending(false);
}
}
};
User has a search function and that code will execute, search function works fine, I want when user click filter with empty value in search, it will load the original loadData. I already tried console.log('Loaddata') to trace if my condition is working fine and it is ok, but when I check the network, it still executing the api call from filterData, not loadData
First load of the page:
When user fires search function:
Where user fires search function but empty search term:
Base on network logs:
The last request should be the badges only, not the with parameters..How I fix this? What Am I missing here?
Thank you!
Looking at your code, your if check in onClickFilter function seems wrong.
you can do something like
if(!searchTerm){
loadData();
}
Doing !searchTerm will return true for every "falsy" value (empty string, 0, null, false, undefined, NaN) whereas x == "" will only return true if x is null (or apparently undefined).
Please let me know if it works.
I got may issue fix by doing this,
I have this code in my service:
const loadData = () => {
config["params"] = {};
return axios.get(API_URL + "api/v1/badges", config).then((response) => {
//console.log("from loaddata..");
//console.log("load data", response);
return response;
});
};
const filterData = (data: any) => {
config["params"] = {
s: data,
};
return axios.get(API_URL + "api/v1/badges", config).then((response) => {
console.log("from filterdata..");
console.log("filter data", response);
return response;
})
;
I just added config["params"] = {}; this line of code to loadData
Thank you all!
I have a function that fires onClick and is supposed to save an array of states to Firebase. There are two states that can fail due to async, so I tried to chain it all in Promises. The Promises are definitely resolving correctly, but the useState hook didn't update the state in resolve when I tried it. So I tried to chain it in the manner below and the same error (Function CollectionReference.doc() cannot be called with an empty path.) is still returning. Why would my states for mid and lastUpdated be empty?
const db = firebaseApp.firestore();
const initialFieldValues = {
mid: "",
lastUpdated: "",
};
const [store, setStore] = useState(initialFieldValues);
const createMID = () => {
return new Promise((resolve, reject) => {
let docID = db.collection("merchant").doc().id;
let key = "mid-".concat(docID);
if (key) {
resolve(key);
} else {
reject("failed to create mid key");
}
});
};
const getTimestamp = () => {
return new Promise((resolve, reject) => {
let timestamp = firebase.firestore.FieldValue.serverTimestamp();
if (timestamp) {
resolve(timestamp);
} else {
reject("failed to get timestamp");
}
});
};
const registerMerchant = () => {
createMID()
.then(
(data) => {
setStore({
...store,
mid: data,
});
console.log(data); //correctly returns data
console.log(store.mid); //returns empty; setStore isn't setting the state
return getTimestamp();
}).then(
(data2) => {
setStore({
...store,
lastUpdated: data2,
})
console.log(data2); //returns object
console.log(store.mid); //returns empty
}
).then(
() => {
if(!merchant && authenticated){
dispatch({
type: "SET_MERCHANT",
merchant: store,
});
} else if(merchant) {
return
}else{
dispatch({
type: "SET_MERCHANT",
merchant: null,
});
}
}
).then( ()=> {
// e.preventDefault();
db.collection('merchant').doc(store.mid).set(store);
}).catch(error => alert(error.message))
}
Here is the code:
const onStartRecord = async() => {
try {
const path = Platform.select({
ios: `file:///audio/${filenameGenerator}.m4a`,
android: `file:///audio/${filenameGenerator}.mp4`,
});
const audioSet: AudioSet = {
AudioEncoderAndroid: AudioEncoderAndroidType.AAC,
AudioSourceAndroid: AudioSourceAndroidType.MIC,
AVEncoderAudioQualityKeyIOS: AVEncoderAudioQualityIOSType.high,
AVNumberOfChannelsKeyIOS: 2,
AVFormatIDKeyIOS: AVEncodingOption.aac,
};
console.log('audioSet', audioSet);
const uri = await audioRecorderPlayer.startRecorder(path, audioSet);
audioRecorderPlayer.addRecordBackListener((e: any) => {
setAudioProp(audioProp => {
return { ...audioProp,
recordSecs: e.current_position,
recordTime: audioRecorderPlayer.mmssss(Math.floor(e.current_position)),
}
});
});
console.log(`uri: ${uri}`);
return uri
} catch (err) {
console.log(err);
return;
}
};
const audioPath = async() => {
const result = await onStartRecord();
return result;
}
const onSubmit = async() => {
const audiopath = await audioPath();
console.log("this is the audiopath", audiopath)
}
};
I can get what I want when I trigger the onSubmit function, but the problem is, it also trigger the onStartRecord function again which will cause error in my case, I just want to get the uri generated when the onStartRecord resolved, but I don't want to trigger it again, so what can I do if I need to use the onSubmit function and get the value from onStartRecord? thx !
Instead of returning uri, onStartRecord should assign it to a global variable.
Then audioPath() can return that variable.
let savedAudioPath;
const onStartRecord = async() => {
try {
const path = Platform.select({
ios: `file:///audio/${filenameGenerator}.m4a`,
android: `file:///audio/${filenameGenerator}.mp4`,
});
const audioSet: AudioSet = {
AudioEncoderAndroid: AudioEncoderAndroidType.AAC,
AudioSourceAndroid: AudioSourceAndroidType.MIC,
AVEncoderAudioQualityKeyIOS: AVEncoderAudioQualityIOSType.high,
AVNumberOfChannelsKeyIOS: 2,
AVFormatIDKeyIOS: AVEncodingOption.aac,
};
console.log('audioSet', audioSet);
const uri = await audioRecorderPlayer.startRecorder(path, audioSet);
audioRecorderPlayer.addRecordBackListener((e: any) => {
setAudioProp(audioProp => {
return { ...audioProp,
recordSecs: e.current_position,
recordTime: audioRecorderPlayer.mmssss(Math.floor(e.current_position)),
}
});
});
console.log(`uri: ${uri}`);
savedAudioPath = uri;
} catch (err) {
console.log(err);
return;
}
};
const audioPath = async () => savedAudioPath;
hi i want to cancel promise on unmount since i received warning,
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
My code:
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
(val) => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
(error) => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
},
};
};
useEffect(() => {
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split('toolbox-talks/');
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log('resolve 1');
setFileUri(filesystemUri);
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log('resolve 2');
setFileUri(imageObject.uri);
}
// otherwise download to cache
} catch (err) {
console.log('error 3');
setFileUri(uri);
}
};
const cancelable = makeCancelable(initialize());
cancelable.promise
.then(() => {
console.log('reslved');
})
.catch((e) => {
console.log('e ', e);
});
return () => {
cancelable.cancel();
};
}, []);
but i still get warning on fast press, help me please?
You're cancelling the promise, but you are not cancelling the axios call or any of the logic that happens after it inside initialize(). So while it is true that the console won't print resolved, setFileUri will be called regardless, which causes your problem.
A solution could look like this (untested):
const makeCancelable = (promise: Promise<void>) => {
let hasCanceled_ = false;
const wrappedPromise = new Promise((resolve, reject) => {
promise.then(
val => (hasCanceled_ ? reject({ isCanceled: true }) : resolve(val)),
error => (hasCanceled_ ? reject({ isCanceled: true }) : reject(error))
);
});
return {
promise: wrappedPromise,
cancel() {
hasCanceled_ = true;
}
};
};
const initialize = async () => {
const getImageFilesystemKey = (remoteUri: string) => {
const [_, fileName] = remoteUri.split("toolbox-talks/");
return `${cacheDirectory}${fileName}`;
};
const filesystemUri = getImageFilesystemKey(uri);
try {
// Use the cached image if it exists
const metadata = await getInfoAsync(filesystemUri);
if (metadata.exists) {
console.log("resolve 1");
return filesystemUri;
} else {
const imageObject = await downloadAsync(uri, filesystemUri);
console.log("resolve 2");
return imageObject.uri;
}
// otherwise download to cache
} catch (err) {
console.error("error 3", err);
return uri;
}
};
useEffect(() => {
const cancelable = makeCancelable(initialize());
cancelable.promise.then(
fileURI => {
console.log("resolved");
setFileUri(fileURI);
},
() => {
// Your logic is such that it's only possible to get here if the promise is cancelled
console.log("cancelled");
}
);
return () => {
cancelable.cancel();
};
}, []);
This ensures that you will only call setFileUri if the promise is not cancelled (I did not check the logic of makeCancelable).
I use React Redux and I create a function to login, but I need to get a callback return after successfull login and redirect user to a page.
I try to passing function as parameter but not working.
How can I get the return after dispatch action?
Login fun
export const login = (request,cb) => {
return dispatch => {
let url = "/api/user/login";
axios({
method: "post",
url: url,
data: request,
config: { headers: { "Content-Type": "multipart/form-data" } }
})
.then(response => {
let authState = {
isLoggedIn: true,
user: response.data
};
cb();
window.localStorage["authState"] = JSON.stringify(authState);
return dispatch({
type: "USER_LOGIN_FULFILLED",
payload: { userAuthData: response.data }
});
})
.catch(err => {
return dispatch({
type: "USER_LOGIN_REJECTED",
payload: err
});
});
};
};
submiting
handleLogin(e) {
this.setState({ showLoader: true });
e.preventDefault();
const request = new Object();
if (this.validator.allValid()) {
request.email = this.state.email;
request.password = this.state.password;
this.props.login(request, () => {
//get callbach here
this.props.history.push('/my-space/my_views');
})
this.setState({ showLoader: false });
} else {
this.setState({ showLoader: false });
this.validator.showMessages();
this.forceUpdate();
}
}
const mapStateToProps = state => {
return {
authState: state
};
};
const mapDispatchToProps = dispatch => {
return {
login: request => dispatch(login(request))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
The cb is missing in your connect(...)
Here is the fix
handleLogin(e) {
this.setState({ showLoader: true });
e.preventDefault();
const request = new Object();
if (this.validator.allValid()) {
request.email = this.state.email;
request.password = this.state.password;
this.props.login(request, () => {
//get callbach here
this.props.history.push('/my-space/my_views');
})
this.setState({ showLoader: false });
} else {
this.setState({ showLoader: false });
this.validator.showMessages();
this.forceUpdate();
}
}
const mapStateToProps = state => {
return {
authState: state
};
};
const mapDispatchToProps = dispatch => {
return {
login: (request, cb) => dispatch(login(request, cb))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm);
Hope it helps:)
If you are using redux-thunk, you can return a Promise from your async action.
The function called by the thunk middleware can return a value,
that is passed on as the return value of the dispatch method.
In this case, we return a promise to wait for.
This is not required by thunk middleware, but it is convenient for us.
But I prefer use useEffect or componentDidUpdate for this purpose:
componentDidUpdate(){
if(this.props.authState.isLoggedIn){
this.props.history.push('/my-space/my_views');
}
}
I recommend using the Redux Cool package if you need actions with callback capability.
Instalation
npm install redux-cool
Usage
import {actionsCreator} from "redux-cool"
const my_callback = () => {
console.log("Hello, I am callback!!!")
}
const callbackable_action = actionsCreator.CALLBACKABLE.EXAMPLE(1, 2, 3, my_callback)
console.log(callbackable_action)
// {
// type: "CALLBACKABLE/EXAMPLE",
// args: [1, 2, 3],
// cb: f() my_callback,
// _index: 1
// }
callbackable_action.cb()
// "Hello, I am callback!!!"
When we try to generate an action object, we can pass the callback function as the last argument. actionsCreator will check and if the last argument is a function, it will be considered as a callback function.
See Actions Creator for more details
react-redux/redux dispatch returns a promise. you can do this if you want to return a value or identify if the request is success/error after being dispatched
Action example
export const fetchSomething = () => async (dispatch) => {
try {
const response = await fetchFromApi();
dispatch({
type: ACTION_TYPE,
payload: response.value
});
return Promise.resolve(response.value);
} catch (error) {
return Promise.reject(error);
}
}
Usage
const foo = async data => {
const response = new Promise((resolve, reject) => {
dispatch(fetchSomething())
.then(v => resolve(v))
.catch(err => reject(err))
});
await response
.then((v) => navigateToSomewhere("/", { replace: true }))
.catch(err => console.log(err));
};
this post is old, but hopefully it will help
Package.json
"react-redux": "^8.0.2"
"#reduxjs/toolkit": "^1.8.5"