I have an API class that has various methods that communicate to a backend like "login, register, createPost" etc. I am connecting this class to a reducer. The reducer contains the state of the user info, which I want to be accessible in my Api class:
import axios from 'axios';
import React, { Component } from 'react';
import { connect } from 'react-redux';
#connect(state => ({
api: state.api,
}) )
export default class Api extends Component {
export const login = async({args}) => {
const url = this.props.api.url.concat('/login/');
const config = {
headers: {
'X-CSRFTOKEN': this.props.api.token
}
};
try {
const data = await axios.post(url, {"username": args.username, "password": args.password}, config);
this.props.api.key = data.data.token;
this.props.api.user = data.data.user;
return data;
} catch (e) {
throw e;
}
}
};
async createPost(args (content of the post)) {
try {
const url = this.props.api.url.concat('/post/PostList');
const Response = await axios.post(url, {...args}, !**this.props.api.key**! );
return Response;
} catch (e) {
throw e;
}
}
In the first method, I set the imported state.key and state.user (connected via redux) information, and I want to access that in the second method (this.props.api.key I surrounded by stars). I am trying to do it this way because I have a multitude of actions on different screens, and users have to pass their authentication information to the api method they're calling on top of whatever they're trying to do in order to be able to execute whatever respective action. I figure that it's easier to pass the user info in my Api class instead of importing the Api state into every different file I call the actions in.
The issue I'm running into is I can't instantiate a new object of api like
const api = new Api();
Because it gives me an error "cannot read property store of undefined," so I can't call the actions api.login(withArgs) in respective files, and if I make the methods static they won't have access to this.props.whatever
How do I instantiate a class that's connected to the global state of redux, or how can I access the info in that global state outside of my reducer file?
Since Api extends React.Component, why are you trying to instantiate the class yourself vs. letting React render it for you?
ReactDOM.render(<Api store={store} />)
or if you are not using JSX
ReactDOM.render(React.createElement(Api, { store })
Related
I would like to implement a auto refresh feature for my VueX store.
Everything the user refresh their browser, an actions in VueX store will be triggered to load the user profile from API call.
Is't possible to achieve that?
import apiService from "#/services/apiService";
import apiUrls from "#/services/apiUrls";
import { getToken } from "#/services/jwtService";
// Code to run actions when user refresh
getToken() !== null ? this.actions.getUserProfile() : "";
const state = {
userProfile: {},
};
const getters = {
userProfile: (state) => state.userProfile,
};
const actions = {
async getUserProfile({ commit }) {
console.log("here");
try {
let response = await apiService.get(apiUrls.PROFILE);
commit("setUserProfile", response.data.data);
} catch (error) {
console.log(error);
}
},
};
Thank you.
A user refresh means that the application will be re-executed. So basically main.js will be re-executed, App.vue re-created, etc.
That means just have to call your code in main.js or in a created lifecycle hook of any top-level component.
By top-level component I means any component which is created early in the app
apologies for the simple question, I'm really new to Vue/Nuxt/Vuex.
I am currently having a vuex store, I wish to be able to populate the list with an API call at the beginning (so that I would be able to access it on all pages of my app directly from the store vs instantiating it within a component).
store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, testArray) {
state.list = testArray
}
}
export const getters = {
getArray: state => {
return state.list
},
}
I essentially want to pre-populate state.list so that my components can call the data directly from vuex store. This would look something like that
db.collection("test").doc("test").get().then(doc=> {
let data = doc.data();
let array = data.array; // get array from API call
setListAsArray(); // put the array result into the list
});
I am looking for where to put this code (I assume inside store.js) and how to go about chaining this with the export. Thanks a lot in advance and sorry if it's a simple question.
(Edit) Context:
So why I am looking for this solution was because I used to commit the data (from the API call) to the store inside one of my Vue components - index.vue from my main page. This means that my data was initialized on this component, and if i go straight to another route, my data will not be available there.
This means: http://localhost:3000/ will have the data, if I routed to http://localhost:3000/test it will also have the data, BUT if i directly went straight to http://localhost:3000/test from a new window it will NOT have the data.
EDIT2:
Tried the suggestion with nuxtServerInit
Updated store.js
export const state = () => ({
list: [],
})
export const mutations = {
set(state, dealArray) {
state.list = dealArray
}
}
export const getters = {
allDeals: state => {
return state.list
},
}
export const actions = {
async nuxtServerInit({ commit }, { req }) {
// fetch your backend
const db = require("~/plugins/firebase.js").db;
let doc = await db.collection("test").doc("test").get();
let data = doc.data();
console.log("deals_array: ", data.deals_array); // nothing logged
commit('set', data.deals_array); // doesn't work
commit('deals/set', data.deals_array); // doesn't work
}
}
Tried actions with nuxtServerInit, but when logging store in another component it is an empty array. I tried to log the store in another component (while trying to access it), I got the following:
store.state: {
deals: {
list: []
}
}
I would suggest to either:
calling the fetch method in the default.vue layout or any page
use the nuxtServerInit action inside the store directly
fetch method
You can use the fetch method either in the default.vue layout where it is called every time for each page that is using the layout. Or define the fetch method on separate pages if you want to load specific data for individual pages.
<script>
export default {
data () {
return {}
},
async fetch ({store}) {
// fetch your backend
var list = await $axios.get("http://localhost:8000/list");
store.commit("set", list);
},
}
</script>
You can read more regarding the fetch method in the nuxtjs docs here
use the nuxtServerInit action inside the store directly
In your store.js add a new action:
import axios from 'axios';
actions: {
nuxtServerInit ({ commit }, { req }) {
// fetch your backend
var list = await axios.get("http://localhost:8000/list");
commit('set', list);
}
}
}
You can read more regarding the fetch method in the nuxtjs docs here
Hope this helps :)
Using the vuex-module-decorator I have a authenticate action that should mutate the state.
#Action
public authenticate(email: string, password: string): Promise<Principal> {
this.principal = null;
return authenticator
.authenticate(email, password)
.then(auth => {
const principal = new Principal(auth.username);
this.context.commit('setPrincipal', principal);
return principal;
})
.catch(error => {
this.context.commit('setError', error);
return error;
});
}
// mutations for error and principal
But this fail with the following message:
Unhandled promise rejection Error: "ERR_ACTION_ACCESS_UNDEFINED: Are you trying to access this.someMutation() or this.someGetter inside an #Action?
That works only in dynamic modules.
If not dynamic use this.context.commit("mutationName", payload) and this.context.getters["getterName"]
What I don't understand is that it works well with #MutationAction and async. However I miss the return type Promise<Principal>.
#MutationAction
public async authenticate(email: string, password: string) {
this.principal = null;
try {
const auth = await authenticator.authenticate(email, password);
return { principal: new Principal(auth.username), error: null };
} catch (ex) {
const error = ex as Error;
return { principal: null, error };
}
}
--
At this time I feel blocked and would like to have some help to implement an #Action that can mutate the state and return a specific type in a Promise.
Just add rawError option to the annotation so it becomes
#Action({rawError: true})
And it display error normally. this is because the the library "vuex-module-decorators" wrap error so by doing this you will able to get a RawError that you can work with
You can vote down this answer if you would like because it isn't answering the specific question being posed. Instead, I am going to suggest that if you are using typescript, then don't use vuex. I have spent the past month trying to learn vue /vuex and typescript. The one thing I am committed to is using typescript because I am a firm believer in the benefits of using typescript. I will never use raw javascript again.
If somebody would have told me to not use vuex from the beginning, I would have saved myself 3 of the past 4 weeks. So I am here to try and share that insight with others.
The key is Vue 3's new ref implementation. It is what really changes the game for vuex and typescript. It allows us to not have to rely on vuex to automatically wrap state in a reactive. Instead, we can do that ourselves with the ref construct in vue 3. Here is a small example from my app that uses ref and a typescript class where I was expecting to use vuex in the past.
NOTE1: the one thing you lose when using this approach is vuex dev tools.
NOTE2: I might be biased as I am ported 25,000 lines of typescript (with 7000 unit tests) from Knockout.js to Vue. Knockout.js was all about providing Observables (Vue's ref) and binding. Looking back, it was kind of ahead of its time, but it didn't get the following and support.
Ok, lets create a vuex module class that doesn't use vuex. Put this in appStore.ts. To simplify it will just include the user info and the id of the club the user is logged into. A user can switch clubs so there is an action to do that.
export class AppClass {
public loaded: Ref<boolean>;
public userId: Ref<number>;
public userFirstName: Ref<string>;
public userLastName: Ref<string>;
// Getters are computed if you want to use them in components
public userName: Ref<string>;
constructor() {
this.loaded = ref(false);
initializeFromServer()
.then(info: SomeTypeWithSettingsFromServer) => {
this.userId = ref(info.userId);
this.userFirstName = ref(info.userFirstName);
this.userLastName = ref(info.userLastName);
this.userName = computed<string>(() =>
return this.userFirstName.value + ' ' + this.userLastName.value;
}
}
.catch(/* do some error handling here */);
}
private initializeFromServer(): Promise<SomeTypeWithSettingsFromServer> {
return axios.get('url').then((response) => response.data);
}
// This is a getter that you don't need to be reactive
public fullName(): string {
return this.userFirstName.value + ' ' + this.userLastName.value;
}
public switchToClub(clubId: number): Promise<any> {
return axios.post('switch url')
.then((data: clubInfo) => {
// do some processing here
}
.catch(// do some error handling here);
}
}
export appModule = new AppClass();
Then when you want to access appModule anywhere, you end up doing this:
import { appModule } from 'AppStore';
...
if (appModule.loaded.value) {
const userName = appModule.fullName();
}
or in a compositionApi based component. This is what would replace mapActions etc.
<script lang="ts">
import { defineComponent } from '#vue/composition-api';
import { appModule } from '#/store/appStore';
import footer from './footer/footer.vue';
export default defineComponent({
name: 'App',
components: { sfooter: footer },
props: {},
setup() {
return { ...appModule }
}
});
</script>
and now you can use userId, userFirstName, userName etc in your template.
Hope that helps.
I just added the computed getter. I need to test if that is really needed. It might not be needed because you might be able to just reference fullName() in your template and since fullName() references the .value variables of the other refs, fullName might become a reference itself. But I have to check that out first.
I sugest this simple solution, work fine for me 👌:
// In SomeClassComponent.vue
import { getModule } from "vuex-module-decorators";
import YourModule from "#/store/YourModule";
someMethod() {
const moduleStore = getModule(YourModule, this.$store);
moduleStore.someAction();
}
If the action has parameters, put them.
Taken from: https://github.com/championswimmer/vuex-module-decorators/issues/86#issuecomment-464027359
I'm currently working on a new Vue.js application. It depends heavily on api calls to my backend database.
For a lot of things I use Vuex stores because it manages shared data between my components. When looking at other Vue projects on github I see a special vuex directory with files that handles all the actions, states and so on. So when a component has to call the API, it includes the actions file from the vuex directory.
But, for messages for example, I don't want to use Vuex because those data is only important for one specific view. I want to use the component specific data here. But here is my problem: I still need to query my api. But I shouldn't include the Vuex actions file. So in that way I should create a new actions file. This way I have a specific file with api actions for vuex and for single components.
How should I structure this? Creating a new directory 'api' that handles actions for both vuex data and component-specific data? Or separate it?
I am using axios as HTTP client for making api calls, I have created a gateways folder in my src folder and I have put files for each backend, creating axios instances, like following
myApi.js
import axios from 'axios'
export default axios.create({
baseURL: 'http://localhost:3000/api/v1',
timeout: 5000,
headers: {
'X-Auth-Token': 'f2b6637ddf355a476918940289c0be016a4fe99e3b69c83d',
'Content-Type': 'application/json'
}
})
Now in your component, You can have a function which will fetch data from the api like following:
methods: {
getProducts () {
myApi.get('products?id=' + prodId).then(response => this.product = response.data)
}
}
Similarly you can use this to get data for your vuex store as well.
Edited
If you are maintaining product related data in a dedicate vuex module,
you can dispatch an action from the method in component, which will internally call the backend API and populate data in the store, code will look something like following:
Code in component:
methods: {
getProducts (prodId) {
this.$store.dispatch('FETCH_PRODUCTS', prodId)
}
}
Code in vuex store:
import myApi from '../../gateways/my-api'
const state = {
products: []
}
const actions = {
FETCH_PRODUCTS: (state, prodId) => {
myApi.get('products?id=' + prodId).then(response => state.commit('SET_PRODUCTS', response))
}
}
// mutations
const mutations = {
SET_PRODUCTS: (state, data) => {
state.products = Object.assign({}, response.data)
}
}
const getters = {
}
export default {
state,
mutations,
actions,
getters
}
Note: vue-resource is retired ! Use something else, such as Axios.
I'm using mostly Vue Resource.I create services directory, and there put all connections to endpoints, for e.g PostService.js
import Vue from 'vue'
export default {
get(id) {
return Vue.http.get(`/api/post/${id}`)
},
create() {
return Vue.http.post('/api/posts')
}
// etc
}
Then in my file I'm importing that service and create method that would call method from service file
SomeView.vue
import PostService from '../services/PostService'
export default {
data() {
item: []
},
created() {
this.fetchItem()
},
methods: {
fetchItem() {
return PostService.get(to.params.id)
.then(result => {
this.item = result.json()
})
}
}
}
Based on concept of Belmin Bedak`s answer, i have wrapped it all into a simple library:
https://github.com/robsontenorio/vue-api-query
You can request your API like this:
All results
// GET /posts?filter[status]=ACTIVE
let post = await Post
.where('status', 'ACTIVE')
.get()
Specific result
// GET /posts/1
let post = await Post.find(1)
Editing
// PUT /posts/1
post.title = 'Awsome!'
post.save()
Relationships
// GET /users/1
let user = await User.find(1)
// GET users/1/posts
let posts = await user
.posts()
.get()
So, I am working on a pretty straight forward mobile app that has these scenes:
a list of people
person profile
add form
now, what I do, when I first load the LIST scene, I make an API call (I have a list component that I populate once I get results from the API... state.people).
All good here... when I tap on a person he's profile opens, no extra API calls, just passing the person object from state.people array.
All good here as well.
When I open ADD NEW person and send the form I make another API call (I post the information and get the new Object back)...
now the bit that is confusing to me.
What I would like is to update the LIST scene state.people by making another API call (get all again) after I get the OK confirmation from the POST.
and then navigate to Person's profile.
but, I am outside the scope of the LIST scene (I am in ADD NEW form). So, what would be the correct redux logic for this one?
The LIST component is already mounted... how do I communicate to LIST if I am on different scene
all these binding actions to components properties is confusing too... why can't redux act like a global hub that would always be accessible and would always retain it's state (at least on mobile app)
There is really a lack of real app examples... so far I see only very simplified examples that are not very useful on the grand scale to understand the whole flow
the store I have
/**
* STORE
*/
'use strict';
import { createStore, applyMiddleware } from 'redux';
import reducer from './_reducer';
import promiseMiddleware from 'redux-promise-middleware';
import thunkMiddleware from 'redux-thunk';
const store = createStore(reducer, {}, applyMiddleware(
thunkMiddleware,
promiseMiddleware()
));
export default store;
and the actions I have:
import * as constants from '../../constants/constants';
import request from '../../utils/request';
export const getAll = () => ({
type: constants.PEOPLE_FETCH,
payload: request(constants.API_PATH + 'person', {method: 'GET'})
});
export const search = (data, searchTerm) => ({
type: constants.PEOPLE_SEARCH,
payload: _filter(data, searchTerm)
});
export const save = (data) => ({
type: constants.PERSON_SAVE,
payload: request(constants.API_PATH + 'person', {method: 'POST', body: JSON.stringify(data)})
});
This can be an example architecture for your app:
Make a Redux store with list of people.
On initial API call, update the store to contain the list fetched by API call.
Wrap your app inside Provider and pass the store to the Provider.
Use connect and mapStateToProps and mapDispatchToProps to connect the Redux store to React state.
Whenever you update or insert new person, and get the new object, you need to dispatch an action which then goes to the reducer function which finally returns the updated Redux store, and dont worry with the re-rendering as React does the re-rendering itself whenever there is a change in a state.
I'll give a small example of store/actions/reducer, with a react + redux app.
store.js
import { applyMiddleware, compose, createStore } from 'redux'
import reducer from './reducer'
import logger from 'redux-logger'
// TOOD: add middleware
let finalCreateStore = compose(
applyMiddleware(logger())
)(createStore)
export default function configureStore (initialState = { todos: [] }) {
return finalCreateStore(reducer, initialState)
}
actions.js
let actions = {
helloWorld: function(data){
return {
type: 'HELLO_WORLD',
data: data
}
}
};
export default actions
reducer.js // Please read from Redux docs that reducers need to be pure functions
export default function myReducer(state = [], action) {
switch (action.type) {
case 'HELLO_WORLD':
return 'welcome' + data;
default:
return state;
}
}
Component.js (the React App) //In component whenever you receive new object, dispatch an action which will modify the store.
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import actions from '../redux/actions'
class App extends Component {
handleClick() {
store.dispath(action.helloWorld("jimmy")); //this dispatches an action, which goes to the reducer to change the state and adds 'welcome' before 'jimmy'
}
render() {
return (
<div onClick={this.handleClick.bind(this)}>
{store.getState()} //getState function to access store values
</div>
)
}
}
function mapStateToProps(state) {
return state
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch) //binds all the actions with dispatcher and returns them
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
This works like whenever you click the 'div' in the React Component, it calls the function, handleClick(), in which there is an action dispatch. This action then calls the reducer itself to update the store. I know you might get confused that how is store getting updated. Its a bit confusing but for that you need to follow a basic tutorial to explain React+Redux.
Please note this is not a runnable example, just a pseudocode. I recommend you to watch this youtube series to completely understand the redux stores+ react+webpack