Call yield put() inside a success callback - reactjs

I am brand new to React and Redux.
I am using react-redux to call the AWS Cognito service which takes an object containing a success and failure callback. I get my JWT back from AWS Cognito when I console.log inside my success callback; however, how can I yield put() inside this callback since it's not a generator function (function*).
Here's some code:
export function* getAWSToken(){
// username, password and userPool come from react state
// not showing code for brevity.
const userData = {
Username: username,
Pool: userPool,
};
const authenticationData = {
Username : username,
Password : password,
};
const cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
const authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
// This here is the callback
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess(result){
yield put(cognitoTokenObtained(result.getIdToken().getJwtToken())
},
onFailure(err){}
});
}

If you're using redux-saga (which is awesome), you can use the call effect to transform an asyc callback like cognitoUser.authenticateUser into a set of instructions to be executed by the middlewhere.
When the middleware resolves the call it will step through the generator to the next yield statement, you can assign the return result to a variable that you can then place in your state using a put effect.
export function* getAWSToken(){
// username, password and userPool come from react state
// not showing code for brevity.
const userData = {
Username: username,
Pool: userPool,
};
const authenticationData = {
Username : username,
Password : password,
};
const cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
const authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
// Note: since you're invoking an object method
// you'll want to pass the object's context (this) with the call via an array
const token = yield apply(cognitoUser, cognitoUser.authenticateUser, [authenticationDetails, { onSuccess(response){return response}, onFailure(){} }]);
yield put(cognitoTokenObtained(token.getIdToken().getJwtToken());
}
There's also this incredible tutorial I'd highly recommend.
Edit: You left out some code for brevity, but I'd highly recommend wrapping the code inside the generator in try catches since you're depending on external IO from an API.

Related

How do I get an object from local storage after json.stringify()

I am currently creating a login page with ReactJS. This page utilises an API which has an 'authorise' function confirming whether the entered details are authorised to use the API. To handle the API calls, I have created a class which has a function for each possible API call.
If the user is able to login successfully (if the authorise function returns a status of 200) they are redirected to a 'ProjectSelect' page/component. From this Project Select page the user will be able to select a specific project from their project list.
Now, here is where the problem lies. I need to now use the API instance from the 'Login' component in the 'ProjectSelect' component. The way in which I am currently trying to do this involves storing it in local storage as such:
Login.jsx:
import {API} from 'filepath';
const api = new API();
async function loginUser(email, password, appid) {
return api.authorise(email, password, appid);
}
export default function Login() {
const classes = useStyles();
const [email, setEmail] = useState();
const [password, setPassword] = useState();
const [appid, setAppId] = useState();
const handleSubmit = async e => {
e.preventDefault();
const response = await loginUser(
email,
password,
appid
);
console.log(response);
if (response.status === 200) {
swal("Success", "Logged In", "success", {
buttons: false,
timer: 2000,
})
.then((value) => {
localStorage.setItem('access_token', response.data.access_token);
localStorage.setItem('api', JSON.stringify(api));
window.location.href = "/bimpluslogin";
});
} else {
swal("Failed", "Login Failed", "error");
}
}
ProjectSelect.jsx:
const api = JSON.parse(localStorage.getItem('api'));
let allProjects = api.getAllProjects(); // Error occurs here ('getAllProjects()' does not exist)
I am aware that if you JSON.stringify() an object it will only take the key values leaving it basically unusable when parsed back through the JSON.
What I've tried:
I have tried to create a new API instance in the ProjectSelect component and storing the auth details in local storage however that causes a lot of issues and doesn't allow me to use all of the functions.
I would appreciate any suggestions on how to store an object from one component and then use that exact same object in another component. Thanks in advance.

How to get current provider of session in Next-auth

In Next-auth, We can get session related info like user: {name, email .. etc}
something as follows:
import { useSession } from "next-auth/client"
export default function Component() {
const [session, loading] = useSession()
if (session) {
return <p>Signed in as {session.user.email}</p>
}
return Sign in
}
I want to get also the current provider name in my component to be used. For you your information, next-auth supports login by many providers as Facebook, Twitter .. etc
For example, if the user logged in via Twitter Api, I want to get this piece of info and print it in his profile page.
Sources:
https://next-auth.js.org/v3/getting-started/client#usesession
Callbacks can be used for passing additional data to session object.
Provider details are provided the first time user signs in.
You can use jwt callback to store data in jwt cookie.
In [...nextauth].js :
const callbacks = {}
callbacks.jwt = async function jwt(token, user , account) {
if (user) {
token = { id: user.id , provider:account.provider , ...moreData}
}
return token
}
const options = {
providers,
callbacks
}
Note: You also need to use session callback for passing the token data to useSession hook.
callbacks.session = async function session(session, token) {
session.user = {
provider : token.provider,
id: dbUser.id,
profile: dbUser.profile,
}
return session
}
export default (req, res) => NextAuth(req, res, options)
This way provider will be stored in the token for subsequent requests.
Learn more about callbacks here : Callbacks

Redux toolkit RTK query mutation not getting returning data

Hi I recently learned the new react toolkit with the rtk query tool, and I am trying to put in a login system together using the createApi from the rtk package.
After giving it a test on the login button pressed, I see the network request going through without any issue(status code 200), and I get a response object providing user, token, however, when I try to get the returning data using useLoginMutation I get an undefined value.
below is the code for my endpoint which is injected in a base api:
export const apiLogin = theiaBaseApi.injectEndpoints({
endpoints: (build) => ({
loginUser: build.mutation<UserReadonly, loginValuesType | string>({
query: (values: loginValuesType, redirect?: string) => {
const { username, password } = values;
const header = gettingSomeHeaderHere
return {
url: "login",
method: "GET",
headers,
crossDomain: true,
responseType: "json",
};
},
}),
}),
});
export const { useLoginUserMutation } = apiLogin
then inside my React component I destructure the mutation result such like below:
const [login, {data, isLoading}] = useLoginUserMutation();
const submitLogin = () => {
// pass in username password from the form
login({username, password});
}
Suppose if I console log out data and isLoading I assume that I will see data: {user: "abc", token: "xyz"}, because under network tab of my inspect window I can see the response of this network request, but instead I am seeing data: undefined
Does any have experience on solving this?
Oh I found the reason, it was a very careless mistake. I had to wrap the reducer to my store, which was what I was missing
In my case the issue was that I was trying to access the UseMutationResult object inside onClick callback. And the object was not updating inside the callback, even though in the component the values were accurate.
If I put the log outside it's working just fine.
here is an example for better understanding (inside handleAddPost the mutationResult is not updating)
Here is a code sample (in case link is not working):
const Component = () => {
const [addPost, mutationResult] = useAddPostMutation();
...
const handleAddPost = async () => {
...
console.log("INSIDE CALLBACK isLoading and other data is not updating:");
console.log(JSON.parse(JSON.stringify(mutationResult)))
...
};
// in the example this is wrapped in an useEffect to limit the number of logs
console.log(mutationResult.data,"OUTSIDE CALLBACK isLoading and other data is working:")
console.log(JSON.parse(JSON.stringify(mutationResult)))
return (
...
<Button
...
onClick={handleAddPost}
>
Add Post
</Button>
...

Next JS router not changing page in async function

I am very new to promises and JS in general, but I am working on creating a register page using a tutorial on YouTube. I am currently using next.js with react and TypeScript to redirect to the user to the home page if no errors occur when registering (no email, password too short, etc.), but the router won't redirect the user within an async onSubmit function.
Here is the current non-working code. The useRegisterMutation hook just creates a function that takes in the input fields and creates a user in the db for me:
const Register: React.FC<registerProps> = ({}) => {
const router = useRouter();
const [register] = useRegisterMutation();
return (
<Wrapper>
<Formik initialValues={{first_name: "", last_name: "", email: "", password: ""}}
onSubmit={async (values, {setErrors}) => {
const response = await register({
variables: {
email: values.email,
pass: values.password,
fname: values.first_name,
lname: values.last_name
}
});
// A user is returned, meaning there were no errors
if (response.data?.createUser?.user) {
await router.push("/");
// An error was returned, meaning that the user was not created
} else if (response.data?.createUser?.errors) {
setErrors(setErrorMap(response.data.createUser.errors));
}
}}>
// ----------- Unimportant HTML -----------
</Formik>
</Wrapper>
);
}
export default Register;
When I remove async / await from the onSubmit function, the router begins correctly working, but the if statements do not work correctly because they are expecting response to be a promise, so they fail every time. Is there some trick to get the router to push to the correct url while still keeping the function as a promise?
Your async code seems to be fine, and you're saying the redirect works without async.
It seems that you might not be getting what you're expecting to get.
I'd log the response and response.data and all of the subsequent properties that you need for your if statement.
p.s.
Feel free to share the tutorial, you might've missed something or maybe it has a mistake.

Pass Data to Firebase auth.user().onCreate Cloud Function

I'm testing out firebase cloud functions and I'm curious as to how to pass data from my form to the cloud function. I've created a registration form that accepts several fields - my plan is to use firebase to create an auth account and have a cloud function that runs whenever a auth account is created. This function will create a user document inside Firestore.
Code below gives an idea of my frontend setup
import React from "react"
import firebase from "firebase"
// create some config for firebase
const app = firebase.initializeApp(config)
const auth = app.auth()
function Form({onSubmit}){
function handleSubmit(e){
e.preventDefault()
const {email, password, firstName, lastName, age} = e.target.elements
const user = {email: email.value, password: password.value, firstName: firstName.value, lastName: lastName.value, age: age.value}
onSubmit().then(() => {}, (err) => {})
}
return <form onSubmit={handleSubmite}>
{/*form fields..*/}
</form>
}
// Register function for firebase
function register(email, password){
return auth.createUserWithEmailAndPassword(email, password)
}
// Parent for form component
function Container(){
return <Form onSubmit={register}/>
}
This is what my cloud function looks like:
const functions = require("firebase-functions")
const admin = require("firebase-admin")
admin.initializeApp()
const db = admin.firestore()
const createUser = (userData, context) => {
const { email } = userData
console.log(userData)
return db
.collection("users")
.doc()
.set({
email,
firstName: "TEST",
})
.catch(console.error)
}
module.exports = {
authOnCreate: functions.auth.user().onCreate(createUser),
}
Any idea how to do this? Am I thinking about this incorrectly? I'm a bit new to firebase.
TLDR; How do I send custom data from a form to a cloud function that will create a user in firestore on a new user being created in auth?
my plan is to use firebase to create an auth account and have a cloud function that runs whenever a auth account is created. This function will create a user document inside Firestore.
This is not possible. Since your Cloud Functions triggers on auth.user().onCreate it only has access to the information that is available with that event. You can't pass additional information to a background function.
The most common solutions are:
Write the document after the user is created, either directly from the client or by having the client call another Cloud Function directly.
Pass all data that is needed to create the user and document to a Cloud Function, and then let the Cloud Function handle both the creation of the user account and the document.

Resources