Passing data from one screen to another screen with react-navigation - reactjs

I'm currently passing the data to one component in react-native with some code which is shown as below:
class Login extends Component {
signInWithGoogle = async () => {
try {
const result = await Google.logInAsync({
iosClientId: IOS_CLIENT_ID,
androidClientId: ANDROID_CLIENT_ID,
scopes: ["profile", "email"]
});
if (result.type === "success") {
console.log(
"LoginScreen.js.js 21 | ",
result.user.givenName,
result.user.familyName,
result.user.email,
result.user.photoUrl
);
this.props.navigation.navigate(
"MyDrawer",
(username = result.user.givenName),
(lastname = result.user.familyName),
(email = result.user.email),
(photoUrl = result.user.photoUrl)
);
return result.accessToken;
} else {
return { cancelled: true };
}
} catch (e) {
console.log("LoginScreen.js.js 30 | Error with login", e);
return { error: true };
}
};
}
My question is how do I pass the data to a different component?

you need to pass an object as second parameter to the navigate method that is in props.
to pass a data to a different component, provide an object as the second argument for the navigate function
change this
this.props.navigation.navigate(
"MyDrawer",
(username = result.user.givenName),
(lastname = result.user.familyName),
(email = result.user.email),
(photoUrl = result.user.photoUrl)
);
to
this.props.navigation.navigate("MyDrawer",{
name: result.user.givenName,
lastname : result.user.familyName,
email :result.user.email,
photoUrl = result.user.photoUrl
});
now in the component that you are passing the data to, you can get the keys using the getKey method of react-navigation
TargetComponent.js
render() {
email = this.props.navigation.getParam("email", defaultValue);
password= this.props.navigation.getParam("password", defaultValue);
}

Related

React component uses old data from previous API call

I am using React Query to fetch data from an API I have built. The component is rendering the old data from the previous api call and not updating with new the data from the new api call.
The new data is only rendering when I refresh the page.
Component:
export const ProfilePageStats = (props: {
user: User;
id: number;
}) => {
const { chatId } = useParams();
const { status: subscribeStatus, data: subscribeData } =
useSubscriptionsWithType(
chatId ? chatId : "",
props.id,
props.user.id,
"SUBSCRIBE"
);
const { status: unsubscribeStatus, data: unsubscribeData } =
useSubscriptionsWithType(
chatId ? chatId : "",
props.id,
props.user.id,
"UNSUBSCRIBE"
);
if (unsubscribeStatus == "success" && subscribeStatus == "success") {
console.log("Working", unsubscribeData);
return (
<ProfilePageStatsWithData
user={props.user}
subscribed={Object.keys(subscribeData).length}
unsubscribed={Object.keys(unsubscribeData).length}
/>
);
}
if (unsubscribeStatus == "error" && subscribeStatus == "error") {
console.log("error");
return <ProfilePageStatsLoading />;
}
if (unsubscribeStatus == "loading" && subscribeStatus == "loading") {
console.log("loading");
return <ProfilePageStatsLoading />;
}
return <ProfilePageStatsLoading />;
};
export const useSubscriptionsWithType = (
chatId: string,
id: number,
userId: number,
type: string
) => {
return useQuery(
["subscriptionsWithType"],
async () => {
const { data } = await api.get(
`${chatId}/subscriptions/${id}/${userId}?type=${type}`
);
return data;
},
{
enabled: chatId > 0 && userId > 0,
refetchOnWindowFocus: false,
}
);
};
The component should update to show the new user values but shows the previous user values. If I click out and select a different user entirely it then shows the values for the previously clicked user.
I can see that React Query is fetching with the correct values for the query but the component still renders the old user data?
It turns out that the fetchStatus value is changing to "fetching" but it not actually calling the api. Hence, why its only using the old values?
Your key part of the useQuery is what tells the hook when to update.
You only use ["subscriptionsWithType"] as key, so it will never know that you need to refetch something.
If you add userId there, it will update when that changes.
So, using
return useQuery(
["subscriptionsWithType", userId],
async () => {
...
will work.
It is likely, that you want all the params, that you use in the url, to be added there.
I solved it by adding a useEffect and refetching based on the changing user id.
useEffect(() => {
refetch();
}, [props.user.id]);

React native: how do I wait for a state to be set, before I call another state related operation?

I am writing a chat app. Users can search for other users, and then press the "Message" button. Then I navigate to ChatScreen.js. If both users have been messaging each other, I set the chatId variable accordingly. If they have not messaged each other before I dont create chatId, until the ery first message has been sent. When the first message is sent, I first, create new chat, store its properties (user ids, chatId, etc) in my db and then I sent the first message. The problem is that I store chatId as a state variable, and when I create the chat I call setChatId(id). setChatId() is not synchronous call, so by the time when I need to send message with sendText(text, chatId); my chatId is undefined even though I have already created a chat and I have called setChatId.
How can I avoid this error? Ofc, I can check if chatId == undefined then calling sendText(text, id), otherwise calling sendText(text, chatId). Is there a better/neath way to avoid the undefined check?
Here is part of my code:
...
import {
createChat,
} from "./actions";
...
function ChatScreen(props) {
...
const [chatId, setChatId] = useState(props.route.params.chatId);
...
const setupChat = async () => {
try {
await createChat(user.id, setChatId);
props.fetchUserChats();
} catch (error) {
console.error("Error creating chat: ", error);
}
};
async function handleSend(messages) {
if (!chatId) {
// creating chat
await setupChat();
}
const text = messages[0].text ? messages[0].text : null;
const imageUrl = messages[0].image ? messages[0].image : null;
const videoUrl = messages[0].video ? messages[0].video : null;
const location = messages[0].location ? messages[0].location : null;
//assuming chatId is already setup but it is not
if (imageUrl) {
sendImage(imageUrl, chatId, setSendImageError);
} else if (location) {
sendLocation(location, chatId, setLocationError);
} else if (videoUrl) {
sendVideo(videoUrl, chatId, setSendImageError);
} else {
sendText(text, chatId);
}
}
...
}
My createChat function from actions.js file
export async function createChat(otherUid, setChatId) {
let chatId = firebase.auth().currentUser.uid + "_" + otherUid;
await firebase
.firestore()
.collection("Chats")
.doc(chatId)
.set({
users: [firebase.auth().currentUser.uid, otherUid],
lastMessage: "Send the first message",
lastMessageTimestamp: firebase.firestore.FieldValue.serverTimestamp(),
})
.then(() => {
console.log("doc ref for creatign new chat: ", chatId);
setChatId(chatId);
})
.catch((error) => {
console.error("Error creating chat: ", error);
});
}
Instead of using a state variable, I would advise you to use useRef(). This would be a good solution to your problem.Eg Define it this way
const chatId = useRef(null),
then set it this way chatId.current = yourChatId
and get it this way chatId.current. I hope this solves your problem

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;
}
}
}
})
}
`

Why does material-ui-lab rating component sometimes send null onChange?

New to working with material-ui and I'm having an issue with input on #material-ui/lab/Rating. Occasionally, when I try to click on the rating component multiple times in a row, the value passed to my controller comes back as null instead of the rating I clicked on. The following is my console after logging incoming data in my controller:
Rating Passed: 5
id: 60a42672c615650cb85629f4
Rating Passed: null
id: 60a42672c615650cb85629f4
Rating Passed: 4
id: 60a42672c615650cb85629f4
Snippet from my front-end:
<CardActions className={classes.cardActions}>
<Rating name={post._id} value={post.raters !== 0 ? post.rating/post.raters : 0} precision={1} max={5} onChange={(event, v)=>{dispatch(ratePost(post._id , {rating: v}))}}/>
</CardActions>
And if it's relevant, my controller:
export const ratePost = async (req, res) => {
try {
console.log("Rating Passed: ", req.body.rating);
const { id: _id } = req.params;
console.log("id: ", _id);
if(!mongoose.Types.ObjectId.isValid(_id)) {
return res.status(404).send('Post to rate not found');
}
const post = await PostMessage.findById(_id);
let newRaters = post.raters + 1;
let newRating = (post.rating + req.body.rating);
if(req.body.rating === null) {
newRaters = post.raters;
newRating = post.rating;
}
const updatedPost = await PostMessage.findByIdAndUpdate(_id, {rating: newRating, raters: newRaters}, {new:true});
res.json(updatedPost);
} catch (error) {
console.error(error);
}
}
Currently, in my controller I just ignore null inputs. However, it makes the site feel unresponsive when a rating click isn't registered. What could cause the rating component to send a null value?

Local storage handling in react in another class

I had a component that each time something was added to state was added to local storage as well. It was deleted from local storage on componentWillUnmnout. I was told to prepare an indirect abstract layer for local storage handling in order to follow single responsibility principle.
I am confused how this could be done, can someone give an example of such layer, class?
componentWillUnmount() {
localStorage.removeItem('currentUser');
}
static getDerivedStateFromProps(nextProps) {
const currUser = JSON.parse(
localStorage.getItem('currentUser')
);
if (
currUser && nextProps.users.some(
(user) => user.id === currUser.id
)
) {
return {
user: currUser,
};
}
return null;
}
const onSelect = (
user
) => {
this.setState({
user,
});
localStorage.setItem('currentUser', JSON.stringify(user));
}
private onRemove = () => {
this.setState({
user: null,
});
localStorage.removeItem('currentUser');
}
Applying single responsibility principle here might be over-programming, since Javascripts is not OOP. But if you need, there are some concerns with using localStorage directly that can be separated:
Your component doesn't need to know where you store persistent data. In this case, it doesn't need to know about the usage of localStorage.
Your component doesn't need to know how you store the data. In this case, it doesn't need to handle JSON.stringify to pass to localStorage, and JSON.parse to retrieve.
With those ideas, an interface for localStorage can be implemented like so
const Storage = {
isReady: function() {
return !!window && !!window.localStorage;
},
setCurrentUser: function(user) {
if (!this.isReady()) throw new Error("Cannot find localStorage");
localStorage.setItem('currentUser', JSON.stringify(user));
return true;
},
getCurrentUser: function() {
if (!this.isReady()) throw new Error("Cannot find localStorage");
if (localStorage.hasOwnProperty('currentUser'))
{
return JSON.parse(localStorage.getItem('currentUser'));
}
return null;
},
removeCurrentUser: function() {
if (!this.isReady()) throw new Error("Cannot find localStorage");
localStorage.removeItem('currentUser');
return true;
}
}
By importing Storage object, you can rewrite your component:
componentWillUnmount() {
Storage.removeCurrentUser();
}
static getDerivedStateFromProps(nextProps) {
const currUser = Storage.getCurrentUser();
if (
currUser && nextProps.users.some(
(user) => user.id === currUser.id
)
) {
return {
user: currUser,
};
}
return null;
}
const onSelect = (
user
) => {
this.setState({
user,
});
Storage.setCurrentUser(user);
}
private onRemove = () => {
this.setState({
user: null,
});
Storage.removeCurrentUser();
}

Resources