React Native - optional values for firebase - javascript

I have a custom hook:
import React, { useState, useEffect } from 'react';
import { Alert } from 'react-native';
import firebase from "../../firebase";
export default function useCurrentUserDetails() {
const uid = firebase.auth().currentUser?.uid;
const [data, setData] = useState(null);
useEffect(() => {
if (!uid) {
setData(null);
return;
}
const unsubscribe = firebase.firestore().collection("users").doc(uid).onSnapshot((snapshot) => {
if (snapshot.exists) {
setData(snapshot.data());
} else {
setData(null);
}
})
return unsubscribe;
}, [uid]);
const updateCurrentUserData = (newData) =>
firebase.firestore().collection("users").doc(uid).set(newData).then(() => {
Alert.alert(
'Profile Updated',
'Nice work!',
[
{ text: 'OK' }
],
{ cancelable: false }
);
}).catch((error) => {
Alert.alert(
'Something went wrong',
'Give it another go',
[
{ text: 'OK' }
],
{ cancelable: false }
);
});
return [data, updateCurrentUserData];
}
In my view im calling the following:
const [currentUserDetails, setCurrentUserDetails] = useCurrentUserDetails();
And on a button press, the values are updated:
const handleSubmit = async () => {
setLoading(true)
const url = await uploadImage(profileURL, firebase.auth().currentUser?.uid)
await setCurrentUserDetails(
{ ...currentUserDetails,
username,
age: Number(age),
height,
country,
occupation,
profileURL: url,
}
)
setLoading(false)
}
The issue im having is that for the first time the user try's to edit the data, it won't update unless I have all the fields present. How could I make each value optional?
For example lets say the person only updated the occupation it will update it and leave the rest and not create any fields for this in the db.

I don't know if how I did this is a correct approach.
y is destructured into separate fields.
const updateUserProfile = (y) => {
const { Age, Hobby, UserName, Job, Country, Name } = y;
return firestore.collection("users").doc(`${currentUser.uid}`).update({
UserName: UserName,
Hobby: Hobby,
Name: Name,
Age: Age,
Country: Country,
Job: Job,
});
};
This way I can update any field I want. But in my case all of these fields already have
default data written, like UserName : UserName, Hobby: hobby. And then I update it with what user changed.
This is how I get "y"
userData is what I get from firestore.
const [data, setData] = useState({ [name]: value });
const x = userData;
const y = Object.assign(x, data);
Sorry if that is a mess of a answer.

Related

I'm having problem figuring why login is working sometimes but sometimes not with Graph and Firebase

So, I have built this way of auth with firebase and graphql using mongo database, the problem is that everything is working, instead trying to login, its the same way of register but sometimes the method works and some times I get apollo client error, which I don't know why.
Here is my code to auth with Firebase and then check if the user exits and call the method and then the oposite.
import { FirebaseAuth } from "../config/Firebase";
import { GoogleAuthProvider, signInWithPopup } from "firebase/auth";
import { Notifier } from "../utils";
import { USER_AUTH_ERROR } from "../config/Responders";
const Google = async (Register, Login, dispatch) => {
var Provider = new GoogleAuthProvider();
const data = await signInWithPopup(FirebaseAuth, Provider);
try {
if (data) {
const user = data.user;
const creationTime = user.metadata.creationTime;
const lastSignInTime = user.metadata.lastSignInTime;
if (creationTime === lastSignInTime) {
const name = user.displayName.split(" ");
const firstName = name[0] || "";
const lastName = name[1] || "";
const config = {
variables: {
createUserInput: {
Name: firstName,
Surname: lastName,
Email: user.email,
Avatar: user.photoURL || null,
Uid: user.uid,
},
},
};
Register(config);
}
else {
const config = {
variables: {
uid: user.uid,
},
};
Login(config);
}
}
else Notifier(dispatch, USER_AUTH_ERROR, `error`);
} catch (error) {
Notifier(dispatch, USER_AUTH_ERROR, `error`);
}
};
export
default Google;
While here is the place where I manage the functions:
const [Register, { data: registerData }] = useMutation(REGISTER);
const [Login, { data: loginData }] = useLazyQuery(AUTH);
const Auther = () => Google(Register, Login, dispatch);
useEffect(() => {
if (!account.Auth) {
if (registerData?.hasOwnProperty("UserRegister")) {
dispatch(Authenticate(registerData.UserRegister));
}
}
}, [registerData]);
useEffect(() => {
if (!account.Auth) {
if (loginData?.hasOwnProperty("UserAuth")) {
dispatch(Authenticate(loginData.UserAuth));
}
}
}, [loginData]);
Here is the error I get:

The object empty check is not working and changing the logic

So I am doing a form validation check and I have taken 'formErrors' and set the errors in this object. However it is initially {} and in my code I am checking for Object.keys(formErrors).length===0 which returns true for even {}
const [formValues, setFormValues] = useState(initialValues);
const [formErrors, setFormErrors] = useState({});
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const url = '/collectdetails';
const handleSubmit = (e) => {
e.preventDefault();
setFormErrors(validate(formValues));
// setIsSubmit(true);
console.log(noErrors);
if (noErrors) {
const { fullName, phoneNumber, emailAddress, role, lookingFor, company } =
formValues;
const data = {
Name: fullName,
MobileNumber: phoneNumber,
Email: emailAddress,
Role: role,
LookingFor: lookingFor,
CompanyName: company,
};
getDetails(url, data).then((user) => {
const { Response } = user;
if (Response === 'OK') {
setCurrentUser(phoneNumber);
navigate('/');
}
});
}
};
useEffect(() => {
if (Object.keys(formErrors).length === 0) {
console.log(formErrors);
setNoErrors(true);
}
}, [formErrors]);
So When I submit the handleSubmit() method is run and it has 2 nested checks. The first one is for noErrors which is a bool state that checks if my object is empty. I have console logged it and it returns true when the component loads as the object is {} in the beginning. Is there any way for me to put a check so that I can see if there are some keys present in the object?
useEffect will run every time your formErrors object changes. This includes the first render.
It would probably be better for you to put your useEffect logic inside your submit handler. The formErrors state object just seems to function as a temporary store for you as you immediately call setNoErrors(true) if it is populated:
const [formValues, setFormValues] = useState(initialValues);
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({ ...formValues, [name]: value });
};
const url = '/collectdetails';
const handleSubmit = (e) => {
e.preventDefault();
// just store in a normal variable
const errors = validate(formValues);
// setIsSubmit(true);
console.log(noErrors); // this isn't defined in your code
// just check the errors object for keys
if (Object.keys(errors).length === 0) {
// errors object is empty
console.log(errors);
setNoErrors(true);
const { fullName, phoneNumber, emailAddress, role, lookingFor, company }
= formValues;
const data = {
Name: fullName,
MobileNumber: phoneNumber,
Email: emailAddress,
Role: role,
LookingFor: lookingFor,
CompanyName: company,
};
getDetails(url, data).then((user) => {
const { Response } = user;
if (Response === 'OK') {
setCurrentUser(phoneNumber);
navigate('/');
}
});
}
};

Why can't I access data after fetching?

I'm trying to keep session stayed logged in after refreshing the browser. The user data that is being fetched is not rendering after being fetched. The console is saying "Cannot read properties of undefined (reading 'user'). This is my code for the login/sign up page.
The data I'm trying to access is in the picture below:
(Auth.js)
const Auth = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const [isSignup, setIsSignup] = useState(false);
const [inputs, setInputs] = useState({
name: "",
username: "",
email: "",
password: ""
})
const handleChange = (e) => {
setInputs(prevState => {
return {
...prevState,
[e.target.name]: e.target.value
}
})
}
const sendRequest = async (type = '') => {
const res = await axios.post(`/user/${type}`, {
name: inputs.name,
email: inputs.email,
username: inputs.username,
password: inputs.password,
}).catch(error => console.log(error))
const data = await res.data;
console.log(data)
return data;
}
const handleSubmit = (e) => {
e.preventDefault()
console.log(inputs)
if (isSignup) {
sendRequest("signup")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
} else {
sendRequest("login")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
}
}
Redux store file
const authSlice = createSlice({
name: "auth",
initialState: { isLoggedIn: false },
reducers: {
login(state) {
state.isLoggedIn = true
},
logout(state) {
state.isLoggedIn = false
}
}
})
export const authActions = authSlice.actions
export const store = configureStore({
reducer: authSlice.reducer
})
Chaining promises using .then() passes the resolved value from one to the next. With this code...
sendRequest("...")
.then(() => dispatch(authActions.login()))
.then(() => navigate("/posts"))
.then(data => localStorage.setItem('token', data.user))
You're passing the returned / resolved value from navigate("/posts") to the next .then() callback. The navigate() function returns void therefore data will be undefined.
Also, your redux action doesn't return the user so you can't chain from that either.
To access the user data, you need to return it from sendRequest()...
const sendRequest = async (type = "") => {
try {
const { data } = await axios.post(`/user/${type}`, { ...inputs });
console.log("sendRequest", type, data);
return data;
} catch (err) {
console.error("sendRequest", type, err.toJSON());
throw new Error(`sendRequest(${type}) failed`);
}
};
After that, all you really need is this...
sendRequest("...")
.then((data) => {
dispatch(authActions.login());
localStorage.setItem('userId', data.user._id);
navigate("/posts");
});
Since you're using redux, I would highly recommend moving the localStorage part out of your component and into your store as a side-effect.

acess specific field graphql react

My code looks like this:
interface MutationProps {
username: string;
Mutation: any;
}
export const UseCustomMutation: React.FC<MutationProps> | any = (username: any, Mutation: DocumentNode ) => {
const [functionForDoingAction, { data, loading, error }] = useMutation(
Mutation,
{
variables: {
username,
},
}
);
useEffect(() => {
// fn trigger for change data
functionForDoingAction({
variables: {
username: username,
},
});
console.log(JSON.stringify(data));
console.log(JSON.stringify(error, null, 2));
}, []);
if (loading) return "loading...";
if (error) return `Submission error! ${error.message}`;
return data;
};
export const DisplayUser = () => {
const GET_USER = gql`
mutation GetUser($username: String!) {
getUser(username: $username) {
pfp
username
password
age
CurrentLive
ismod
description
fullname
}
}
`;
const { username }: { username: any } = useParams();
const MyData = UseCustomMutation(username, GET_USER);
console.log(JSON.stringify(MyData));
I wanna a access MyData.pfp but it gives me this error:
TypeError: Cannot read property 'pfp' of undefined
if it matters when i go on e.g. localhost:3000/user/dakepake variable MyData looks like this:
UserProfile.tsx:39 {"getUser":{"pfp":""https://i.pinimg.com/564x/65/25/a0/6525a08f1df98a2e3a545fe2ace4be47.jpg"","username":""dakepake"","password":""mohikanac10"","age":14,"CurrentLive":"""","ismod":false,"description":""this user dont have a bio yet"","fullname":""damjan alimpic"","__typename":"GetUserResponse"}}
How can I fix this?
i fixed this on my own , i just replaced MyData.pfp whit MyData.getUser.pfp and now its working

Converting from class to functional component with async state setting

I have a simple class-based component that I'm trying to convert to a function-based component, but am running into all kinds of dead ends.
My component is a straightforward adaptation of the boilerplate gifted-chat package, and uses Watson Assistant as a backend to provide responses. There's nothing complex about the backend part, these are just thin wrappers on Watson Assistants's API:
getSessionID = async (): Promise<string>
gets a session ID for use in communicating with the backend, and
sendReply = async (reply: string, sessionID: string): Promise<string>
returns Assistant's response to the string provided as a reply. These are not the source of the trouble I'm having (the bodies of both could be replaced with return await "some string" and I'd have the same issues): the class-based version (below) works perfectly.
But I'm at a loss to figure out how to convert this to a functional form, in particular:
I'm struggling to find a suitable replacement for componentWillMount. Using useEffect with sessionID as state results in errors: getMessage gets called (even if I await) before the required sessionID is set.
I can avoid this by not making sessionID state (which it arguably shouldn't be) and just making it a global (as in the functional attempt below). But even if I do this:
After each user reply, and receipt of a response, the user reply is removed from the conversation, so that the entire conversation just consists of generated replies.
Both of these problems are, I think, linked to the lack of callbacks in the hook-based state setting idiom, but the issue could also lie elsewhere. In any case, I'm at a loss to know what to do.
Chatter.tsx (working class based version)
import React from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
class Chatter extends React.Component {
state = {
messages: [],
sessionID: null,
}
componentWillMount() {
WatsonAssistant.getSessionID()
.then((sID) => {
this.setState( {
sessionID: sID,
} )
} )
.then(() => this.getMessage(''))
.catch((error) => {
console.error(error)
} )
}
onSend = (message = []): void => {
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ), () => {
this.getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
} )
}
getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, this.state.sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
this.setState((previousState) => ( {
messages: GiftedChat.append(previousState.messages, message),
} ))
}
render() {
return (
<GiftedChat
messages={ this.state.messages }
onSend={ messages => this.onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
}
export default Chatter
Chatter.tsx (failed function based attempt)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage(''))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages)
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '))
}
const getMessage = async (text: string): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(messages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Chatter.tsx (working function based version)
import React, {FC, ReactElement, useEffect, useState } from 'react'
import { GiftedChat } from 'react-native-gifted-chat'
import WatsonAssistant from "../services/WatsonAssistant"
let sessionID: string
const Chatter: FC = (): ReactElement => {
const [ messages, setMessages ] = useState([])
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => sessionID = sID )
.then(() => getMessage('', []))
.catch((error) => {
console.error(error)
} )
}
fetchData()
}, [ ])
const onSend = async (message = []) => {
const newMessages = await GiftedChat.append(messages, message)
await setMessages(newMessages) // Apparently, no waiting goes on here
await getMessage(message[0].text.replace(/[\n\r]+/g, ' '), newMessages)
}
const getMessage = async (text: string, currentMessages): Promise<void> => {
let response = await WatsonAssistant.sendReply(text, sessionID)
let message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
}
await setMessages(await GiftedChat.append(currentMessages, message))
}
return (
<GiftedChat
messages={ messages }
onSend={ messages => onSend(messages) }
user={ {
_id: 1,
} }
/>
)
}
export default Chatter
Ok, since I don't have your full code I'm not sure this will just work as-is (in particular without the types from your dependencies I'm not sure if/how much the compiler will complain), but should give you something you can adapt easily enough.
const reducer = ({ messages }, action) => {
switch (action.type) {
case 'add message':
return {
messages: GiftedChat.append(messages, action.message),
};
case 'add sent message':
return {
// Not sure if .append is variadic, may need to adapt
messages: GiftedChat.append(messages, action.message, action.message[0].text.replace(/[\n\r]+/g, ' ')),
}
}
};
const Chatter = () => {
const [sessionID, setSessionID] = useState(null);
const [messages, dispatch] = useReducer(reducer, []);
const getMessage = async (text: string, sessionID: number, type: string = 'add message'): Promise<void> => {
const response = await WatsonAssistant.sendReply(text, sessionID);
const message = {
_id: Math.round(Math.random() * 1000000).toString(),
text: response,
createdAt: new Date(),
user: {
_id: '2',
name: 'Watson Assistant',
},
};
dispatch({
type,
message,
});
};
useEffect(() => {
const fetchData = async () => {
WatsonAssistant.getSessionID()
.then(sID => (setSessionID(sID), sID))
.then(sID => getMessage('', sID))
.catch((error) => {
console.error(error)
});
}
fetchData();
}, []);
return (
<GiftedChat
messages={messages}
onSend={messages => getMessage(messages, sessionID, 'add sent message')}
user={{
_id: 1,
}}
/>
);
};
Main difference is useReducer. As far as I can tell in the original code you had two actions: append this message or append this message and then a copy of it with the text regex replaced. I've used different dispatches to the reducer to handle the cases rather than the callback to setState. I've modified your attempt at useEffect, here I'm (ab)using the comma operator to return the ID returned from the service so that it can be fed directly to getMessage as a parameter rather than relying on state that hasn't been updated yet.
I'm still kinda skeptical in general about the hooks API, but assuming this works I actually think it simplifies the code here.

Categories