Async function undefined despite binding - javascript

I'm struggling to figure out why the function storeEmailVerified() in my React Native code below returns undefined. Here is the error message:
that.storeEmailVerified is not a function. (In
'that.storeEmailVerified("true")', 'that.storeEmailVerified' is
undefined)
I have bound both of the following functions to 'this' in the constructor.
constructor(props) {
super(props);
this.storeEmailVerified = this.storeEmailVerified.bind(this);
this.checkEmailVerified = this.checkEmailVerified.bind(this);
}
checkEmailVerified = async () => {
let currentUser = firebase.auth().currentUser.uid;
firebase.database().ref().child("/users").child(currentUser).on("value", function (snapshot) {
if (snapshot.exists()) {
const emailVerified = snapshot.child("emailVerified").val();
if (emailVerified == true) {
this.setState({
emailVerified: true
})
this.storeEmailVerified("true");
// AsyncStorage.setItem("emailVerified", true)
}
else {
this.setState({
emailVerified: false
})
this.storeEmailVerified("false");
// AsyncStorage.setItem("emailVerified", false)
}
}
});
};
storeEmailVerified = async status => {
try {
await AsyncStorage.setItem("emailVerified", status)
} catch (error) {
alert(error)
return 'error'
}
}

Related

Vuex: Wait for websocket response before dispatching action

So this is the scenario / premises:
In order to populate a chat queue in real time I need to open a connection to a websocket, send a message and then set the data to a websocket store. This store will basically manage all the websocket state.
Before populating the chat queue there's two parameters I need: a shiftId coming from one http API request and a connectionId coming from the websocket. Using those two parameters I finally can subscribe to a third http API and start receiving messages to populate the chat queue.
The problem is that due to the async behaviour of the websocket (or that's what I think, please feel to correct me if I'm wrong) I always get an empty "connectionId" when trying to make the put to that "subscription" API. I have tried with async/await and promises but nothing seems to work. I'm pretty new to async/await and websockets with Vuex so pretty sure I'm doing something wrong.
This is the user vuex module where I do all the login/token operations and dispatch a "updateEventsSubscription" action from the shift vuex module. In order for the "updateEventsSubscription" action to work I need to get the response from the "processWebsocket" action (to get the connectionId parameter) and from the "startShift" action (to get the shiftId parameter) coming from the shifts vuex module:
import UserService from '#/services/UserService.js'
import TokenService from '#/services/TokenService.js'
import router from '#/router'
export const namespaced = true
export const state = {
accessToken: '',
errorMessage: '',
errorState: false,
userEmail: localStorage.getItem('userEmail'),
userPassword: localStorage.getItem('userPassword'),
}
export const mutations = {
SET_TOKEN(state, accessToken) {
state.accessToken = accessToken
TokenService.saveToken(accessToken)
},
SET_USER(state, authUserJson) {
state.userEmail = authUserJson.email
state.userPassword = authUserJson.password
localStorage.setItem('userPassword', authUserJson.password)
localStorage.setItem('userEmail', authUserJson.email)
},
SET_ERROR(state, error) {
state.errorState = true
state.errorMessage = error.data.error_description
},
CLOSE_NOTIFICATION(state, newErrorState) {
state.errorState = newErrorState
},
}
export const actions = {
signIn({ commit, dispatch, rootState }, authUserJson) {
return UserService.authUser(authUserJson)
.then((result) => {
commit('SET_USER', authUserJson)
commit('SET_TOKEN', result.data.access_token)
dispatch('token/decodeToken', result.data.access_token, {
root: true,
})
dispatch(
'shifts/updateEventsSubscription',
rootState.token.agentId,
{
root: true,
}
)
router.push('/support')
})
.catch((error) => {
console.log(error)
if (error.response.status === 400) {
commit('SET_TOKEN', null)
commit('SET_USER', {})
commit('SET_ERROR', error.response)
} else {
console.log(error.response)
}
})
},
signOut({ commit }) {
commit('SET_TOKEN', null)
commit('SET_USER', {})
localStorage.removeItem('userPassword')
localStorage.removeItem('userEmail')
TokenService.removeToken()
router.push('/')
},
closeNotification({ commit }, newErrorState) {
commit('CLOSE_NOTIFICATION', newErrorState)
},
}
export const getters = {
getToken: (state) => {
return state.accessToken
},
errorState: (state) => {
return state.errorState
},
errorMessage: (state) => {
return state.errorMessage
},
isAuthenticated: (state) => {
return state.accessToken
},
userEmail: (state) => {
return state.userEmail
},
userPassword: (state) => {
return state.userPassword
},
}
This is websocket store: I pass the connectionId to the state in order to be able to use it in another vuex action to subscribe for new chats:
export const namespaced = true
export const state = {
connected: false,
error: null,
connectionId: '',
statusCode: '',
incomingChatInfo: [],
remoteMessage: [],
messageType: '',
ws: null,
}
export const actions = {
processWebsocket({ commit }) {
const v = this
this.ws = new WebSocket('mywebsocket')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
},
}
export const mutations = {
SET_REMOTE_DATA(state, remoteData) {
const wsData = JSON.parse(remoteData.data)
if (wsData.connectionId) {
state.connectionId = wsData.connectionId
console.log(`Retrieving Connection ID ${state.connectionId}`)
} else {
console.log(`We got chats !!`)
state.messageType = wsData.type
state.incomingChatInfo = wsData.documents
}
},
SET_CONNECTION(state, message) {
if (message == 'open') {
state.connected = true
} else state.connected = false
},
SET_ERROR(state, error) {
state.error = error
},
}
And finally this is the shift store (where the problem is), as you can see I have a startShift action (everything works fine with it) and then the "updateEventsSubscription" where I'm trying to wait for the response from the "startShift" action and the "processWebsocket" action. Debugging the app I realize that everything works fine with the startShift action but the websocket action sends the response after the "updateEventsSubscription" needs it causing an error when I try to make a put to that API (because it needs the connectionId parameter coming from the state of the websocket).
import ShiftService from '#/services/ShiftService.js'
export const namespaced = true
export const state = {
connectionId: '',
shiftId: '',
agentShiftInfo: '{}',
}
export const actions = {
startShift({ commit }, agentId) {
return ShiftService.startShift(agentId)
.then((response) => {
if (response.status === 200) {
commit('START_SHIFT', response.data.aggregateId)
}
})
.catch((error) => {
console.log(error)
if (error.response.status === 401) {
console.log('Error in Response')
}
})
},
async updateEventsSubscription({ dispatch, commit, rootState }, agentId) {
await dispatch('startShift', agentId)
const shiftId = state.shiftId
await dispatch('websocket/processWebsocket', null, { root: true })
let agentShiftInfo = {
aggregateId: state.shiftId,
connectionId: rootState.websocket.connectionId,
}
console.log(agentShiftInfo)
return ShiftService.updateEventsSubscription(shiftId, agentShiftInfo)
.then((response) => {
commit('UPDATE_EVENTS_SUBSCRIPTION', response.data)
})
.catch((error) => {
if (error.response.status === 401) {
console.log('Error in Response')
}
})
},
}
export const mutations = {
START_SHIFT(state, shiftId) {
state.shiftId = shiftId
console.log(`Retrieving Shift ID: ${state.shiftId}`)
},
UPDATE_EVENTS_SUBSCRIPTION(state, agentShiftInfo) {
state.agentShiftInfo = agentShiftInfo
},
}
You should convert your WebSocket action into a promise that resolves when WebSocket is connected.:
export const actions = {
processWebsocket({ commit }) {
return new Promise(resolve=> {
const v = this
this.ws = new WebSocket('mywebsocket')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
resolve();
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
});
},
}
So I realized that I have to resolve the promise on the this.ws.message instead. By doing that all my data is populated accordingly, there's still sync issues (I can't feed the websocket state at the moment because due to its async behaviour the state is not there yet when other components try to use it via: rootGetters.websocket.incomingChats for example) but I guess that's part of another question. Here's the final version of the module action:
export const actions = {
processWebsocket({ commit }) {
return new Promise((resolve) => {
const v = this
this.ws = new WebSocket('wss://ws.rubiko.io')
this.ws.onopen = function (event) {
commit('SET_CONNECTION', event.type)
v.ws.send('message')
}
this.ws.onmessage = function (event) {
commit('SET_REMOTE_DATA', event)
resolve(event)
}
this.ws.onerror = function (event) {
console.log('webSocket: on error: ', event)
}
this.ws.onclose = function (event) {
console.log('webSocket: on close: ', event)
commit('SET_CONNECTION')
ws = null
setTimeout(startWebsocket, 5000)
}
})
},
}
Anyways, thanks #Eldar you were in the right path.

TypeError: props.onLoggedIn is not a function

I have a main view where users login/register to see movies and the login view page has a function that when you submit, is supposed to query the function in main view.
Main View:
export class MainView extends React.Component {
constructor(props) {
super(props);
// Initial state is set to null
this.state = {
movies: [],
user: null
};
}
componentDidMount() {
let accessToken = localStorage.getItem("token");
if (accessToken !== null) {
this.setState({
user: localStorage.getItem("user"),
});
this.getMovies(accessToken);
}
}
/* When a user successfully logs in, this function updates the `user` property in state to that *particular user*/
onLoggedIn(authData) {
console.log(authData);
this.setState({
user: authData.user.Username,
});
localStorage.setItem("token", authData.token);
localStorage.setItem("user", authData.user.Username);
this.getMovies(authData.token);
}
onLoggedOut() {
localStorage.removeItem("token");
localStorage.removeItem("user");
this.setState({
user: null,
});
}
getMovies(token) {
axios
.get(`https://app.herokuapp.com/movies`, {
headers: { Authorization: `Bearer ${token}` },
})
.then((response) => {
// Assign the result to the state
this.setState({
movies: response.data,
});
})
.catch(function (error) {
console.log(error);
});
}
Login View:
export function LoginView(props) {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
// Declare hook for each input
const [usernameErr, setUsernameErr] = useState("");
const [passwordErr, setPasswordErr] = useState("");
// validate user inputs
const validate = () => {
let isReq = true;
if (!username) {
setUsernameErr("Username required");
isReq = false;
} else if (username.length < 2) {
setUsernameErr("Username must be at least 2 characters long");
isReq = false;
}
if (!password) {
setPasswordErr("Password required");
isReq = false;
} else if (password.length < 6) {
setPassword("Password must be at least 6 characters long");
isReq = false;
}
return isReq;
};
const handleSubmit = (e) => {
e.preventDefault();
const isReq = validate();
if (isReq) {
/* Send request to the server for authentication */
axios
.post(`https://app.herokuapp.com/login`, {
Username: username,
Password: password,
})
.then((response) => {
const data = response.data;
props.onLoggedIn(data);
})
.catch((error) => {
console.log(error, "no such user");
});
}
};
For some reason it tells me that props.onLoggedIn is not a function, which I have already imported in to MainView (I mean LoginView) which should recognise the function. Is there anything that I am missing?

Getting a nested parameter return value in the wrapped function return

I have a scenario where I need to get the return value from a function that passed to another function as a parameter. I tried multiple ways. But couldn't get the returnValue to the CreateProfileComponent from ProfileAction.js file.
// ProfileAction.js
export default (database) => {
return {
createProfile: async (createdProfile) => {
const profileCollection = database.get("profiles");
const { name, email } = createdProfile;
try {
await database.action(async () => {
const returnValue = await profileCollection.create((profile) => {
profile.name = name;
profile.email = email;
});
});
} catch (error) {
console.log("createProfile", error);
}
},
};
};
// CreateProfileComponent.js
const CreateProfileComponent = () => {
const database = useDatabase();
const profileAction = ProfileAction(database);
const createdRecord = await profileAction.createProfile({
name: "John Doe",
email: "johndoe#gmail.com",
});
}
Finally what I want is the returnValue value in CreateProfileComponent. The functions database.actions() and profileCollection.create() are used from a third party library (WatermelonDB)
I am not sure what database.action does but you should return a value in this function. Like following: return await database.action(async () => {
And throw an error on catch
export default (database) => {
return {
createProfile: async (createdProfile) => {
const profileCollection = database.get("profiles");
const { name, email } = createdProfile;
try {
return await database.action(async () => {
const returnValue = await profileCollection.create((profile) => {
profile.name = name;
profile.email = email;
});
});
} catch (error) {
console.log("createProfile", error);
throw error;
}
},
};
};
// CreateProfileComponent.js
const CreateProfileComponent = () => {
const database = useDatabase();
const profileAction = ProfileAction(database);
try {
const createdRecord = await profileAction.createProfile({
name: "John Doe",
email: "johndoe#gmail.com",
});
} catch (e) {
}
}

Cannot read property 'conversationId' of undefined while using a reducer function

Just to make it clear router uses the code below and my messages.js are inside api folder....
router.use("/messages", require("./messages"));
so my api call is correct.
Backend for posting the message.... I know conversationId will be null if no conversation exists but... I am trying to send message where conversation exists already and still I am getting cannot read the conversationId of undefined....
// expects {recipientId, text, conversationId } in body
// (conversationId will be null if no conversation exists yet)
router.post("/", async (req, res, next) => {
try {
if (!req.user) {
return res.sendStatus(401);
}
const senderId = req.user.id;
const { recipientId, text, conversationId, sender } = req.body;
// if we already know conversation id, we can save time and just add it to message and return
if (conversationId) {
const message = await Message.create({ senderId, text, conversationId });
return res.json({ message, sender });
}
// if we don't have conversation id, find a conversation to make sure it doesn't already exist
let conversation = await Conversation.findConversation(
senderId,
recipientId
);
if (!conversation) {
// create conversation
conversation = await Conversation.create({
user1Id: senderId,
user2Id: recipientId,
});
if (onlineUsers.includes(sender.id)) {
sender.online = true;
}
}
const message = await Message.create({
senderId,
text,
conversationId: conversation.id,
});
res.json({ message, sender });
} catch (error) {
next(error);
}
});
module.exports = router;
This is the frontend that posts the data to the backend....
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
Okay so here is detail information on how I am dispatching it.
class Input extends Component {
constructor(props) {
super(props);
this.state = {
text: "",
};
}
handleChange = (event) => {
this.setState({
text: event.target.value,
});
};
handleSubmit = async (event) => {
event.preventDefault();
// add sender user info if posting to a brand new convo,
// so that the other user will have access to username, profile pic, etc.
const reqBody = {
text: event.target.text.value,
recipientId: this.props.otherUser.id,
conversationId: this.props.conversationId,
sender: this.props.conversationId ? null : this.props.user,
};
await this.props.postMessage(reqBody);
this.setState({
text: "",
});
};
render() {
const { classes } = this.props;
return (
<form className={classes.root} onSubmit={this.handleSubmit}>
<FormControl fullWidth hiddenLabel>
<FilledInput
classes={{ root: classes.input }}
disableUnderline
placeholder="Type something..."
value={this.state.text}
name="text"
onChange={this.handleChange}
/>
</FormControl>
</form>
);
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(withStyles(styles)(Input));
const mapDispatchToProps = (dispatch) => {
return {
postMessage: (message) => {
dispatch(postMessage(message));
},
};
};
// message format to send: {recipientId, text, conversationId}
// conversationId will be set to null if its a brand new conversation
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body);
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
So I have attached what I want to do here now....
But I am still getting the problem....
// CONVERSATIONS THUNK CREATORS, this is how I am getting data from the backend
export const fetchConversations = () => async (dispatch) => {
try {
const { data } = await axios.get("/api/conversations");
dispatch(gotConversations(data));
} catch (error) {
console.error(error);
}
};
export const setNewMessage = (message, sender) => {
return {
type: SET_MESSAGE,
payload: { message, sender: sender || null },
};
};
// REDUCER
const reducer = (state = [], action) => {
switch (action.type) {
case GET_CONVERSATIONS:
return action.conversations;
case SET_MESSAGE:
return addMessageToStore(state, action.payload);
case ADD_CONVERSATION:
return addNewConvoToStore(
state,
action.payload.recipientId,
action.payload.newMessage
);
default:
return state;
}
};
I am getting an error saying Cannot read property 'conversationId' of undefined while using a reducer function... Should I give the setintial value of the message to empty?
export const addMessageToStore = (state, payload) => {
const { message, sender } = payload;
// if sender isn't null, that means the message needs to be put in a brand new convo
if (sender !== null) {
const newConvo = {
id: message.conversationId,
otherUser: sender,
messages: [message],
};
newConvo.latestMessageText = message.text;
return [newConvo, ...state];
}
return state.map((convo) => {
if (convo.id === message.conversationId) {
const convoCopy = { ...convo };
convoCopy.messages.push(message);
convoCopy.latestMessageText = message.text;
return convoCopy;
} else {
return convo;
}
});
};
Issue
The saveMessage function is declared async
const saveMessage = async (body) => {
const { data } = await axios.post("/api/messages", body);
return data;
};
but the postMessage action creator isn't async so it doesn't wait for the implicitly returned Promise to resolve before continuing on and dispatching to the store. This means that data.message is undefined since a Promise object doesn't have this as a property.
export const postMessage = (body) => (dispatch) => {
try {
const data = saveMessage(body); // <-- no waiting
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};
Solution
Declare postMessage async as well and await the data response value.
export const postMessage = (body) => async (dispatch) => {
try {
const data = await saveMessage(body); // <-- await response
if (!body.conversationId) {
dispatch(addConversation(body.recipientId, data.message));
} else {
dispatch(setNewMessage(data.message));
}
sendMessage(data, body);
} catch (error) {
console.error(error);
}
};

how to check function is called or not after http post call?

I am trying to test my one function in which there is a http post request ,after success it call a function I want to check it function is called or not after success.
here is my code
https://codesandbox.io/s/ecstatic-currying-5q1b8
I am testing below function
export const saveWithoutSubmit = async (values, updateTaskListAfterFilter) => {
var obj = {
remarks: values.remarks,
requestedBy: localStorage.getItem("msisdn")
};
try {
const response = await sendPostRequest(`${API_TASK_URL}closeSr`, {
...obj,
saveWithoutSubmit: true
});
if (response && response.data && response.data.status.code !== "200") {
error(response.data.result.message);
} else {
console.log(response);
success(response.data.status.message);
updateTaskListAfterFilter();
}
} catch (e) {
if (e.response && e.response.data) {
console.log(e.response.data.message);
error(e.response.data.status.message);
}
}
};
I am doing testing like this
describe("remark service test", () => {
const fakeAxios = {
post: jest.fn(() => Promise.resolve({ data: { greeting: "hello there" } }))
};
const sendPostRequest = jest.fn(() =>
Promise.resolve({ data: { greeting: "hello there" } })
);
it("save without sumit", async done => {
const mockUpdateTaskListAfterFilter = jest.fn();
const updateTaskListAfterFilter = () => {};
saveWithoutSubmit({}, mockUpdateTaskListAfterFilter);
// expect(updateTaskListAfterFilter).toBeCalled();
expect(mockUpdateTaskListAfterFilter).toBeCalled();
done();
});
});
getting error
Why not just put a variable in the normal arrow function and await saveWithoutSubmit
remark.service.js
const sendPostRequest = () => {
return Promise.resolve({
data: {
greeting: "hello there",
status: {
code: "200"
}
}
});
};
export const saveWithoutSubmit = async(values, updateTaskListAfterFilter) => {
try {
const response = await sendPostRequest();
if (response && response.data && response.data.status.code !== "200") {
console.log("Error");
return;
} else {
console.log("Sucess");
updateTaskListAfterFilter();
return;
}
} catch (e) {
if (e.response && e.response.data) {
console.log(e.response.data.message);
}
}
};
remark.service.test.js
import {
saveWithoutSubmit
} from "./remark.service";
describe("remark service test", () => {
it("save without sumit", async function() {
console.log("save without sumit getting called");
let called = false;
const mockUpdateTaskListAfterFilter = () => {
console.log("callback");
called = true;
};
await saveWithoutSubmit({}, mockUpdateTaskListAfterFilter);
console.log("after saveWithoutSubmit", called);
expect(called).toBe(true);
});
});

Categories