How to wait for the end of an action with useDispatch to move on? - reactjs

I currently have a real problem. I want to redirect my user to the right conversation or publication when they press a notification.
All the code works, but I have the same problem all the time: the redirection happens before the action is completed, which results in a nice error telling me that the item is "null".
If I redirect to a publication with a new comment, it shows the publication, but the comments load one or two seconds after being redirected.
How is it possible to wait for the end of an action before redirecting?
Thanks a lot
My action (with Redux Thunk)
export const fetchPublications = token => {
return async dispatch => {
await axios
.get(`/articles?token=${token}`)
.then(response => {
const articles = response.data.articles;
const groups = response.data.groups;
const groupPosts = response.data.groupPosts;
const comments = response.data.comments;
const loadedArticles = [];
const loadedGroups = [];
const loadedGroupPosts = [];
const loadedComments = [];
for (const key in articles) {
loadedArticles.push(
new Article(
articles[key].id,
articles[key].title,
articles[key].content,
articles[key].description,
articles[key].cover,
articles[key].dateCreation,
articles[key].creatorPhoto,
articles[key].creatorFirstName,
articles[key].creatorLastName,
articles[key].creatorId,
articles[key].slug,
articles[key].isOnline,
articles[key].isForPro,
'article',
),
);
}
for (const key in groups) {
loadedGroups.push(
new Group(
groups[key].id,
groups[key].name,
groups[key].icon,
groups[key].cover,
groups[key].description,
groups[key].isPublic,
groups[key].isOnInvitation,
groups[key].dateCreation,
groups[key].slug,
groups[key].safeMode,
groups[key].isOnTeam,
groups[key].role,
groups[key].isWaitingValidation,
'group',
),
);
}
for (const key in groupPosts) {
loadedGroupPosts.push(
new GroupPost(
groupPosts[key].id,
groupPosts[key].content,
groupPosts[key].dateCreation,
groupPosts[key].lastModification,
groupPosts[key].creatorPhoto,
groupPosts[key].creatorFirstName,
groupPosts[key].creatorLastName,
groupPosts[key].creatorId,
groupPosts[key].onGroupId,
groupPosts[key].groupName,
groupPosts[key].groupIcon,
'groupPost',
groupPosts[key].liked,
groupPosts[key].likesCounter,
groupPosts[key].commentsCounter,
),
);
}
for (const key in comments) {
loadedComments.push(
new Comment(
comments[key].id,
comments[key].content,
comments[key].dateCreation,
comments[key].lastModification,
comments[key].creatorPhoto,
comments[key].creatorFirstName,
comments[key].creatorLastName,
comments[key].creatorId,
comments[key].onPostId,
),
);
}
dispatch({
type: FETCH_PUBLICATIONS,
articles: loadedArticles,
groups: loadedGroups,
groupPosts: loadedGroupPosts,
comments: loadedComments,
});
})
.catch(error => {
console.log(error);
throw new Error('Une erreur est survenue.');
});
};
};
My notification handler
const handleNotificationResponse = async response => {
if (response.actionIdentifier === 'expo.modules.notifications.actions.DEFAULT') {
try {
if (response.notification.request.content.data.discussionId) {
if (isAuth) {
const discussionId =
response.notification.request.content.data.discussionId;
dispatch(messengerActions.fetchMessenger(userToken));
const item = messages.filter(
message => message.id == discussionId,
);
navigationRef.current?.navigate('MessengerApp', {
screen: 'Discussion',
params: { item: item[0] },
});
}
} else if (response.notification.request.content.data.groupPostId) {
if (isAuth) {
const groupPostId =
response.notification.request.content.data.groupPostId;
dispatch(newsfeedActions.fetchPublications(userToken));
const item = groupPosts.filter(
groupPost => groupPost.id == groupPostId,
);
navigationRef.current?.navigate('App', {
screen: 'Comments',
params: {
item: item[0],
},
});
}
}
} catch (err) {}
} else {
}
};

Related

How to implement Authorization with Custom Directives in apollo with graphql-tools/utils?

I know that Apollo 2 allowed custom directives by extending the class "SchemaDirectiveVisitor." However, I am using apollo 3 and I know that the way to achieve this now is by using graphql-tools/utils and graphql-tools/schema.
In my index.js I have the following code:
const serverServer = async () => {
app.use(AuthMiddleware);
app.use(
cors({
origin: 'mydomain',
})
);
let schema = makeExecutableSchema({
typeDefs: [typeDefsLibrary, typeDefsDynamicContent, userTypeDefs],
resolvers: {
Query,
Mutation,
Article,
Blog,
Podcast,
SermonNotes,
Sermon,
// dynamic Content
Friday,
Thursday,
// Post Content
Commentary,
Quote,
Thought,
UserContent_SermonNotes,
// User Content
User,
All_Posts,
},
});
schema = AuthorizationDirective(schema, 'auth');
const apolloServer = new ApolloServer({
schema,
context: ({ req }) => {
const { isAuth, user } = req;
return {
req,
isAuth,
user,
};
},
});
await apolloServer.start();
apolloServer.applyMiddleware({ app: app, path: '/api' });
app.listen(process.env.PORT, () => {
console.log(`listening on port 4000`);
});
};
serverServer();
then on my schema file I have:
directive #auth(requires: [RoleName] ) on OBJECT | FIELD_DEFINITION
enum RoleName {
SUPERADMIN
ADMIN
}
type Commentary #auth(requires: [SUPERADMIN, ADMIN]) {
ID: ID
USER_ID: ID
VERSE_ID: String
body: String
category_tags: String
referenced_verses: String
verse_citation: String
created_date: String
posted_on: String
creator(avatarOnly: Boolean): User
comments(showComment: Boolean): [Commentary_Comment]
approvals: [Commentary_Approval]
total_count: Int
}
and this is my custom directive code:
const { mapSchema, getDirective, MapperKind } = require('#graphql-tools/utils');
const { defaultFieldResolver } = require('graphql');
const { ApolloError } = require('apollo-server-express');
//const { logging } = require('../../helpers');
module.exports.AuthorizationDirective = (schema, directiveName) => {
return mapSchema(schema, {
[MapperKind.FIELD]: (fieldConfig, _fieldName, typeName) => {
const authDirective = getDirective(schema, fieldConfig, directiveName);
console.log('auth Directive line 10: ', authDirective);
if (authDirective && authDirective.length) {
const requiredRoles = authDirective[0].requires;
if (requiredRoles && requiredRoles.length) {
const { resolve = defaultFieldResolver } = fieldConfig;
fieldConfig.resolve = function (source, args, context, info) {
if (requiredRoles.includes('PUBLIC')) {
console.log(
`==> ${context.code || 'ANONYMOUS'} ACCESSING PUBLIC RESOLVER: ${
info.fieldName
}`
);
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
}
if (!requiredRoles.includes(context.code)) {
throw new ApolloError('NOT AUTHORIZED', 'NO_AUTH');
}
console.log(`==> ${context.code} ACCESSING PRIVATE RESOLVER: ${info.fieldName}`);
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
};
return fieldConfig;
}
}
},
});
};
But is not working. It seems like it is not even calling the Custom Directive. As you see I have a "console.log('auth Directive line 10: ', authDirective);" on my schema directive function that return "undefined."
I know this post is so ling but I hope someone can help!
Thanks in advance!
Below is the code worked for me
I have used [MapperKind.OBJECT_FIELD]: not [MapperKind.FIELD]:
I have referred this from #graphql-tools ->
https://www.graphql-tools.com/docs/schema-directives#enforcing-access-permissions
`
const { mapSchema, getDirective, MapperKind } = require('#graphql-tools/utils');
const { defaultFieldResolver } = require('graphql');
const HasRoleDirective = (schema, directiveName) => {
return mapSchema(schema, {
// Executes once for each object field in the schems
[MapperKind.OBJECT_FIELD]: (fieldConfig, _fieldName, typeName) => {
// Check whether this field has the specified directive
const authDirective = getDirective(schema, fieldConfig, directiveName);
if (authDirective && authDirective.length) {
const requiredRoles = authDirective[0].requires;
// console.log("requiredRoles: ", requiredRoles);
if (requiredRoles && requiredRoles.length) {
// Get this field's original resolver
const { resolve = defaultFieldResolver } = fieldConfig;
// Replace the original resolver with function that "first" calls
fieldConfig.resolve = function (source, args, context, info) {
// console.log("Context Directive: ", context);
const { currentUser } = context;
if(!currentUser) throw new Error("Not Authenticated");
const { type } = currentUser['userInfo']
const isAuthorized = hasRole(type, requiredRoles);
if(!isAuthorized) throw new Error("You Have Not Enough Permissions!")
//logging(context, info.fieldName, args);
return resolve(source, args, context, info);
};
return fieldConfig;
}
}
}
})
}
`

(Refactor/Improve) Loop to make API calls and manupilate Array following the "no-loop-func"

Despite looking and following numerous answers here at stackoverflow,I have still failed to refactor this code to abide by the ESLint no-loop-func.
I keep getting the following warning, despite my efforts to refactor the code:
Compiled with warnings.
Function declared in a loop contains unsafe references to variable(s) 'lastResult', 'biologyBooks', 'page' no-loop-func
Here's the code:
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({ total: 0, biologyBooksByAuthor: [] });
let isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async() => { // fetch items
let page = 1;
let scienceBooks, biologyBooks;
// create empty arrays to store book objects for each loop
let scienceBooks = biologyBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do { // the looping - this is what I have failed to refactor
try {
await apiFullCall( // Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`
).then(res => {
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
body &&
body.results &&
body.results.map(eachBook => { // we map() over the returned "results" array
// the author with queried "author_id" writes science books;
// so we add each book (an object) into the science category
scienceBooks.push(eachBook);
// We then filter the author's biology books (from other science books)
biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof(is_biology) === "boolean" && is_biology === true
);
return null;
}
);
// increment the page with 1 on each loop
page++;
}
}
}).catch(error => console.error('Error while fetching data:', error));
} catch (err) { console.error(`Oops, something went wrong ${err}`); }
// keep running until there's no next page
} while (lastResult.next !== null);
// update the state
setState(prevState => ({
...prevState, total: scienceBooks.length, biologyBooksByAuthor: biologyBooks,
}));
};
React.useEffect(() => { // fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
};
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
}
Please note that I actually declared the said variables lastResult, biologyBooks and page outside the "do-while".
Any help or clues will be greatly appreciated.
The function the warning is referring to is the .then callback, if you're using async/await stick to it, try removing the .then part by assigning the result to a variable instead and remove the unnecessary .map, you can concatenate previous results with spread operator or .concat.
import React from 'react';
import { apiFullCall } from '../../apiHelper';
const MyComponent = props => {
const [state, setState] = React.useState({
total: 0,
scienceBooksByAuthor: [],
});
const isLoaded = React.useRef(true);
const token = sessionStorage.getItem('token');
const authorID = sessionStorage.getItem('author_id');
const getBooks = async () => {
// fetch items
let page = 1;
let scienceBooks = [];
// create a lastResult object to help check if there is a next page
let lastResult = { next: null };
do {
// the looping - this is what I have failed to refactor
try {
const res = await apiFullCall(
// Make API calls over paginated records
'',
token,
'get',
`books/?author_id=1&page=${page}`,
);
if (res) {
const { status, body } = res;
if (status === 200 || status === 201) {
lastResult = body; // assign lastResult to pick "next"
// concatenate new results
scienceBooks = [
...scienceBooks,
...((lastResult && lastResult.results) || []),
];
// increment the page with 1 on each loop
page += 1;
}
}
} catch (err) {
console.error(`Oops, something went wrong ${err}`);
}
// keep running until there's no next page
} while (lastResult.next !== null);
const biologyBooks = scienceBooks.filter(
({ is_biology }) =>
typeof is_biology === 'boolean' && is_biology === true,
);
// update the state
setState(prevState => ({
...prevState,
total: scienceBooks.length,
scienceBooksByAuthor: scienceBooks,
}));
};
React.useEffect(() => {
// fetch science books by author (logged in)
if (isLoaded && authorID) {
getBooks();
}
return function cleanup() {...}; // clean up API call, on unmount
}, [isLoaded, authorID]);
return (
// render the JSX code
);
};

How to add some option to a select box above all mapping in React?

I want to add an All Option to my existing select box.
Select box is creating with some API data. With the API data set I want to add an ALL option above.
This is my code.
const useChemicals = () => {
const [data, setData]: any = useState([]);
useEffect(() => {
const getChemicalsData = async () => {
try {
const results = await searchApi.requestChemicalsList();
if (results.data) {
let groupCount = 0;
const chemList: any = [];
results.data.data.chemicals.map((chemical: any, index: number) => {
if (chemical.key === '') {
chemList.push({
label: chemical.value,
options: [],
});
}
});
results.data.data.chemicals.map((chemical: any, index: number) => {
if (chemical.key === '') {
if (index > 1) {
groupCount += 1;
}
} else {
chemList[groupCount].options.push({
label: chemical.value,
value: chemical.key,
});
}
});
setData([...chemList]);
}
} catch (e) {}
};
getChemicalsData();
}, []);
return data && data;
};
export default useChemicals;
How can I add this. Please help me, I am new to React.

React-Apollo: Recommended way of subscribing to multiple events that doesn't require UI updates

So i want to subscribe to multiple events for the current logged user.
I've extracted the subscriptions to a separate function that update my logged user state from inside and returns an array of subscriptions.
Now i wanted to know is there a different / better way of doing this ?
Is this the correct / recommended way of approaching this problem ?
Current implementation
export const subscribeToCurrentUserUpdates = (setLoggedUser) => {
const friendRequestObserver$ = apolloClient.subscribe(
{ query: queries.NEW_FRIEND_REQUEST },
);
const followersUpdatesObserver$ = apolloClient.subscribe(
{ query: queries.FOLLOWERS_UPDATES },
);
const acceptedFriendRequestObserver$ = apolloClient.subscribe(
{ query: queries.ACCEPTED_FRIEND_REQUEST },
);
const friendRequestSubscription = friendRequestObserver$.subscribe({
next: ({ data: { newFriendRequest } }) => {
Alert.success(`${newFriendRequest.username} just sent you a friend request`);
setLoggedUser((loggedUser) => {
loggedUser.incomingFriendRequests.unshift(newFriendRequest._id);
});
},
error: err => console.error(err),
});
const followersUpdatesSubscription = followersUpdatesObserver$.subscribe({
next: ({ data: { followersUpdates: { follower, isFollow } } }) => {
if (isFollow) {
Alert.success(`${follower.username} is now following you`);
}
setLoggedUser((loggedUser) => {
isFollow
? loggedUser.followers.unshift(follower._id)
: loggedUser.followers.splice(loggedUser.followers.indexOf(follower._id), 1);
});
},
error: err => console.error(err),
});
const acceptedFriendRequestSubscription = acceptedFriendRequestObserver$.subscribe({
next: ({ data: { acceptedFriendRequest: newFriend } }) => {
Alert.success(`${newFriend.username} just accepted your friend request!`);
setLoggedUser((loggedUser) => {
loggedUser.friends.push(newFriend._id);
loggedUser.sentFriendRequests.splice(
loggedUser.sentFriendRequests.indexOf(newFriend._id), 1,
);
});
},
error: err => console.error(err),
});
return [
friendRequestSubscription,
followersUpdatesSubscription,
acceptedFriendRequestSubscription,
];
};
The way i subscribe from my component
const App = () => {
const currentUserSubscriptionRef = useRef();
useEffect(() => {
if (loggedUser && !currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current = subscribeToCurrentUserUpdates(
setLoggedUser,
);
}
if (!loggedUser && currentUserSubscriptionRef.current) {
currentUserSubscriptionRef.current.forEach((subscription) => {
subscription.unsubscribe();
});
currentUserSubscriptionRef.current = null;
}
}, [loggedUser, setLoggedUser]);
}

Using mergeProps to access state in mapDispatchToProps

I need to access state that's been loaded in one dispatch handler from other dispatch handlers. According to this article "where you intend to use properties from store as parameters to dispatch actions, mergeProps is the cleanest choice"
This current container code causes the contained component's required props to not be defined. I'm hoping someone can point out my errors:
const mergeProps = (propsFromState, propsFromDispatch) => {
return {
onLeave() {
const topics = propsFromState.topics;
return propsFromDispatch.onLeaveWithTopics(topics);
},
onReceived(args, kwargs, event) {
const topics = propsFromState.topics;
return propsFromDispatch.onReceivedWithTopics(args, kwargs, event, topics);
},
};
};
const mapStateToProps = (state) => ({
topics: state.simpl.topics,
connectionStatus: state.simpl.connectionStatus,
errors: state.errors,
progressComponent: optionsWithDefaults.progressComponent,
});
const mapDispatchToProps = (dispatch) => {
return ({
...
onLeaveWithTopics(topics) {
console.log(`onLeave:: topics: `, topics);
if (topics) {
topics.forEach((topic) => {
dispatch(disconnectedScope(topic));
});
}
return Promise.resolve();
},
onReceivedWithTopics(args, kwargs, event, topics) {
console.log(`onReceived:: args: `, args, `, event: `, event, `, topics: `, topics);
if (kwargs.error) {
dispatch(showGenericError(args, kwargs));
} else {
const [pk, resourceName, data] = args;
if (topics) {
const resolvedTopics = topics.map(
(topic) => AutobahnReact.Connection.currentConnection.session.resolve(topic)
);
resolvedTopics.forEach((topic) => {
const actions = {
[`${topic}.add_child`]: addChild,
[`${topic}.remove_child`]: removeChild,
[`${topic}.update_child`]: updateScope,
};
if (actions[event.topic]) {
console.log("dispatching: ", actions[event.topic])
dispatch(actions[event.topic]({ resource_name: resourceName, data, pk }));
}
});
}
}
},
});
I could be wrong here but don't you need to pass other props forward:
const mergeProps = (propsFromState, propsFromDispatch) => {
return {
...propsFromState,
...propsFromDispatch,
onLeave() {
const topics = propsFromState.topics;
return propsFromDispatch.onLeaveWithTopics(topics);
},
onReceived(args, kwargs, event) {
const topics = propsFromState.topics;
return propsFromDispatch.onReceivedWithTopics(args, kwargs, event, topics);
},
};
};

Resources