should I wrap my try catch block inside of the for loop RTK QUERY? - redux

I use rtk query and make optimistic UI. So I select by endpoints and change the data but there is one thing. I have a constant varibale "patchResult" in my for loop and outer for loop I await my query fullfilled. So I can not patchResult.undo() because patchResult variable is inside the for loop and my try catch block is outer the for loop so I dont have access to the variable. Should I put the try catch block in the for loop or is it bad ?
async onQueryStarted({ user_id }, { dispatch, queryFulfilled, getState }) {
for (const { endpointName, originalArgs } of Api.util.selectInvalidatedBy(getState(), [{ type: 'USER'}])) {
const patchResult = dispatch(
UserApi.util.updateQueryData('users', originalArgs, (draft) => {
return {
...draft,
user: {
...draft.user,
is_follow: !draft.user.is_follow
}
}
})
);
}
try {
await queryFulfilled
} catch {
patchResult.undo();
}

You will probably need an array of patches:
async function onQueryStarted(
{ user_id },
{ dispatch, queryFulfilled, getState }
) {
let allPatches = [];
for (const { endpointName, originalArgs } of Api.util.selectInvalidatedBy(
getState(),
[{ type: "USER" }]
)) {
const patchResult = dispatch(
UserApi.util.updateQueryData("users", originalArgs, (draft) => {
return {
...draft,
user: {
...draft.user,
is_follow: !draft.user.is_follow,
},
};
})
);
allPatches.push(patchResult);
}
try {
await queryFulfilled;
} catch {
for (const patchResult of allPatches) {
patchResult.undo();
}
}
}

Related

Get content related to a user in Strapi

I have a collection in Strapi called projects and I want to be able to fetch only the projects belonging to the currently logged in user. I'm using Next.js with NextAuth on the frontend and I'm currently filtering the results using:
/api/projects?filters[user][id][$eq]=${session.id}
This works fine except the endpoint still allows a user to fetch projects for all users if accessed directly. I'm thinking a better approach would be to setup a custom API endpoint in Strapi which would be something like /api/projects/:user. Is this the best way to acheive this? I've managed to setup a custom endpoint in Strapi using the CLI but I'm not sure what logic needs to go in the controller. Would modifiying an exisiting endpoint be better?
Any advice appreciated, thanks!
Custom endpoint create is good idea. I had same problem. Once i created custom endpoint then i got data with entitiyservice. It's work. Below image is my code.
./scr/api/[collection]/controllers/[collection].js
'use strict';
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::user-profile.user-profile', ({ strapi }) => ({
async me(ctx) {
try {
const user = ctx.state.user;
const datas = await strapi.entityService.findMany("api::user-profile.user-profile", {
filters: {
user: {
id: user.id
}
}
})
return datas;
} catch (err) {
ctx.body = err;
}
}
}));
If you will use all endpoints in collection like (create,update,delete,find,findone). You must override the all endpoints . Example is the below.
'use strict';
const { createCoreController } = require('#strapi/strapi').factories;
module.exports = createCoreController('api::education.education', ({ strapi }) => ({
async create(ctx) {
try {
const user = ctx.state.user;
ctx.request.body.data.users_permissions_user = user.id
const datas = await strapi.entityService.create("api::education.education", {
...ctx.request.body
})
return datas;
} catch (err) {
ctx.body = err;
}
},
async update(ctx) {
try {
const user = ctx.state.user;
ctx.request.body.data.users_permissions_user = user.id
const { id } = ctx.params;
const experienceData = await strapi.entityService.findMany("api::education.education", {
filters: {
users_permissions_user: {
id: user.id
},
id: id
}
});
if (experienceData.length === 0) {
return {
data: null,
error: {
message: ''
}
}
}
const datas = await strapi.entityService.update("api::education.education", id, {
...ctx.request.body
})
return datas;
} catch (err) {
ctx.body = err;
}
},
async delete(ctx) {
try {
const user = ctx.state.user;
const { id } = ctx.params;
const experienceData = await strapi.entityService.findMany("api::education.education", {
filters: {
users_permissions_user: {
id: user.id
},
id: id
}
});
if (experienceData.length === 0) {
return {
data: null,
error: {
message: ''
}
}
}
const datas = await strapi.entityService.delete("api::education.education", id)
return datas;
} catch (err) {
ctx.body = err;
}
},
async findOne(ctx) {
try {
const user = ctx.state.user;
const { id } = ctx.params;
const experienceData = await strapi.entityService.findMany("api::education.education", {
filters: {
users_permissions_user: {
id: user.id
},
id: id
}
});
if (experienceData.length === 0) {
return {
data: null,
error: {
message: ''
}
}
}
const datas = await strapi.entityService.findOne("api::education.education", id)
return datas;
} catch (err) {
ctx.body = err;
}
},
async find(ctx) {
try {
const user = ctx.state.user;
const datas = await strapi.entityService.findMany("api::education.education", {
filters: {
users_permissions_user: {
id: user.id
}
}
})
return datas;
} catch (err) {
ctx.body = err;
}
},
}));
No extra endpoints and no extra codes.
Strapi v4
Yes, creating separate endpoint for this task would be great.
Instead of /api/projects/:user using this type of route, use /api/projects as you can get current logged in users details from ctx.state.user
No, Instead of modifying your existing controller create new controller and use that controller to satisfy your needs.
I ended up extending my controller. In src/api/controllers/project.js I made the following changes:
"use strict";
const { createCoreController } = require("#strapi/strapi").factories;
module.exports = createCoreController("api::project.project", {
async find(ctx) {
const user = ctx.state.user;
ctx.query.filters = {
...(ctx.query.filters || {}),
user: user.id,
};
return super.find(ctx);
},
});
Then simply call the /api/projects endpoint.
Answer based on this guide Limit access of Strapi users to their own entries.

wait until first hook is complete before fetching data

I have this custom hook which fetches the query.me data from graphql. The console.log statement shows that this hook is running a number of times on page load, but only 1 of those console.logs() contains actual data.
import { useCustomQuery } from '../api-client';
export const useMe = () => {
const { data, isLoading, error } = useCustomQuery({
query: async (query) => {
return getFields(query.me, 'account_id', 'role', 'profile_id');
},
});
console.log(data ? data.account_id : 'empty');
return { isLoading, error, me: data };
};
I then have this other hook which is supposed to use the id's from the above hook to fetch more data from the server.
export const useActivityList = () => {
const { me, error } = useMe();
const criteria = { assignment: { uuid: { _eq: me.profile_id } } } as appointment_bool_exp;
const query = useQuery({
prepare({ prepass, query }) {
prepass(
query.appointment({ where: criteria }),
'scheduled_at',
'first_name',
'last_name',
);
},
suspense: true,
});
const activityList = query.appointment({ where: criteria });
return {
activityList,
isLoading: query.$state.isLoading,
};
};
The problem I am facing is that the second hook seems to call the first hook when me is still undefined, thus erroring out. How do I configure this, so that I only access the me when the values are populated?
I am bad with async stuff...
In the second hook do an early return if the required data is not available.
export const useActivityList = () => {
const { me, error } = useMe();
if (!me) {
return null;
// or another pattern that you may find useful is to set a flag to indicate that this query is idle e.g.
// idle = true;
}
const criteria = { assignment: { uuid: { _eq: me.profile_id } } } as appointment_bool_exp;
...

React-Saga - how to make nested generators work

I upgraded from old redux saga to the latest version and the following stopped working.
function* loadAlbumPhoto({ entity }, entityId) {
try {
const { accessToken: at } = yield select(state => state.user.info);
let {
data: { data: albums }
} = yield call(API.loadAlbumByEntityId, { entityName: entity, entityId, type: PHOTO });
if (!albums.length) {
const options = {
entityId,
entityName: entity,
title: PHOTO,
type: PHOTO
};
yield call(API.createAlbum, options);
({ data: { data: albums } } = yield call(API.loadAlbumByEntityId, { entityName: entity, entityId, type: PHOTO }));
}
const [album] = albums;
const { data: { data: photos } } = yield call(API.loadPhotosByAlbumId, album.id);
return yield photos.map(function* (photo) {
const src = yield getPhotoUrl(photo.uploadData.path, at);
return {
src,
uploadId: photo.uploadId,
photoId: photo.id
};
});
} catch (err) {
console.log(err);
return [];
}
}
function* getPhotoUrl(path, at) {
try {
const userPhoto = yield API.userPhoto(path, at);
return userPhoto;
} catch (err) {
/* eslint-disable no-console */
console.log(err);
/* eslint-enable no-console */
}
return "";
}
As you can see i am trying to return array from loadAlbumPhoto but my problem is that i need to call getPhotoUrl function which is also a generator function.
The problem is that the result of loadAlbumPhoto is Array of generators and not Array of values. It happened since my upgrade to the last version of redux and redux saga.
Already tried to use yield* but not working or i don't know how to use it.
yield*
I would do a bit of refactoring of the anonymous generator and then convert your yield to use all: https://redux-saga.js.org/docs/api/#alleffects---parallel-effects
function* getPhotoDetails(photo) {
const src = yield getPhotoUrl(photo.uploadData.path, at);
return {
src,
uploadId: photo.uploadId,
photoId: photo.id
};
}
function* loadAlbumPhoto({ entity }, entityId) {
// similar up to yield photos.map...
return yield all(photos.map(photo => call(getPhotoDetails, photo)));
// similar after
}

Async / Await Vuex

I want to call an action in created hook, wait until is done and in same hook to display the result. Is that possible?
I tried to put async / await in actions but doesn't help.
This is the action property with the async function in the store:
actions: {
async FETCH_USER({commit}) {
await firebase.firestore().collection('test').get().then(res => {
commit('FETCH_USER', res.docs[0].data())
})
}
}
created() {
this.FETCH_USER()
console.log(this.GET_USER)
},
methods: {
...mapActions([
'FETCH_USER'
]),
login() {
if(this.$refs.form.validate()) {
console.log('welcome')
}
}
},
computed: {
...mapGetters([
'GET_USER'
])
}
export default new Vuex.Store({
state: {
user: null
},
getters: {
GET_USER: state => state.user
},
mutations: {
FETCH_USER(state, user) {
state.user = user
}
},
actions: {
FETCH_USER({commit}) {
firebase.firestore().collection('test').get().then(res => {
commit('FETCH_USER', res.docs[0].data())
})
}
}
})
async/await version
async FETCH_USER({ commit }) {
const res = await firebase.firestore().collection('test').get()
const user = res.docs[0].data()
commit('FETCH_USER', user)
return user
}
async created() {
// The action returns the user out of convenience
const user = await this.FETCH_USER()
console.log(user)
// -- or --
// Access the user through the getter
await this.FETCH_USER()
console.log(this.GET_USER)
}
You need to await the action call because it is an async function.
Promise version
FETCH_USER({ commit }) {
return firebase.firestore().collection('test').get().then(res => {
const user = res.docs[0].data()
commit('FETCH_USER', user)
return user
})
}
created() {
this.FETCH_USER().then(user => {
console.log(user)
})
// -- or --
this.FETCH_USER().then(() => {
console.log(this.GET_USER)
})
}

Angular2 return true/false with subscription

I need to check if user exists in the database and return true if so. I have a function (i am using firebase by the way):
checkIfUserExists(login: string): boolean {
this.af.database.list('/users', {
query: {
orderByChild: 'login',
equalTo: login
}
}).subscribe(response => {
if(response.length === 0 ) return true
})
return false
}
The problem is that the function always returns false. It does not wait for checking subscription. Is there some way to fix it?
You could return the observable itself:
checkIfUserExists(login: string): Observable<boolean> {
return this.af.database.list('/users', {
query: {
orderByChild: 'login',
equalTo: login
}
}).map(response => {
if(response.length === 0 ) {
return true;
} else {
return false;
}
});
}
In this case, you can use it like this:
checkIfUserExists('some username')
.subscribe(userExists => {
// Do something
});
This is because your processing is asynchronous so you need to handle processing with callbacks. This way you'll be sure to execute processing when asynchronous processing are executed.
You can use either observables (rxjs - like in my sample) or promises for such use cases.
You could return a promise instead and then call find():
checkIfUserExists(login: string): boolean {
return this.af.database.list('/users', {
query: {
orderByChild: 'login',
equalTo: login
}
}).subscribe(response => {
if(response.length === 0 )
return true;
return false;
}).toPromise().find();
}
You can return a promise using Native Promise API:
checkIfUserExists(login: string): boolean {
return new Promise(function(resolve, reject) {
this.af.database.list('/users', {
query: {
orderByChild: 'login',
equalTo: login
}
}).subscribe(response => {
if(response.length === 0 ) {
reject();
} else {
resolve()
}
})
});
}
Then use it as,
checkIfUserExists('login').then(function(response) {
// found
}, function() {
// not found
});

Resources