I want to initialize my store with data from an API.
nuxtServerInit is not useful for my case because my Nuxt app target is set on 'static'.
I'm trying to implement a router middleware to fetch data asynchronously but without blocking the main thread. In other word, the middleware lets the page to load (mounted) but updates store whenever the data has been fetched.
The problem with an asynchronous middleware is that, it will not resolved until data is fetched.
And, the problem with a synchronous middleware is that, my store will not updated.
here is my implementation:
middleware/initializer.js
export default function({ store }) {
store.dispatch('app/initializeApp').then(() => console.log('app initialized'))
}
store/app.js
initializeApp({ dispatch }) {
return dispatch('fetchOptions')
},
fetchOptions({ commit }) {
return this.$axios.$post(someEndpoint).then(({ options }) => {
commit('setAppOptions', options)
return true
}).catch(() => false)
}
I don't understand why the console log works but my store is not updated.
Related
In my application I need some data to be loaded inside the VueX store before routing starts (for example user sessions).
An example of a race condition would be the following:
// In routes definition
{
name: 'login',
path: '/login',
component: Login,
meta: {
goToIndexIf: () => store.getters['auth/loggedIn']
}
}
In this situation the route guard might be executed before the user had been received from the server.
Using conditional rendering did not help as the route guards are executed with or without a <router-view v-if="storeReady"> within the rendered template.
How can I make all my routing wait on some asynchronous data?
The solution is simple. Add an init or equivalent Vuex action to the relevant parts of your store.
It should return a Promise of all the requests for data that your application absolutely needs*:
init ({ dispatch }) { // Could also be async and use await instead of return
return Promise.all([
dispatch('getUserSession'), // Using another action
dispatch('auth/init'), // In another module
fetch('tehKittenz') // With the native fetch API
// ...
])
}
The above code can use anything that returns a Promise.
Then just create a global navigation guard in your router using beforeEach.
This guard will wait on the promise generated by a dispatch to the store.
// In your router initialization code
const storeInit = store.dispatch('init')
// Before all other beforeEach
router.beforeEach((to, from, next) => {
storeInit.then(next)
.catch(e => {
// Handle error
})
})
This way, if routing happens before the store is fully loaded the router will simply wait.
If routing happens after, the promise will already be in a fulfilled state and routing will proceed.
Don't forget to use something like conditional rendering so as not to display a blank screen while routing is waiting on the data.
*: This will prevent all routing and navigation as long as the data is being fetched. Be careful.
Since this question was first asked, vue-router (v3.5.1) has exposed the ability to check for the initial navigation to perform operations like this and run on the first route only.
Compare from to VueRouter.START_LOCATION.
import VueRouter from 'vue-router'
const router = new VueRouter({
// ...
})
router.beforeEach((to, from, next) => {
if (from === VueRouter.START_LOCATION) {
// initial navigation, handle Vuex initialization/hydration.
initalizeOrWait().then((isLoggedIn) => {
// handle navigation or pass to guard how it fits your needs here etc.
next();
});
} else {
next();
}
})
What i did and worked fine for me is wrapping my Vue instance (new Vue({... })) inside .then() "Promise".. this promise would resolve(null) if everything is fine and resolve an error when an errors occurs so i can render the vue instance conditionally based on the error
here i call my async function and wait until it loads the store and then initialize my app
my async function that uses the token to get me the data
doing so will allow the routes guards who work on the fetched store data to work properly
Hope that helps and sorry if my english is bad :)
I am new in vue js . I want to call api . it is perfectly working. but i want to put condition on that if api response is this than vue js array is updated .
this is my code . but it gives me error "do not mutate vuex store state outside mutation handlers."
How to handle this?
axios.post(baseUrl+'/api/v1/addProductWishlist',pitem,
{headers: {
'Authorization': 'Bearer ' + x,
'Content-Type':'application/json'
}
}).then(r => {
if(r.data.status == 202)
{
}
else{
state.cartItems.push(payload);
}
});
use Mutation to modify state, do not redirect change state instance
for more detail: https://vuex.vuejs.org/guide/mutations.html
Notice that you don't follow the correct pattern of using Vuex.
You should update the state ONLY from within a mutation. It is an important part in state management in Vuex (as well as in other frameworks). Not following this concept might break the reactivity Vuex maintains and make a big app extremely unpredictable since you can't follow which part of the app have changed a value in your state.
A good practice would be to separate mutations, actions and api calls to 3 different files. The action might be your functionality manager - create an action that calls the api file function (that only executes the axios api call), and after getting back the response calling the mutation from the mutations file to update the state accordingly.
// actions.js
{
myAction: async ({ commit }) => {
const response = await myApiCall();
if (response.data.status == 202) { ... }
else {
commit('ADD_CART_ITEM', response.item)
}
}
}
// api.js
export function myApiCall() {
return axios.post(...)
}
// mutations.js
{
ADD_CART_ITEM: (state, payload) => state.cartItems.push(payload);
}
I was working with redux-thunk and superagent npm for jwt authentication and i would want to know how to implement post calls using thunk-middleware in the actions.js file and not in the main reducer.js file
There's a couple of different ways to go about it, but I personally like to use the axios library. Axios essentially just assists with making an API request and parsing the data back from the API into json.
In your actions.js file.
export const authenticateUser = () => {
return (dispatch, getState) => {
//get the token from the reducer
const jwtToken = getState().jwtTokenReducer.tokenKey
axios.post("/api/authenticate-user", jwtToken) //jwtToken passed into request
.then((res) =>){
dispatch({
type: "AUTHENTICATE_USER",
payload: res.data
})
}
.catch((errors) => {
dispatch({
type: "ERRORS",
payload: errors.response.data
})
})
}
}
So take a look at the above syntax. Typically when you define an action-creator, you setup a function that returns an action (object). But thanks to redux-thunk, you can now setup your action-creators to return functions with dispatch as an argument.
So in your returned function you can define certain logic, like making a request to an API like we did up there. Then we can take that data by using the .then promise handler and use it as a payload for an action that we would explicitly dispatch to our reducers.
In my application I need some data to be loaded inside the VueX store before routing starts (for example user sessions).
An example of a race condition would be the following:
// In routes definition
{
name: 'login',
path: '/login',
component: Login,
meta: {
goToIndexIf: () => store.getters['auth/loggedIn']
}
}
In this situation the route guard might be executed before the user had been received from the server.
Using conditional rendering did not help as the route guards are executed with or without a <router-view v-if="storeReady"> within the rendered template.
How can I make all my routing wait on some asynchronous data?
The solution is simple. Add an init or equivalent Vuex action to the relevant parts of your store.
It should return a Promise of all the requests for data that your application absolutely needs*:
init ({ dispatch }) { // Could also be async and use await instead of return
return Promise.all([
dispatch('getUserSession'), // Using another action
dispatch('auth/init'), // In another module
fetch('tehKittenz') // With the native fetch API
// ...
])
}
The above code can use anything that returns a Promise.
Then just create a global navigation guard in your router using beforeEach.
This guard will wait on the promise generated by a dispatch to the store.
// In your router initialization code
const storeInit = store.dispatch('init')
// Before all other beforeEach
router.beforeEach((to, from, next) => {
storeInit.then(next)
.catch(e => {
// Handle error
})
})
This way, if routing happens before the store is fully loaded the router will simply wait.
If routing happens after, the promise will already be in a fulfilled state and routing will proceed.
Don't forget to use something like conditional rendering so as not to display a blank screen while routing is waiting on the data.
*: This will prevent all routing and navigation as long as the data is being fetched. Be careful.
Since this question was first asked, vue-router (v3.5.1) has exposed the ability to check for the initial navigation to perform operations like this and run on the first route only.
Compare from to VueRouter.START_LOCATION.
import VueRouter from 'vue-router'
const router = new VueRouter({
// ...
})
router.beforeEach((to, from, next) => {
if (from === VueRouter.START_LOCATION) {
// initial navigation, handle Vuex initialization/hydration.
initalizeOrWait().then((isLoggedIn) => {
// handle navigation or pass to guard how it fits your needs here etc.
next();
});
} else {
next();
}
})
What i did and worked fine for me is wrapping my Vue instance (new Vue({... })) inside .then() "Promise".. this promise would resolve(null) if everything is fine and resolve an error when an errors occurs so i can render the vue instance conditionally based on the error
here i call my async function and wait until it loads the store and then initialize my app
my async function that uses the token to get me the data
doing so will allow the routes guards who work on the fetched store data to work properly
Hope that helps and sorry if my english is bad :)
In my application, we have a somewhat long-running "provisioning" task that asynchronously completes that we essentially have to use polling to detect that it has finished. So that a user isn't blocked from using the app while this takes place, we kick that off at the beginning of the user interaction flow. However, at the end of the interaction flow, the user takes an action that needs to await that provisioning task if it hasn't completed. Here's a sort of sequence diagram to illustrate.
[provisioning start][poll for completion....]
[user interactions] [blocked action....][post provisioning task]
The problem I'm having is figuring out an idiomatic way to do this properly in Redux. Here are some things I've considered doing:
Have a "requests" reducer, where I'd store the Promise for that long-running provisioning task, then when [blocked action] executes, it simply awaits the promise. The problem with this is that Redux specifically requests that all store state is serializable, so it seems that things like Promises are not welcome there.
Subscribe to the store in the [blocked action] action creator and look for data that signals the provisioning has completed. Unfortunately, redux-thunk, which I'm pretty fond of, doesn't contain a reference to the store, just its dispatch and getState methods.
Having my React component wait for the provisioning to complete before executing [blocked action]. I don't like this because it adds too much action sequencing logic awareness to my view layer.
Since none of these seem like great options, I'm currently going with:
Store the provisioning Promise in a module property and essentially do option #1 without getting the Promise from the store. I'm not wild about this, since it moves state outside of the redux loop, but it seems like the easiest option.
Is there a more idiomatic way to achieve this with redux-thunk? If there's another asynchronous middleware that makes this cleaner, I'm willing to consider, though I'm already quite invested in redux-thunk.
There are several redux-promise-middleware options that let you dispatch promise actions and have the middleware consume them and emit PENDING,RESOLVED|REJECTED actions as the promise state changes. If you are working with promises and Redux, you might want to look at one of them. However they won't help you with this specific problem.
I think redux-saga is a middleware well suited to help you with this scenario. I've not yet used it personally so I can't provide an example.
Another, perhaps simpler, option is redux-tap. Use tap to expose the stream of actions that your app can subscribe to. Have a blocked action creator thunk subscribe to that stream and await the action that signals that the promise has completed (it should of course first check the contents of the store via getState to see if the promise completed before this blocked action was dispatched). Something like this:
// ========= configureStore.js
import ee from 'event-emitter';
// ...
export const actionStream = ee();
// ...
const emitActions = tap(({type} => type, (type, action) => actionStream.emit(type, action);
// install emitActions middleware to run *after* thunk (so that it does not see thunk actions but only primitive actions)
// =========== your action module.js
import {actionStream} from './configureStore';
export function blockedAction(arg) {
return (dispatch, getState) => {
if (getState().initDone) {
return dispatch({type: "blockedAction", payload: arg});
}
// wait for init action
actionStream.once("initAction", initAction => dispatch({type: "blockedAction", payload: arg}));
};
}
Keep in mind that it is not a binary "either thunk or xxx middleware" choice. You can load thunk as well as many other middlewares. In one app, I use:
thunk
a promise middleware
console logging middleware
custom middleware for batching updates
custom middleware for persisting actions to IndexedDB
custom middleware for pausing the action stream
middleware to prevent recursive store notifications
Without any middleware other than thunk, here is another option:
Option 5 - combine your option 2 with your option 4: Store the promise somewhere globally. Have the blocked action creator thunk await the promise before dispatching the raw action. To minimize the "state outside of the store", you can also have the promise action emit actions into the store to keep the store updated (possibly using a promise middleware)
I think this is simpler—unless I'm missing something—if we're more rigorous in thinking about the UI as a function of state. At every point in your interaction flow there are a few things that are either true or false:
Provisioning has started/not started
Provisioning has finished/not finished
User action is pending/is not pending
These three facts are enough to tell us what the user should be seeing on their screen at a given moment, so our state should include these three facts:
const initialState = {
provisioningStarted: false,
provisioningDone: false,
laterActionIsPending: false,
// ...
};
Using redux-thunk we can handle the provisioning action:
function startProvisioning() {
return dispatch => {
dispatch({ type: START_PROVISIONING });
pollProvisioning().then(postSetupInfo => {
dispatch({ type: DONE_PROVISIONING, postSetupInfo });
});
};
}
...and in our reducer:
function appReducer(state, action) {
// ...
switch (action.type) {
case START_PROVISIONING:
return { ...state, provisioningStarted: true };
case DONE_PROVISIONING:
return { ...state, provisioningDone: true, postSetupInfo: action.postSetupInfo };
case LATER_ACTION_PENDING:
return { ...state, laterActionIsPending: true };
// ...
}
}
As I said, this should be enough to tell us what the user should be seeing:
const Provisioner = ({provisioningStarted, provisioningDone, /* ... */}) => {
if (provisioningStarted) {
if (laterActionIsPending) {
if (provisioningDone) {
return <p>Finished! Here's your info: {postSetupInfo}</p>;
}
return <p>Provisioning...</p>;
}
return <button type="button" onClick={doAction}>Do action</button>;
}
return <button type="button" onClick={startProvisioning}>Start provisioning</button>;
};
export default connect(mapStateToProps, mapDispatchToProps)(Provisioner);
Not exactly redux-thunk but functionally equivalent is using
redux-background. A package I created to make the process easier.
It goes something like this:
import { startJob } from 'redux-background';
import store from './store';
store.dispatch(startJob('provision', function(job, dispatch, getState) {
const { progress, data } = job;
return new Promise((resolve, reject) => {
// Do some async stuff
// Report progress
progress(10);
// Return value
return 10;
})
}, { data: '...' } );
On your state when it ends you should have something like this
{
background: {
provision: {
active: true,
running: false,
value: 10,
error: null,
/* and lot of more metadata */
....
},
}
}