I'm using Vue resource to connect to my backend api. I have a form component which I use for both creating a new resource item and modifying existing resource items. The form works fine, but when I want to save the form, it needs to use the proper http method for the api call. If I am creating a new item, it should use the POST method, and if I'm updating an existing item it should use the PUT method. Right now, my form save method looks something like this:
if (this.itemId > 0) { // Update existing item
myresource.update({id: this.itemId}, this.item).then(response => {
//...
}, response => {
//...
});
}
else { // Post new item
myresource.save({}, this.item).then(response => {
//...
}, response => {
//...
});
}
Basically, I have to use an if statement to check whether to use the update or save resource functions, then the success/fail promises both use the same code. Is there some way to combine the two methods above with something like this:
var method = this.itemId ? 'PUT' : 'POST';
myresource.request(method, {id: this.itemId}, this.item).then(response => {
//...
}, response => {
//...
});
The above code obviously doesn't work, but is there a similar way to accomplish this without using an if statement and repeating my success/fail promises for each request type?
One simple option would be to create the request based on the conditions and then connect the remaining promises in a single chain:
const request = (this.itemId > 0) ? myresource.update ? myresource.save;
request({
id: this.itemId // Make the save call ignore the id
}, this.item).then(response => {
// Shared code
});
Related
I am adapting a ReactNative mobile app to ReactNative Web. The app is done with react-navigation.
Currently, every time I set the params of a route (either through navigate or setParams), those params show in the URL. I end up with bad looking URLs like so:
http://localhost:19006/me/undefined?user=%5Bobject%20Object%5D
Either that or the URL contains irrelevant data for the user and generally looks messy.
Is there a way to not show the route params inside the URL?
You should re-consider if params is an appropriate place to have this data if you don't want in the URL. That you think that the URL contains irrelevant data is a sign that the data doesn't belong in the navigation state.
If you visit a screen directly from a URL, it should render the same as when you navigated to it porgrammatically. If you're passing something in params, it means that that information is needed for the screen to render correctly, and if you remove it from the URL, then that information is lost. Consider that it's possible to open the page directly from a URL, either on Web or with a deep link. If the required data isn't available, then it's not going to work.
In your case, seems like you're passing a full user object (maybe not, hard to say without code). Ideally, this object should be in your global store instead of params, and you should only pass the user id in params. Then you should gran the full object from your global store using that id, or trigger a data fetch if the objet isn't fetched yet (if needed).
You didn't specify the version of React Navigation in your question. In v5, you can customize how your params are stringified to URLs, and how they are parsed back using the stringify and parse options:
const linking = {
screens: {
Profile: {
path: 'user/:id',
parse: {
id: (id) => id.replace(/^#/, '')
},
stringify: {
id: (id) => `#{id}`,
},
},
},
};
This should help you with making URLs look prettier, and handle cases where params are not simple strings. However you can't omit params from the URL altogether since they are necessary data for the screen to render.
More details: https://reactnavigation.org/docs/configuring-links#passing-params
satya164's answer is definitely the proper path to follow. Just wanted to also post this solution as it is a direct answer to the question, even if not the most advisable.
import { getPathFromState} from "#react-navigation/native";
const linking = {
screens: {
...
},
getPathFromState(state, config){
let path = getPathFromState(state, config);
const index = path.indexOf("?")
if(index>=0){
path = path.substr(0, index);
}
return path;
}
};
NOTE: To use this solution you need to make sure that every object and function parameters are optional, otherwise if you reload the page you will get an error.
I removed every object and function from the url adding this custom getPathFromState to linkingOptions:
const linking = {
config: {
screens: {
...
}
},
getPathFromState: (state, options) => {
const cleanState = {
...state,
routes: state.routes.map(route => {
if(!route.params) {
return route
}
const cleanParams = {}
for(const param in route.params) {
const value = route.params[param]
if(typeof value !== "object" && typeof value !== "function") {
cleanParams[param] = value
}
}
return {
...route,
params: cleanParams,
}
}),
}
return getPathFromState(cleanState, options) //imported from #react-navigation/native
},
}
You can pass the parameters the component needs as a prop
like this
navigation.navigate('Details', {
itemId: 86,
otherParam: 'anything you want here',
});
then in Details Component
const { itemId , otherParam} = route.params;
Alternatively, if you use Redux/Mobx or any other global state management
You can pull the data from there and not pass it through the URL
then get data with the help connect or with useSelector hooks
I've been searching around for possible answers to my question but I couldn't find anymore suggestions.
The structure of my project is as follows. I have a PoolMainPage where I show some information of the activePool Object. Within the PoolMainPage there are options to direct to subpages (TeamSelector and PoolStandings), which also require information of the activePoolObject.
The activePool Object is fetched from an endpoint in the Vuex store. The code for that is the following:
const actions = {
getActivePool({ commit }) {
const config = {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
withCredentials: true
}
const activePoolId = localStorage.getItem('activePoolId')
if (activePoolId) {
return axios.get('/pools/pool-information?poolId=' + activePoolId, config)
.then((response) => {
commit('SET_ACTIVE_POOL', response.data)
return response
})
.catch((error) => {
return Promise.reject(error)
})
} else {
commit('SET_ACTIVE_POOL', null);
return;
}
}
}
The endpoint does it's job and returns the activePool object. In my PoolMainPage component (as well as in the subcomponents) I want to call this action and fetch the object, using:
created() {
if (!this.$store.getters.activePool) {
this.$store.dispatch("getActivePool");
}
},
I added the check to prevent that the endpoint is called everytime the page is refreshed and the activePool is already set. To actually load the activePool into the component, I created a computed property:
computed: {
activePool() {
return this.$store.getters.activePool;
},
},
This all works when the endpoint has returned its data, but before that I receive an error on another computed property, which is dependent on the activePool object:
maxToSpend() {
return this.activePool.inGameBudget;
},
Question 1: how do I make sure that maxToSpend does not compute until the activePool actually set? I can simply add an additional check if (this.activePool) {, but then I would have to do that for all of the computed properties.
Question 2: I don't know if this is possible, but how do make sure that I don't have to add the code to fetch the activePool from the endpoint and get it using the computed property within each of the components that I created: TeamSelector and PoolStandings?
All help/suggestions are appreciated! Let me know if anything is unclear or requires some additional information.
Thanks!
Jeroen
How do I make sure that maxToSpend does not compute until the activePool actually set?
Basically you cannot do that. The computed properties are compute right after component create. See Lifecycle Diagram, computed properties are compute at Init injections & reactivity state.
I can simply add an additional check if (this.activePool) {, but then I would have to do that for all of the computed properties.
You case use Getters:
state: {
...
},
getters: {
maxToSpend: state => {
if (!state.activePool) return
return state.activePool.inGameBudget
}
},
actions: {
...
}
Then you can use as:
computed: {
maxToSpend () {
return this.$store.getters.maxToSpend
}
}
I don't know if this is possible, but how do make sure that I don't have to add the code to fetch the activePool from the endpoint and get it using the computed property within each of the components that I created: TeamSelector and PoolStandings?
Basically no. But if both TeamSelector and PoolStandings have common a parent (might be PoolMainPage?) then you can call it only once from that parent.
In my opinion the way to explicitly dispatch mandatory action for every page its needed it's not a bad idea.
You can mount activePool getter to avoid errors when accessing nested properties. Like:
// in getters.js
const activePool = (state) => ({
...state.activePool,
maxToSpend: state.activePool && state.activePool.inGameBudget,
})
activePool.inGameBudget will always be there, regardless the Promise. So now you can access this.activePool.inGameBudget; in .vue file and you wont get the error.
I am building an SPA and I have a couple of different forms that submit data to an API. I am using axios for the ajax calls and have built a wrapper class around it for my use-case, called api. Inside that class I handle errors thrown by that instance.
The problem is I was storing an instance of the api class in each form's state. I later realized that functions shouldn't live in the state due to serialization.
The reasoning behind having the api class in the state was so that all of the children components of a form could access the errors and display their respective error along with removing the error on update.
One solution could be using an axios interceptor and commit all errors to a global errors module. But then I wouldn't know which errors belong to which form, in case two forms (or other requests) were submitted at the same time. I could of course save the errors in regard to the request URI, but then I would also have to take the request method into consideration.
reportError(state, { uri, method, errors }) {
state.errors[uri + '#' + method] = errors;
}
Then I could have a getter like:
getErrorsByRequest: state => ({ uri, method }) => {
return ...
}
But this feels unnecessarily awkward.
Since what I am trying to achieve is most likely very common, I am wondering, how do I sanely handle ajax errors reusably with Vuex?
I was checking for my old projects, and i did something similar:
This is my axios instance interceptor:
axiosInstance.interceptors.response.use(response => response, error => {
const { status } = error.response
...
...
// data invalid (Unprocessable Entity)
if (status === 422) {
// errors list from response
const dataErrors = error.response.data.errors
let objErrors = {}
// joining just the first error from array errors as value for each prop
for (let key in dataErrors) {
objErrors[key] = dataErrors[key].join()
}
// commiting to errors module
store.commit('errors/SET_ERRORS', objErrors)
}
...
...
})
Here my store module errors:
export const state = {
form_errors: {}
}
export const mutations = {
SET_ERRORS: (state, errors) => { state.form_errors = errors },
CLEAN_ERRORS: (state) => { state.form_errors = {} }
}
Using on components:
computed: {
...mapState('errors', ['form_errors'])
}
Using on form template:
<q-input
v-model="form.description"
:error-message="form_errors.description"
:error="!!form_errors.description"
/>
Неllo! I need help.
I have these data - tree terms in the store.
How to write a post-request to the server to sync terms in Vue components methods, when the mockdata is off.
P.S. Нow can use Axios?
methods: {
onTermCreate(newTerm) {
const actionName = this.getModuleActionName('term', ACTIONS_NAMES.CREATE_TERM);
this.$store.dispatch(actionName, newTerm).then(() => {
// TODO add post request to sync terms state with server, then reset form //
this.showPostRequestData();
this.resetForm();
})
},
addNewDataToExistingTerm(existingTerm, newTerm) {
existingTerm.parents.push(newTerm);
const actionName = this.getModuleActionName('term', ACTIONS_NAMES.UPDATE_TERM);
this.$store.dispatch(actionName, existingTerm).then(() => {
// TODO add post request to sync terms state with server, then reset form //
this.showPostRequestData();
this.resetForm();
})
}
}
Put all your API calls inside Actions in your Vuex store. This way every change that gets committed to your state only comes from inside the store and makes it easier for you to debug and make the code easier to understand.
Now to use axios you should do something like this:
state = {
//
},
getters = {
//
},
mutations = {
SET_TERM : (state,payload) => {
state.term = payload
}
},
actions = {
CREATE_TERM : async (context,payload) => {
let { data } = await Axios.get('/path/to/api/')
context.commit('SET_TODO',data)
}
}
Please read this awesome article for more explanations and details.
I'm trying to wait for certain strings in a sort of dictionary containing all the text for buttons, sections, labels etc.
I start out by sending a list of default strings to a controller that registers all the strings with my CMS in case those specific values do not already exist. After that I return a new object containing my "dictionaries", but with the correct values for the current language.
I run the call with an event listener that triggers a dispatch() on window.onload, and then add the data to a Vuex module state. I then add it to a computed prop.
computed: {
cartDictionary() {
return this.$store.state.dictionaries.myDictionaries['cart']
}
}
So now here's the problem: In my template i try to get the values from the cartDictionaryprop, which is an array.
<h2 class="checkout-section__header" v-html="cartDictionary['Cart.Heading']"></h2>
But when the component renders, the prop doesn't yet have a value since it's waiting for the AJAX call to finish. And so of course I get a cannot read property of undefined error.
Any ideas on how to work around this? I would like to have the dictionaries accessible through a global object instead of passing everything down through props since it's built using atomic design and it would be insanely tedious.
EDIT:
Adding more code for clarification.
My module:
const dictionaryModule = {
namespaced: true,
state: {
dictionaries: []
},
mutations: {
setDictionaries (state, payload) {
state.dictionaries = payload
}
},
actions: {
getDictionaries ({commit}) {
return new Promise((resolve, reject) => {
Dictionaries.init().then(response => {
commit('setDictionaries', response)
resolve(response)
})
})
}
}
}
My Store:
const store = new Vuex.Store({
modules: {
cart: cartModule,
search: searchModule,
checkout: checkoutModule,
filter: filterModule,
product: productModule,
dictionaries: dictionaryModule
}
})
window.addEventListener('load', function () {
store.dispatch('dictionaries/getDictionaries')
})
I think you can watch cartDictionary and set another data variable.
like this
<h2 class="checkout-section__header" v-html="cartHeading"></h2>
data () {
return {
cartHeading: ''
}
},
watch: {
'cartDictionary': function (after, before) {
if (after) {
this.cartHeading = after
}
}
}
Because this.$store.state.dictionaries.myDictionarie is undefined at the the begining, vuejs can't map myDictionarie['core']. That's why your code is not working.
You can do this also
state: {
dictionaries: {
myDictionaries: {}
}
}
and set the dictionaries key values during resolve.
I also would have liked to see some more of your code, but as i can't comment your questions (you need rep > 50), here it goes...
I have two general suggestions:
Did you setup your action correctly? Mutations are always synchronous while actions allow for asynchronous operations. So, if you http client returns a promise (axios does, for example), you should await the result in your action before calling the respective mutation. See this chapter in the official vuex-docs: https://vuex.vuejs.org/guide/actions.html
You shouldn't be using something like window.onload but use the hooks provided by Vue.js instead. Check this: https://v2.vuejs.org/v2/guide/instance.html#Lifecycle-Diagram
EDIT: As a third suggestion: Check, whether action and mutation are called properly. If they are handled in their own module, you have to register the module to the state.