Update V-if in vuejs reactively - javascript

I was wondering if it's possible to re-render this v-if statement inside my component.
I am enabling/disabling a user account through firebase admin functions. This currently works, however whenever I disable a user I have to refresh the page in order to show updates, I can manually refresh, but wondered if there is a way to do this with reactivity? I've tried to update the array manually (UsersAuth contains all the users from Firebase, with the disabled: true|false boolean).
html
<span v-if="usersAuth[index].disabled === true"> <button type="button" v-on:click="enableUser(user.id, index)" class="btn btn-success">Enable</button></span>
<span v-if="usersAuth[index].disabled === false"><button type="button" v-on:click="disableUser(user.id)" class="btn btn-primary">Disable</button></span>
VueJS Methods
data () {
return {
users: [],
user: null,
usersAuth: null,
selecteduser: null
}
},
created () {
// call all users from the firebase store.
const addMessage = firebase.functions().httpsCallable('listAllUsers')
addMessage()
.then(result => {
this.usersAuth = result.data.users
})
firebase.auth().onAuthStateChanged((user) => {
this.user = user
})
this.users = []
firebase
.firestore()
.collection('roles')
.get()
.then(snap => {
snap.forEach(doc => {
const user = doc.data()
console.log(doc.data())
user.id = doc.id
this.users.push(user)
})
})
// get the users' enabled status
},
disableUser (uid) {
const addMessage = firebase.functions().httpsCallable('disableUser')
const data = { uid: uid }
addMessage(data)
.then((result) => {
if (result === true) {
console.log(this.userAuth)
}
})
.catch(function (error) {
console.log(error)
})
},
enableUser (uid, index) {
const addMessage = firebase.functions().httpsCallable('enableUser')
const data = { uid: uid }
addMessage(data)
.then((result) => {
this.usersAuth[index].disabled = true
})
.catch(function (error) {
console.log(error)
})
},
listAllUsers () {
const addMessage = firebase.functions().httpsCallable('listAllUsers')
addMessage()
.then((result) => {
console.log(result)
})
.catch(function (error) {
console.log(error)
})
}
Firebase function (if you require this)
exports.disableUser = functions.https.onCall(async (data, context) => {
if (!context.auth.token.superadmin) return
try {
listUsers = admin.auth().updateUser(data.uid, {
disabled: true
})
.then(function() {
console.log("Successfully disabled user " + data.uid);
})
return true
} catch (error) {
console.log(error)
}
});
exports.enableUser = functions.https.onCall(async (data, context) => {
if (!context.auth.token.superadmin) return
try {
listUsers = admin.auth().updateUser(data.uid, {
disabled: false
})
.then(function() {
console.log("Successfully disabled user " + data.uid);
})
return true
} catch (error) {
console.log(error)
}
});
exports.listAllUsers = functions.https.onCall((data, context) => {
if (!context.auth.token.superadmin) return
try {
return admin.auth().listUsers()
} catch (error) {
console.log(error)
}
});

In your enableUser method, this.usersAuth[index].disabled = true should be this.usersAuth[index].disabled = false, so that you're enabling the user rather than disabling them.
You can read The Vue Instance and Reactivity in Depth for more information about how reacitivty works with Vue.
When a Vue instance is created, it adds all the properties found in
its data object to Vue’s reactivity system. When the values of those
properties change, the view will “react”, updating to match the new
values.
On a side note, if disabled is either true or false, you can simplify your code to:
<span v-if="usersAuth[index].disabled">
and <span v-else>

Related

Firebase Function - pass in function context

Below is my firebase function:
const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');
admin.initializeApp();
exports.deleteUser = functions
.runWith({
timeoutSeconds: 540,
memory: '2GB'
})
.https.onCall((data, context) => {
const userId = context.auth.uid;
var promises = [];
// DELETE DATA
var paths = ['users/' + userId, 'messages/' + userId, 'chat/' + userId, 'like/' + userId];
paths.forEach((path) => {
promises.push(
recursiveDelete(path).then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE FILES
const bucket = admin.storage().bucket();
var image_paths = ["avatar/" + userId, "avatar2/" + userId, "avatar3/" + userId];
image_paths.forEach((path) => {
promises.push(
bucket.file(path).delete().then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE USER
promises.push(
admin.auth().deleteUser(userId)
.then( () => {
console.log('Successfully deleted user');
return true;
})
.catch((error) => {
console.log('Error deleting user:', error);
})
);
return Promise.all(promises).then(() => {
return true;
}).catch(er => {
console.error('...', er);
});
});
function recursiveDelete(path, context) {
return firebase_tools.firestore
.delete(path, {
project: process.env.GCLOUD_PROJECT,
recursive: true,
yes: true,
token: functions.config().fb.token
})
.then(() => {
return {
path: path
}
}).catch( (error) => {
console.log('error: ', error);
return error;
});
}
// [END recursive_delete_function]
When calling this function, how can I pass in the context.auth.id?
Below is what i've tried:
async function deleteAccount(userId) {
const deleteUser = firebase.functions().httpsCallable("deleteUser");
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
}
But im getting the following error:
Unhandled error TypeError: Cannot read property 'uid' of undefined
I know the the context.auth.id is available server side but In this instance I need a way i can pass it in.
You don't have to pass user's UID in callable cloud function. The user must be logged in with Firebase authentication and Firebase SDKs will take care of the rest.
Can you try logging current user in deleteAccount function before calling cloud function just to ensure user is logged in? Also context.auth.uid is UID of user that is calling the function. If you want to access the userId that you are passing in the function, refactor the code as shown below.
The deleteUser() function would take only 1 parameter that's the data you want to pass in Cloud functions.
// not deleteUser({}, { userId })
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
When you are explicitly passing any data in Cloud function, that can be access from data and not context:
const { userId } = data;

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.

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 redirect after dispatching an action in React.js

I have implemented a signup view with backend validation, and I want to redirect to Login view after successfull signup.
Here is my dispatching method:
export const initSignup = (details) => {
return (dispatch) => {
axios.post("http://localhost:8080/auth/signup", {
email: details.email,
password: details.password
}).then((result) => {
console.log(result);
return dispatch(_signup(result));
})
.catch((err) => {
//use err.response to get our custom err response from backend
//since once we send 400+ response status it goes to catch block
console.log(err);
dispatch(_signupError(err.response));
})
}
}
const _signup = (result) => {
return {
type: SIGNUP,
result: result
}
}
const _signupError = (err) => {
return {
type: ERROR,
error: err
}
}
After disptach _signup I want to redirect to "/login".
Can someone help me here.
You need to use routes with paths and history prop
const { from } = { from: { pathname: "/myPath" } };
this.props.history.push(from);

Facebook graph API: return object from GraphRequest callback

I have a function that successfully makes requests, but am having trouble translating it to the React-Redux data flow because I can't figure out how to return the object that is received in the callback.
What I currently have doesn't work, but I am confident it makes the request successfully because I can see it when I console.log it.
import FBSDK, { AccessToken, GraphRequest, GraphRequestManager } from 'react-native-fbsdk'
export const callback = (error, result) => {
if (error) {
console.log(error);
return error
} else {
console.log(result);
return result;
}
}
export const graphRequestFor = fields => {
AccessToken.getCurrentAccessToken()
.then(token => {
console.log(token);
const request = new GraphRequest(
'/me',
{
accessToken: token.accessToken,
parameters: {
fields: {
string: fields
}
}
}, callback
)
new GraphRequestManager().addRequest(request).start()
})
}
export const login = () => {
graphRequestFor('first_name, last_name')
.then(results => {
return results
}) //undefined
}
Not an answer to my direct question, but a solution to my problem is to make the api call directly inside the action and dispatching the object from the callback
import FBSDK, { AccessToken, GraphRequest, GraphRequestManager } from 'react-native-fbsdk'
export const login = params => dispatch => {
AccessToken.getCurrentAccessToken()
.then(token => {
const request = new GraphRequest(
'/me',
{
accessToken: token.accessToken,
parameters: {
fields: {
string: params
}
}
}, (error, result) => {
if (error) {
console.log(error);
return dispatch(receiveCurrentUser(error))
} else {
return dispatch(receiveCurrentUser(result))
}
}
)
new GraphRequestManager().addRequest(request).start()
})
}

Categories