How to set state to api data in the store - javascript

I am trying to set my state to the data I'm getting from my API with a GETTER in the store.
during the mounted() lifecyclehook trigger the GETTER getProducts() which looks like this:
export const getters = {
async getProducts() {
axios.get('/api/products')
.then(res => {
var data = res.data
commit('setProducts', data)
})
.catch(err => console.log(err));
}
}
In the GETTER I try to trigger a MUTATION called setProducts() which looks like this:
export const mutations = {
setProducts(state, data) {
state.products = data
}
}
But when I run this I get the error ReferenceError: commit is not defined in my console.
So obviously what goes wrong is triggering the MUTATION but after looking for 2 days straight on the internet I still couldn't find anything.
I also tried replacing commit('setProducts', data) with:
this.setProducts(data)
setProducts(data)
Which all ended with the error "TypeError: Cannot read properties of undefined (reading 'setProducts')"

If your function getProduct is defined in a Vue component, you have to access the store like this :
this.$store.commit('setProducts', data)
If your function is not defined in a Vue component but in an external javascript file, you must first import your store
import store from './fileWhereIsYourStore.js'
store.commit('setProducts', data)
If your getters export is literally the definition of your store's getters, you can use the solution of importing the store first, but you should know that it is clearly not a good practice to make commits in getters. There must be a better solution to your problem.
EDIT : To answer your comment, here's how you could do it:
// Your store module
export default {
state: {
products: []
},
mutations: {
SET_PRODUCTS(state, data) {
state.products = data
}
},
actions: {
async fetchProducts(store) {
await axios.get('/api/products')
.then(res => {
var data = res.data
store.commit('SET_PRODUCTS', data)
})
.catch(err => console.log(err));
}
}
}
Now, you can fetch products and populate your store in each of your components like this :
// A random Vue Component
<template>
</template>
<script>
export default {
async mounted() {
await this.$store.dispatch('fetchProducts')
// now you can access your products like this
console.log(this.$store.state.products)
}
}
</script>
I didn't tested this code but it should be ok.

Only actions do have commit in their context as you can see here.
Getters don't have commit.
Otherwise, you could also use mapActions (aka import { mapActions } from 'vuex'), rather than this.$store.dispatch (just a matter of style, no real difference at the end).
Refactoring your code to have an action as Julien suggested is a good solution because this is how you should be using Vuex.
Getters are usually used to have some state having a specific structure, like sorted alphabetically or alike. For common state access, use the regular state or the mapState helper.

Related

Angular Apollo Set watchQuery Results to a Usable Variable

New to Angular/Apollo/TS and this is driving me nuts, so any help is appreciated.
I am working on setting up a small app with Angular 10, Apollo, and a GraphQL API. I recently built the same thing in Vue and thought recreating the project would be a good way to pick up some Angular.
My connection to the API is working, as is my query, but I can't figure out how to map the results to an array so I can access them in my component. Using console.log inside the subscription shows the correct data is returned. console.log outside of the query on 'this' shows the query results, however they are never saved/mapped to the variable they should be set to.
Here's the code for my service:
import { Injectable } from '#angular/core';
import { Apollo } from 'apollo-angular';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import gql from 'graphql-tag';
const USER_SEARCH = gql`
query getUsers {
search(query: "moose", type: USER, first: 10) {
nodes {
... on User {
login
email
location
name
}
}
pageInfo {
hasNextPage
hasPreviousPage
startCursor
endCursor
}
userCount
}
}`;
export class UserService {
loading: boolean = true;
users: [];
constructor(private apollo: Apollo) { }
getUsers(): any {
this.apollo.watchQuery<any>({
query: USER_SEARCH
})
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.users = data.search;
});
console.log(this);
return this.users;
}
}
I can call the getUsers() function from my component, and 'this' has the service listed, and inside of it 'users' has my query results listed in it. However, console.log for this.users in the service or the component returns undefined.
I've tried about every type of example I could find, including the query examples from the apollo docs, and the example of using apollo with angular from hasura.io. Tried using a pipe and map, pluck, just valueChanges, a few different subscribes, setting a variable inside the function to assign the data value to, setting the query to variable, setting the query in ngOnInit in the component, and a few other things I'm sure I'm forgetting. Nothing seems to work. I looked into using a callback to wait for the query to return before setting the value, but my understanding is that I shouldn't have to do anything like that. I'm sure it's something dumb I'm missing or don't know about with Apollo or Angular, but I'm just not positive what it is I'm missing.
Any ideas?
this.getUsers = this.getUsers.bind(this);
within a constructor?
using setTimeout is not an ideal solution, you can directly update your component variable in subscribe callback function and do whatever you want to do with it in your template. Look at my example
getItems() {
this.apollo
.watchQuery({
query: this.getItemsQuery,
})
.valueChanges.subscribe((result: any) => {
this.items = result?.data?.items;
});
}
and in template
<mat-option *ngFor="let item of items" [value]="item.price">
{{ item.name }}
</mat-option>
Maybe not the ideal solution, so I'm still open to trying other things, but I was able to get the value set in my component by using a promise with a timer in the service, then an async await in the component.
Service
getUsers(): any {
return new Promise((resolve, reject) => {
let me = this;
this.apollo.watchQuery<any>({
query: USER_SEARCH
})
.valueChanges
.subscribe(({ data, loading }) => {
this.loading = loading;
this.users = data.search;
});
setTimeout( function() {
if(me.users !== 'undefined'){
resolve(me.users)
}
}, 1000)
})
}
Component
async getUsers(): Promise<any> {
this.users = await this.userService.getUsers();
console.log(this.users);
}
This allows this.users to be set from the service. As far as I can tell, Apollo is still running the query when Angular starts setting values, resulting in the value originally being shown as undefined, but my service having values from the query in the console. Not sure if there's a better way with Apollo or Angular to resolve this issue, but if so I'd love to hear about it.
Thanks!

import props, destructuring assignment & unresolved variable warning

I'm a bit new at frontend, so I have a question about code practices with with props importing.
I'm using next.js (which is based on React) and I'm need to insert props from API endpoint right to my page.
According to the example, it should looks like this:
export async function getServerSideProps({query}) {
const res = await fetch(encodeURI(`url_here+${query}`));
const json = await res.json();
The problem is with «what happens» next:
If I export my props (result of the function above) to page like this:
return { props: {
_id: json._id,
ilvl: json.ilvl,
...
checksum: json.checksum,
And import it like with using of destructuring assignment as an argument function:
function CharacterPage({ _id, id, ... }) {
...
}
THE PROBLEM
That there are almost 16+ key:values in response json object from API endpoint.
So if I will follow to the codestyle from above that will be.. em.. guess you already understand.
So I could export result from API endpoint like:
export async function getServerSideProps({query}) {
const res = await fetch(encodeURI(`url_here`));
const json = await res.json();
return {props: {json}
}
And import it, as one argument to the page like:
function CharacterPage({json})
But if I'll use json.name object keys on page (for conditional rendering) my IDE (WebStrom) shows me unresolved variable warning.
So where can I read about correct import practice and find react-import props example with lots of keys from JSON?
Should I use:
let {id, name, ...etc} = json
right after:
function CharacterPage({json})
for every key that I want to access or there is a better way/code practice for importing props?
My First idea is you can modify the JSON object in return of getServerSideProps. It would be more clear to identify which kind of object with attributes used here.
return { props: {
name: json.name,
id: json.id
...
}
}
If you cannot do that, it would be better to destructure initially.
let {id, name, ...etc} = json
But only destructure the elements you need. There is no need for destructuring all the elements.

Vue/Vuex - Module two depends on module one, and module one gets data from server

Check this out:
import accountModule from '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
export default new Vuex.Store({
modules: {
account: accountModule,
other: otherModule,
}
});
The data initialization in other depends on the account module because the account module has user specific settings. Suppose other.state.list depends on account.state.settings.listOrder. However, I want the data for the account module to come from the server. Which is async. So when other is trying to get set up, it can't just try to reference account.state.settings.listOrder because the response from the server may not have come back yet.
I tried exporting a promise in accountModule that resolves with the module itself. But that approach doesn't seem to work.
import accountModulePromise from '#/store/modules/account/account';
accountModulePromise.then(function (accountMoudle) {
import otherModule from '#/store/modules/other/other';
...
});
This gives me an error saying that import statements need to be top level.
The following doesn't work either:
let accountModule = await import '#/store/modules/account/account';
import otherModule from '#/store/modules/other/other';
...
It gives me an error saying that await is a reserved word. I'm confused though, because https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import says that I should be able to do it.
Your last code block didn't work because of await have to be inside async function.
Remember, the await keyword is only valid inside async functions. If
you use it outside of an async function's body, you will get a
SyntaxError.
From MDN.
You can use Dynamic Module Registration:
accountModulePromise.then(async () => {
let otherModule = await import('#/store/modules/other/other');
store.registerModule('other', otherModule.default);
});
But when you want to get state or dispatch actions you have to check whether module is registered which is pretty bad.
In my opinion it would be better if you redesign your module structure to decoupling each other. Try to move your initialize code to main.js or App.vue then dispatch actions to update module states from that.
Updates
From your last update, Another idea to decoupling your store, I think you should store your list without order and sort it only when you use. You can do this with:
Computed property:
...
computed: {
list () {
let list = this.$store.state.other.list
let order = this.$store.state.account.settings.listOrder
if (!list || !order) return []
return someSort(list, order)
}
},
beforeCreate () {
this.$store.dispatch('other/fetchList')
this.$store.dispatch('account/fetchListOrder')
}
...
Or Vuex getters:
...
getters: {
list: (state) => (order) => {
return someSort(state.list, order)
}
}
...
...
computed: {
list () {
let order = this.$store.state.account.settings.listOrder
return this.$store.getters['others/list'](order)
}
}
...
Okay, so you have two modules. One with state that is fetched from the server, the other with state that is dependent on the first, correct?
I would suggest the following approach:
Set up your modules with empty 'state' to begin with. Then create an action within accountModule to set up the state from the server. Use a getter on other to order the list. Finally, dispatch your action upon app creation.
const account = {
namespaced: true,
state: {
listOrder: ''
},
mutations: {
setListOrder (state, newListOrder) {
state.listOrder = newListOrder
}
},
actions: {
async fetchServerState (ctx) {
let result = await fetch("/path/to/server")
ctx.commit('setListOrder', result.listOrder)
// or whatever your response is, this is an example
}
}
}
const other = {
namespaced: true,
state: {
unorderedList: []
},
getters: {
list (state, getters, rootState) {
return someSort(state.unorderedList, rootState.account.listOrder);
}
}
}
within App.vue (or wherever)
created () {
this.$store.dispatch('account/fetchServerState')
}

Vuex: Call getters from action

Is there a way for a dispatch/action to call a getter inside of it?
mutations: {
setData(state, data) {
state.data = data;
}
}
actions: {
sendDataToServer({ commit }, payload) {
// call getter (data) and assign to variable
// do async functions from the data returned
}
},
getters: {
getAppData: state => () => {
return state.data;
}
}
So what's the best practice here? Using the mutation to change the state and then get the state and pass it to action which will then execute the async function or do I need to restructure my implementation?
call mutation -> get the data via getter -> call action
OR
do it all on the action (do mutation on the action and do the action/async method without the need of the getter)?
In addition to commit, actions has default injected parameters which are dispatch, getters and rootGetters. So you can simply write;
sendDataToServer({ commit, getters }, payload) to access getters.
You have access to getters inside an action:
getters: {
getUser(state){
return state.user
}
}
actions : {
myAction({ getters }){
let user = getters.getUser
}
}
In the action, you see the first parameter has {commit} in it. Similarly, you can pass {commit, state}. This way, you can directly access the state.data.
I think in your example, you would want to do the action because you can call the mutation from inside action itself using commit('setData').
The first parameter is there for you to use state and mutation as you prefer. Personally, I have only worked on projects where you do the action first and do mutation to store it in the app. For example, if I want to store a car info in the server somewhere, first I would do the action (and save it to remote db). Once I confirm that it saved in db, I would locally mutate in the store. This totally depends on case by case basis. But good thing is that you can mutate from inside the action
Action handlers receive a context object which exposes the same set of methods/properties on the store instance, so you can call context.commit to commit a mutation, or access the state and getters via context.state and context.getters
actions: {
sendDataToServer(context, payload) {
// context object contains state, commit, getters
context.getters.getAppData
}
},
Refer docs: https://vuex.vuejs.org/guide/actions.html#dispatching-actions
If you are using nuxt and isolated files in vuex, like this =
store -
|
|-- index.js
|
|-- store.js
|
|-- product.js
// store.js
export const getters = {
getNameStore: state => state.getNameStore ? state.getNameStore : null
};
I want the getNameStore of the store.js into product.js
// product.js
export const actions = {
setResultSearch({ commit, dispatch }, text) {
console.log(
'getNameStore',
this.getters["store/getNameStore"]
);
};
this.getters["store/getNameStore"]

Redux: using thunk middleware and combineReducers introduces extra key to getState

Problem: When using thunk middleware before introducing Redux.combineReducers, the getState passed to the thunk correctly returns an object with the correct keys. After refactoring to use Redux.combineReducers, the getState passed to the thunk now returns an object with nested keys. See code below which (hopefully) illustrates my point. This could lead to a potential maintenance nightmare of having to constantly grab the correct key for any thunk method that accesses state.
Question: Is there a simple way to set the correct context key within the thunk? The code feels brittle when I combine reducers and have to insert keys to access the correct state. Am I missing something simple?
Before code:
const Redux = require('redux'),
Thunk = require('redux-thunk');
// this is an action generator that returns a function and is handled by thunk
const doSomethingWithFoo = function() {
return function(dispatch, getState) {
// here we're trying to get state.fooValue
const fooValue = getState().fooValue;
dispatch({ type: "DO_SOMETHING", fooValue });
}
};
// this is a simple action generator that returns a plain action object
const doSimpleAction = function(value) {
// we simply pass the value to the action.
// we don't have to worry about the state's context at all.
// combineReducers() handles setting the context for us.
return { type: "SIMPLE_ACTION", value };
}
const fooReducer(state, action) {
// this code doesn't really matter
...
}
const applyMiddleware = Redux.applyMiddleware(Thunk)(Redux.createStore);
const fooStore = applyMiddleware(fooReducer);
After code (introducing a more global appStore):
// need to rewrite my thunk now because getState returns different state shape
const doSomethingWithFoo = function() {
return function(dispatch, getState) {
// here we're trying to get state.fooValue, but the shape is different
const fooValue = getState().foo.fooValue;
dispatch({ type: "DO_SOMETHING", fooValue });
}
};
const appReducers = Redux.combineReducers({
foo: fooReducer,
bar: barReducer,
});
const appStore = applyMiddleware(appReducers);
After thinking about it some more, I think the answer is to refactor the doSomethingWithFoo action generator so that it accepts fooValue as a parameter. Then I don't have to worry about state object shape changing.
const doSomethingWithFoo(fooValue) {
return function(dispatch, getState) {
// now we don't have to worry about the shape of getState()'s result
dispatch({ type: "DO_SOMETHING", fooValue });
}
}
You're over-thinking things. By definition, store.getState() returns the entire state, and combineReducers() pulls together multiple sub-reducers into a larger object. Both are working as intended. You're writing your own application, so you're responsible for how you want to actually organize your state shape and deal with it. If you feel things are too "brittle" this way, it's up to you to find a good way to structure things, but that's not a problem with Redux.
Also, using getState() in an action creator to determine what to do IS an entirely valid approach. In fact, the Reducing Boilerplate section of the Redux docs even does that as a demonstration:
export function addTodo(text) {
// This form is allowed by Redux Thunk middleware
// described below in “Async Action Creators” section.
return function (dispatch, getState) {
if (getState().todos.length === 3) {
// Exit early
return
}
dispatch(addTodoWithoutCheck(text))
}
}

Categories