Axios in Vuex Store returning promise and not data - axios

I'm not sure why I can't get the data I want from this axios.get in my Vuex store.
I've setup this action to commit a change to my mutation like this:
mutations: {
updateShop: (state, payload ) => {
state.shop = payload;
return state.shop;
}
},
actions: {
getShop: ({ commit }) => {
return axios.get("/admin/wine_app/shops").then(response => {
debugger; // I DO have data here??
commit('updateShop', response.data);
});
}
}
But when I stop it with that debugger I DO have the data, but when I use the getShop action in an component I see the promise being returned.
Any idea why?
EDIT:
It MIGHT just not be ready!! I'm seeing this in the console
Promise {<pending>}
__proto__: Promise
[[PromiseStatus]]: "pending"
[[PromiseValue]]: undefined

make the action getShop async
getShop: async ({ commit }) => {
const response = await axios.get("/admin/wine_app/shops");
debugger; // I DO have data here??
commit('updateShop', response.data);
}
await the action where you call it
await this.$store.dispatch('getShop')
Use the shop state prop in your code
this.$store.state.shop
or use mapState if you would like to use several state props.
Also make sure not to return any data from mutations. They must change state and not return its props.

The reason you are seeing a promise is because your action is returning the axios call. Remove the return in both your mutation and action methods.
Your action method uses axios to retrieve the data. This then commits your mutation with the response data. Your mutation method updates the state with that data. At this point your state.shop is updated.
Within your Vue components you can access that data by accessing the state as a computed property.
computed: {
shop() {
return this.$store.state.shop
}
// Or use mapState
...mapState(['shop'])
}
Whenever your state changes this should update in your components due to reactivity.

Related

Suspense returns data too fast in #tanstack/react-query v4

I am upgrading a React app from react-query v3 to #tanstack/react-query v4.
Almost everything works, but I'm having a problem with Suspense.
I have a react component:
const WrapperPageEdit: React.FC<MyProps> = ({
pageUuid,
redirect,
}: MyProps) => {
const FormPage = React.lazy(() => import('./FormPage'));
const { data } = usePageView(pageUuid);
if (data?.[0]) {
const pageObjectToEdit= data[0];
const content = pageObjectToEdit.myStuff.content;
return (
<Suspense
fallback={<Trans id="loading.editor">Loading the editor...</Trans>}
>
<FormPage
id={uuid}
content={content}
redirect={redirect}
/>
</Suspense>
);
}
return <p>No data.</p>;
};
And here's my query:
export function usePageView(
uuid: string,
): UseQueryResult<DrupalPage[], Error> {
return useQuery<DrupalPage[], Error>(
queryKeyUsePageView(uuid),
async () => {
return fetchAnon(getPageByPageUuid(uuid));
},
{
cacheTime: YEAR_MILLISECONDS,
staleTime: YEAR_MILLISECONDS,
onSuccess: (data) => {
if (data?.[0]) {
data.map((element) => processResult(element));
}
},
},
);
}
This works in v3 but fails in v4 with the following error:
TypeError: Cannot read properties of undefined (reading 'content')
The reason the property is undefined is because that property is set by the processing in onSuccess (data.map).
The issue appears to be that in v4, the component WrapperPageEdit is refreshed before onSuccess in the usePageView query has finished processing, whereas in v3, the component WrapperPageEdit is not refreshed until the onSuccess data.map is complete.
How can I correctly fix this? I can write some additional code to try to check whether the onSuccess data.map is complete, but since react-query handled this automatically in v3, I'd like to rewrite my code in v4 so that it is the same.
The problem is likely that you are mutating the data in onSuccess. Directly modifying data in callbacks is not a good idea. Instead, do your transformation for example directly in the queryFn:
async () => {
const data = fetchAnon(getPageByPageUuid(uuid));
if (data?.[0]) {
data.map((element) => processResult(element));
}
return data
},
other good places to do data transformation is e.g. the select option, but it should always happen in an immutable way, because otherwise, you are overwriting the cached data inadvertently. React prefers updates to be immutable.

Amplify Datastore subscription cost

I am trying to understand the cost of Datastore. It seems that it subscribes to all Mutations. So if there are 50 users, then each message will be send 50 times, even if it not required.
As each real time mutation costs money, we will be paying unnecessary 49 times for this real time message mutation.
Also , it seems to me SyncExpression doesn't have any effect on this Subscription.
I am really stuck here. It will be great of someone can clarify
Amplify generates the datastore boilerplate code for you, but you still need to call it. You won't pay for every user and every mutation.
You will only subscribe to a mutation (explicitly call the code to listen for changes) on a per-user basis for things that user is interested in. e.g. if you are viewing a TODO item, you'd subscribe the user to that item and they'll immediately see if someone else modify it on another device.
UPDATE
Long story... I was triggering back-end computation via GraphQL by making a lambda resolver. The computation took too long and the GQL call would timeout. I updated the code so the GQL call called itself asynchronously (re-trigger the lambda), and returned immediately. Then when the long-running task completed in the spun-up lambda, I updated the a record in the database.
I update the record using AppSync instead of direct GQL so it would trigger mutations, and in the react client, I listen to a mutation for the specific record that will be updated. This way, there is just 1 user listening (if they've triggered the long running action) and that user is only notified about changes to the single DB record they're interested in, and not receiving other user's updates.
I don't know if all this is applicable to your situation. The code snippets below may help you, but they're somewhat out of context.
// In amplify/backend/api/projectname/schema.graphql
type Subscription {
onCouponWithIdUpdated(id: ID!): Coupon #aws_subscribe(mutations: ["updateCoupon"])
}
// In my useSendCoupon hook...
// Subscribe to coupon updates
useEffect(() => {
if (0 === couponId) {
return
}
console.log(`subscribe to coupon updates for couponId:`, couponId)
const onCouponWithIdUpdated = /* GraphQL */ `
subscription OnCouponWithIdUpdated($id: ID!) {
onCouponWithIdUpdated(id: $id) {
id
proofLink
owner
}
}
`
const subscription = API
.graphql(graphqlOperation(onCouponWithIdUpdated, { id: couponId }))
.subscribe({
next: ({ provider, value }) => {
const coupon = value.data.onCouponWithIdUpdated
//console.log(`Proof Link:`, coupon.proofLink)
setProofLinks([coupon.proofLink])
setSendCouponState(COUPON_STATE_PREVIEW_SUCCESS)
},
error: error => console.warn(error)
})
console.log('subscribed: ', subscription)
return () => {
console.log(`unsubscribe to coupon updates`)
subscription.unsubscribe()
}
}, [couponId])
// inside a lambda...
const updateCouponWithProof = async (authorization, couponId, proofLink) => {
const initializeClient = () => new AWSAppSyncClient({
url: process.env.API_XXXX_GRAPHQLAPIENDPOINTOUTPUT,
region: process.env.REGION,
auth: {
type: AUTH_TYPE.AMAZON_COGNITO_USER_POOLS,
jwtToken: authorization
},
disableOffline: true,
})
const executeMutation = async (mutation, operationName, variables) => {
const client = initializeClient()
try {
const response = await client.mutate({
mutation: gql(mutation),
variables,
fetchPolicy: "no-cache",
})
return response.data[operationName]
} catch (err) {
console.log("Error while trying to mutate data", err)
throw JSON.stringify(err)
}
}
const updateCoupon = /* GraphQL */ `
mutation UpdateCoupon(
$input: UpdateCouponInput!
$condition: ModelCouponConditionInput
) {
updateCoupon(input: $input, condition: $condition) {
id
proofLink
owner
}
}
`
const variables = { input: { id: couponId, proofLink } }
try {
return await executeMutation(updateCoupon, 'updateCoupon', variables)
} catch (error) {
console.log(`executeMutation error`, error)
}
}

How to display API data using fetch on Dialogflow GUI

I want to display data from OMDB API on Dialogflow GUI but it's not happening. Data is being displayed fine on Google Cloud Console.
function infoHandler(agent){
let movieName = agent.parameters.movie;
agent.add(`The information for ${movieName} is as follow`);
fetch('http://www.omdbapi.com/?apikey=e255decd%20&s='+ movieName)
.then(result => result.json())
.then((json) => {
let id = json.Search[0].imdbID;
fetch('http://www.omdbapi.com/?apikey=e255decd%20&i=' + id)
.then(result => result.json())
.then((json) => {
agent.add(json.Title + json.Plot + json.imdbRatinng);
return;
}).catch((ex) => {
console.log(ex);
});
})
.catch((e) => {console.log(e);});
The issue is that fetch() causes an asynchronous operation. However, there is nothing that indicates to the Dialogflow handler dispatcher that it is asynchronous and that it should wait for it to complete before sending back a reply. To do that, you'd need to return a Promise.
Fortunately the then/catch chain that you have that are built off the fetch() return a Promise. So all you need to do is return the Promise that they have. This is as simple, in your case, by placing a return before the fetch() call. So it would look something like this:
return fetch('http://www.omdbapi.com/?apikey=e255decd%20&s='+ movieName)
// Other lines remain the same

How do I define an action without having to implement a reducer for it?

I'm converting some existing redux code to the toolkit way. We have a lot of actions that trigger thunks (to load data from backend) but dont have a reducer. Our pattern being the load/success/fail triple. Basically only the success and fails need a reducer statement. How do I do this with the toolkit? Do I have to put in a reducer that just returns the unchanged state for the load actions?
With redux-toolkit you have a few options here...
1. Existing thunks + RTK actions
If you only need to update one slice of your store with the loaded data, you can create “success” and “fail” actions in the reducers property on that slice. Then, change your thunk to dispatch those instead of the old success/fail actions.
const slice = createSlice({
name: 'data',
initialState: {},
reducers: {
fetchDataSuccess(state, action) {
// Do something with the response
},
fetchDataError(state, action) {
// Do something with the error
}
}
}
const { fetchDataSuccess, fetchDataError } = slice.actions
export function fetchData() {
return dispatch => api.getData()
.then(response => dispatch(fetchDataSuccess(response.data)))
.catch(error => dispatch(fetchDataError(error))
}
export default slice.reducer
2. Existing thunks + extraReducers
If you don't want to refactor the existing thunk, or if the actions will be used across multiple slices, you can use the extraReducers property.
// These can also be defined in a separate file and imported
const FETCH_SUCCESS = 'data/FETCH_SUCCESS'
const FETCH_FAIL = 'data/FETCH_FAIL'
export function fetchData() {
return dispatch => api.getData()
.then(response => dispatch({ type: FETCH_SUCCESS, payload: response.data }))
.catch(error => dispatch({ type: FETCH_FAIL, payload: error }))
}
const slice = createSlice({
// ... the usual properties
extraReducers: {
[FETCH_SUCCESS](state, action) {
// Do something with the response
},
[FETCH_FAIL](state, action) {
// Do something with the error
}
}
}
3. createAsyncThunk
This approach is similar to the above, but the createAsyncThunk utility handles a lot of it for you, like catching errors, dispatching the actions at the right time, etc.
const fetchData = createAsyncThunk(
'data/fetchData',
() => api.getData().then(response => response.data)
)
const slice = createSlice({
// ... the usual properties
extraReducers: {
[fetchData.fulfilled](state, action) {
// Do something with the response
},
[fetchData.rejected](state, action) {
// Do something with action.error
}
}
}
// Components still call this like a normal function: fetchData()
export { fetchData }
export default slice.reducer
Whichever way you end up going, if you're not using the "load" action (or .pending from createAsyncThunk), you don't need to add it to either reducers or extraReducers.
I think you can simply create thunk-actions.ts (or eg. saga-actions.ts) file to keep actions that trigger data loading.
import { createAction } from '#reduxjs/toolkit';
export const fetchUserComments = createAction<{ id: string }>(
'fetchUserComments',
);
all actions that have reducer's logic will be generated by slice

Asynchronous Request to Facebook API with Redux

I am using Redux and trying to make a call to Facebook API with their JS SDK. I've only ever used promises with Redux and so since the method FB.getLoginStatus just returns a simple JS object, I'm not sure how to ensure that the payload doesn't return undefined.
With redux-promise, you add it to the applyMiddleware(ReduxPromise)... and then it ensures nothing is returned until the promise resolves. But I don't know how to do that here.
I've also used async/await functions with React Native without an issue, but I tried using them here and for some reason the code still returns the payload, before the asynchronous request (await ...) is finished. So I tried working with redux-await, but couldn't get it to work.
export function getLoginStatus() {
var res = FB.getLoginStatus(function(res) {
console.log(res);
});
console.log("res ", res);
return {
type: GET_LOGIN_STATUS,
payload: res
}
}
Hm, things can get a little tricky as I've not used redux-promise. And I can't tell exactly what else you have tried. But this would be my first shot:
async function _getLoginStatus() {
var payload = new Promise( (resolve, fail) => {
FB.getLoginStatus((res)=>resolve(res));
});
return {
type: GET_LOGIN_STATUS,
payload: payload
}
}
// Last time I exported an async function I needed this HYMMV
export let getLoginStatus = _getLoginStatus;
And then elsewhere in the code:
import {getLoginStatus} from 'whatever.js';
var payloadResult = await getLoginStatus();