Middleware to Vuex state not updating state? - javascript

I am trying to get my firestore data that I am getting to be stored in my state but it does not show up in my Vue dev tools in the state.
When I console.log() the data I am getting through the store action I can see I am getting the right data but it will not update the state.
I am using middle-ware on my home page and another page to dispatch my action in order to get the required data.
I have also used a conditional statement within the middle-ware below to try to only dispatch action when my other state variables are not null because the firestore query requires state.user
//this is check-auth middleware
export default function(context) {
// in initAuth we are forwarding it the req
context.store.dispatch('initAuth', context.req)
console.log('WE ARE GRABBING USER INFO')
context.store.dispatch('grabUserInfo', context.req)
console.log('There is already user info there')
// context.store.dispatch('currentUser')
}
We are dispatching grabUserInfo to run a action that has a firestore query in it.
grabUserInfo(vuexContext, context) {
let userInfo = []
var userRef = db.collection('users')
var query = userRef
.where('user_id', '==', vuexContext.state.user)
.get()
.then(querySnapshot => {
querySnapshot.forEach(doc => {
console.log(doc.data())
userInfo.push(doc.data())
})
})
vuexContext.commit('setUserInfoSub', userInfo)
}
my
console.log(doc.data()) is showing
subscribers: ["noFace2"]
subscriptions: ["noFace3"]
user_id: "VbomJvANYDNe3Bek0suySs1L8oy1"
username: "noFace1"
my info should be going through a mutation and commiting to state, but it does not show up in my state vue dev tools.
setUserInfoSub(state, payload) {
state.userInfoSub = payload
}
I don't understand how the data is not ending up in my state. Here is my State and mutations.
const createStore = () => {
return new Vuex.Store({
state: {
loadedCards: [],
user: null,
username: null,
userInfoSub: [],
token: null
},
mutations: {
setCards(state, cards) {
state.loadedCards = cards
},
setUser(state, payload) {
state.user = payload
},
setUsername(state, payload) {
state.username = payload
},
setUserInfoSub(state, payload) {
state.userInfoSub = payload
},
setToken(state, token) {
state.token = token
}

Change your mutation to this:
setUserInfoSub(state, payload) {
Vue.set(state, 'userInfoSub', payload);
}
This will allow Vue's reactivity system to kick back in for the state variable reassignment.
Per #Alexander's comment, you should also move the commit() inside then() given the async nature of the Firebase query.

Related

How do you use Redux Toolkit and React-Redux for User Registration & Authentication?

I've been working on the user registration of my React-Redux project (I'm really new to Redux), and I'm having trouble with registering Users with error handling...
My curriculum teaches nothing on using Redux with React (which is what my project does) to send data to an backend database.
I have been using useState for setting both the current user (currentUser, onLogin) and any errors (errors, setErrors) inside of UserInput.jsx, and I'm trying to move that functionality over to usersSlice.js.
Right now, I am trying to set my User object to be the action.payload. I also need to deal with how to pass the new User's info from my form, to my Slice.
Any solutions?
My code:
usersSlice.js
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
export const signup = createAsyncThunk("users/signup", async ({username, password}, thunkAPI) => {
await fetch("/signup", {
method: "POST",
headers: headers,
body: JSON.stringify({user: {username, password}})
}).then((data) => {
data.json().then((data) => {
if(data.errors){
return thunkAPI.rejectWithValue(data.errors);
} else{
// Find a way to set the current user!!!
return data;
}
})
})
});
const usersSlice = createSlice({
name: "users",
initialState: {
user: [], // This should be an SINGLE User Object!!!
errorMessage: null,
status: 'idle',
},
reducers: {
userLogin(state, action){
state.user.push(action.payload);
},
userLogout(state){
state.user = [];
},
},
extraReducers(builder){
// Omit extraReduxers logic
}
});
UserInput.jsx:
function UserInput({onLogin, username, setUsername, password, setPassword, errors, setErrors}){
const dispatch = useDispatch();
function handleSubmit(e){
e.preventDefault();
const user ={
username: username,
password: password
}
dispatch(signup(user));
if(user.errors) { // Returns null due to the user object above...
setErrors(user.errors);
}
else{
setErrors(null);
onLogin(user); // setCurrentUser(user);
}
}
// Omit the signup form
}
export default UserInput;

Simulate actioncable websocket receive in webdriverIo

Is there a way during webdriverio runtime to simulate an actioncable receive?
I am using a fork of the package action-cable-react called actioncable-js-jwt for Rails actioncable js connections. Both of these packages are no longer maintained, but actioncable-js-jwt was the only actioncable for react package I could find that supported jwt authentication. I am building an app in my company's platform and jwt authentication is required.
The problem I am running into is that I have a react component which dispatches a redux action to call an api. The api returns a 204, and the resulting data is broadcasted out from Rails to be received by the actioncable connection. This triggers a dispatch to update the redux store with new data. The component does actions based on new data compared to the initial value on component load, so I cannot simply just set initial redux state for wdio - I need to mock the actioncable receive happening.
The way the actioncable subscription is created is:
export const createChannelSubscription = (cable, receivedCallback, dispatch, channelName) => {
let subscription;
try {
subscription = cable.subscriptions.create(
{ channel: channelName },
{
connected() {},
disconnected(res) { disconnectedFromWebsocket(res, dispatch); },
received(data) { receivedCallback(data, dispatch); },
},
);
} catch (e) {
throw new Error(e);
}
return subscription;
};
The receivedCallback function is different for each channel, but for example the function might look like:
export const handleUpdateRoundLeaderWebsocket = (data, dispatch) => {
dispatch({ type: UPDATE_ROUNDING_LEADER, round: data });
};
And the redux state is used here (code snippets):
const [currentLeader, setCurrentLeader] = useState(null);
const userId = useSelector((state) => state.userId);
const reduxStateField = useSelector((state) => state.field);
const onChange = useCallback((id) => {
if (id !== currentLeader) {
if (id !== userId && userId === currentLeader) {
setShow(true);
} else {
setCurrentLeader(leaderId);
}
}
}, [currentLeader, userId]);
useEffect(() => {
onChange(id);
}, [reduxStateField.id, onChange]);
Finally, my wdio test currently looks like:
it('has info dialog', () => {
browser.url('<base-url>-rounding-list-view');
$('#my-button').click();
$('div=Continue').click();
// need new state after the continue click
// based on new state, might show an info dialog
});
Alternatively, I could look into manually updating redux state during wdio execution - but I don't know how to do that and can't find anything on google except on how to provide initial redux state.

Vue3/Vuex - Where am I "mutating vuex store state outside mutation handlers"?

When calling a Vuex Action where Firebase successfully updates, followed by a Vuex Mutation on state.profile, Vue triggers the following errors upon save within watch().
[Vue warn]: Unhandled error during execution of watcher callback
Error: [vuex] do not mutate vuex store state outside mutation handlers.
I have cloned and then merged store.state.profile into formData via rfdc.
Form values are updated within it's own reactive() data object formData, versus immediately committing to store in order to later provide both save() and cancel() options.
template
<template>
<element-textarea
id="about"
title="About"
:rows="10"
v-model="about"
></element-textarea>
<element-checkbox
v-for="(specialty, i) in jSpecialties"
:key="i"
:id="specialty.toLowerCase()"
:title="specialty"
v-model="specialties[specialty]"
>
{{ specialty }}
</element-checkbox>
</template>
script
...
const clone = require('rfdc/default')
export default defineComponent({
...
setup(){
...
let specialties: { [key: string]: boolean } = {}
jSpecialties.map(name => (specialties[name] = false))
const formData = reactive({
about: '',
specialties
})
watch(
() => store.state.profile,
() => {
try {
Object.assign(formData, clone(store.state.profile))
} catch (err) {
console.log('Error Appears Here', err)
}
}
)
const save = () =>
store.dispatch('updateProfile', formData)
.catch(err => console.log(err))
return { ...toRefs(formData), save }
}
})
vuex
export default createStore({
strict: true,
state: {
profile: {}
},
mutations: {
setProfile: (state, payload) => state.profile = payload,
},
actions: {
updateProfile: ({state, commit}, data) => {
const update = () =>
db
.collection(...)
.doc(...)
.set(data, {merge: true})
.then(_ => commit('setProfile', data))
.catch(err => console.log(err))
return update()
}
}
})
When removing strict: true from vuex, I receive this error:
Maximum recursive updates exceeded. This means you have a reactive effect that is mutating its own dependencies and thus recursively triggering itself. Possible sources include component template, render function, updated hook or watcher source function.
Replacing watch() with watchEffect() get rid of the Maximum recursive updates exceeded error.
watchEffect(() => {
try {
Object.assign(formData, clone(store.state.profile))
} catch (error) {
console.log(error)
}
})
However, when updating Vuex with strict: true, I am still receiving the following:
Error: [vuex] do not mutate vuex store state outside mutation handlers.
I've discovered the issue. I overlooked my copying of a reference object back to state.
I needed to clone formData prior to dispatching, as the same data object was also committed to store via actions.
const save = () =>
store.dispatch('updateProfile', clone(formData))
.catch(err => console.log(err))

How to dispatch an action from within another action in another Vuex store module?

CONTEXT
I have two store modules : "Meetings" and "Demands".
Within store "Demands" I have "getDemands" action, and within store "Meetings" I have "getMeetings" action. Prior to access meetings's data in Firestore, I need to know demands's Id (ex.: demands[i].id), so "getDemands" action must run and complete before "getMeetings" is dispatched.
Vuex documentation dispatching-action is very complete, but still, I don't see how to fit it in my code. There are also somme other good answered questions on the topic here :
Vue - call async action only after first one has finished
Call an action from within another action
I would like to know the best way to implement what I'm trying to accomplish. From my perspective this could be done by triggering one action from another, or using async / await, but I'm having trouble implementing it.
dashboard.vue
computed: {
demands() {
return this.$store.state.demands.demands;
},
meetings() {
return this.$store.state.meetings.meetings;
}
},
created() {
this.$store.dispatch("demands/getDemands");
//this.$store.dispatch("meetings/getMeetings"); Try A : Didn't work, seems like "getMeetings" must be called once "getDemands" is completed
},
VUEX store
Module A – demands.js
export default {
namespaced: true,
state: {
demands:[], //demands is an array of objects
},
actions: {
// Get demands from firestore UPDATED
async getDemands({ rootState, commit, dispatch }) {
const { uid } = rootState.auth.user
if (!uid) return Promise.reject('User is not logged in!')
const userRef = db.collection('profiles').doc(uid)
db.collection('demands')
.where('toUser', "==", userRef)
.get()
.then(async snapshot => {
const demands = await Promise.all(
snapshot.docs.map(doc =>
extractDataFromDemand({ id: doc.id, demand: doc.data() })
)
)
commit('setDemands', { resource: 'demands', demands })
console.log(demands) //SECOND LOG
})
await dispatch("meetings/getMeetings", null, { root: true }) //UPDATE
},
...
mutations: {
setDemands(state, { resource, demands }) {
state[resource] = demands
},
...
Module B – meetings.js
export default {
namespaced: true,
state: {
meetings:[],
},
actions: {
// Get meeting from firestore UPDATED
getMeetings({ rootState, commit }) {
const { uid } = rootState.auth.user
if (!uid) return Promise.reject('User is not logged in!')
const userRef = db.collection('profiles').doc(uid)
const meetings = []
db.collection('demands')
.where('toUser', "==", userRef)
.get()
.then(async snapshot => {
await snapshot.forEach((document) => {
document.ref.collection("meetings").get()
.then(async snapshot => {
await snapshot.forEach((document) => {
console.log(document.id, " => ", document.data()) //LOG 3, 4
meetings.push(document.data())
})
})
})
})
console.log(meetings) // FIRST LOG
commit('setMeetings', { resource: 'meetings', meetings })
},
...
mutations: {
setMeetings(state, { resource, meetings }) {
state[resource] = meetings
},
...
Syntax:
dispatch(type: string, payload?: any, options?: Object): Promise<any
Make the call right
dispatch("meetings/getMeetings", null, {root:true})

Next/Apollo: How to correctly update apollo cache if the relevant query was run in getInitialProps

I'm using nextjs and apollo (with react hooks). I am trying to update the user object in the apollo cache (I don't want to refetch). What is happening is that the user seems to be getting updated in the cache just fine but the user object that the component uses is not getting updated. Here is the relevant code:
The page:
// pages/index.js
...
const Page = ({ user }) => {
return <MyPage user={user} />;
};
Page.getInitialProps = async (context) => {
const { apolloClient } = context;
const user = await apolloClient.query({ query: GetUser }).then(({ data: { user } }) => user);
return { user };
};
export default Page;
And the component:
// components/MyPage.jsx
...
export default ({ user }) => {
const [toggleActive] = useMutation(ToggleActive, {
variables: { id: user.id },
update: proxy => {
const currentData = proxy.readQuery({ query: GetUser });
if (!currentData || !currentData.user) {
return;
}
console.log('user active in update:', currentData.user.isActive);
proxy.writeQuery({
query: GetUser,
data: {
...currentData,
user: {
...currentData.user,
isActive: !currentData.user.isActive
}
}
});
}
});
console.log('user active status:', user.isActive);
return <button onClick={toggleActive}>Toggle active</button>;
};
When I continuously press the button, the console log in the update function shows the user active status as flipping back and forth, so it seems that the apollo cache is getting updated properly. However, the console log in the component always shows the same status value.
I don't see this problem happening with any other apollo cache updates that I'm doing where the data object that the component uses is acquired in the component using the useQuery hook (i.e. not from a query in getInitialProps).

Categories

Resources