I've created a contact form in ReactJS and am trying to send an email to myself using Nodemailer with firebase functions. The email will consist of whatever the person has filled out in the contact form.
I have tried the firebase function using postman and it works, I successfully receive an email when the endpoint is tried.
However when I try to call it in my React app, nothing is sent, and there are no errors reported. I have looked online to see what I am doing wrong but no luck.
Here is my code.
firebase functions index.js
// import needed modules
const functions = require("firebase-functions");
const nodemailer = require("nodemailer");
const office365Email = functions.config().outlook365.email;
const office365Password = functions.config().outlook365.password;
// create and config transporter
const transporter = nodemailer.createTransport({
host: "smtp.office365.com",
port: 587,
secure: false, // true for 465, false for other ports
auth: {
user: office365Email,
pass: office365Password,
},
});
// export the cloud function called `sendEmail`
exports.sendEmail = functions.https.onCall((data, context) => {
// for testing purposes
console.log(
"from sendEmail function. The request object is:",
JSON.stringify(data.body)
);
const email = data.email;
const name = data.name;
const message = data.message;
// config the email message
const mailOptions = {
from: email,
to: office365Email,
subject: "New message from the nodemailer-form app",
text: `${name} says: ${message}`,
};
return transporter.sendMail(mailOptions).then(() => {
return { success: true };
}).catch(error => {
console.log(error);
});
});
here is my reactjs code:
import React, {useRef, useState} from "react";
import {useAuth} from "../contexts/AuthContext";
import {useHistory} from "react-router-dom";
import firebase from 'firebase/app';
import classes from "./SyncManagerDemo.module.scss";
import {Button, Container, Form} from "react-bootstrap";
export default function ContactUs() {
const nameRef = useRef();
const emailRef = useRef();
const messageRef = useRef();
const firmRef = useRef();
const history = useHistory();
const [error, setError] = useState("")
const [loading, setLoading] = useState(false)
const sendEmail = firebase.functions().httpsCallable('sendEmail');
async function handleSubmit(e) {
e.preventDefault()
console.log("nameRef: " + nameRef.current.value);
console.log("emailRef: " + emailRef.current.value);
console.log("messageRef: " + messageRef.current.value);
return /*const test =*/ await sendEmail({
name: nameRef.current.value,
email: emailRef.current.value,
message: messageRef.current.value
}).then(result => {
return console.log("message sent");
}).catch(error => {
console.log("error occurred.");
return console.log(error);
});
}
return (
<React.Fragment>
<div className={classes.body}>
<Container>
<div className={classes.body}>
<h2>Contact Us</h2>
<Form onSubmit={handleSubmit}>
<Form.Group id="email">
<Form.Label>Email</Form.Label>
<Form.Control type="email" ref={emailRef} required/>
</Form.Group>
<Form.Group id="name">
<Form.Label>Name</Form.Label>
<Form.Control type="text" ref={nameRef} required/>
</Form.Group>
<Form.Group id="message">
<Form.Label>Message</Form.Label>
<Form.Control type="text" ref={messageRef} required/>
</Form.Group>
<Form.Group id="firm">
<Form.Label>Firm</Form.Label>
<Form.Control type="text" ref={firmRef} required/>
</Form.Group>
<Button disabled={loading} className="w-100" type="submit">
Send
</Button>
</Form>
</div>
</Container>
</div>
</React.Fragment>
)
not sure if this helps but my postman POST request body I send through looks like this which works:
{
"data": {
"email": "my email#email.com",
"name": "test name",
"message": "hello world"
}
}
Any and all help is appreciated.
Related
I have a register page which asks for username, email, password and verify password.
in firebase, authentication, it saves the account
but in the firestore - collection - users - when a new account is made, it does not populate
I'm still quite new to coding, this is my first project. In another page of the app, I do have a section where images are stored as a collection, and I can get these to render on a different page. But for the past 12 hours I've been trying everything that I could think of and I can not get this to work. please help
import { useState } from "react";
import "./forms.css";
import "./Header.css";
import { auth, createUserDocument } from "./firebase";
import { useNavigate, Link } from "react-router-dom";
import {
createUserWithEmailAndPassword,
sendEmailVerification,
} from "firebase/auth";
import { useAuthValue } from "./AuthContext";
function Register() {
const [displayName, setDisplayName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState("");
const navigate = useNavigate();
const { setTimeActive } = useAuthValue();
const validatePassword = () => {
let isValid = true;
if (password !== "" && confirmPassword !== "") {
if (password !== confirmPassword) {
isValid = false;
setError("Passwords does not match");
}
}
return isValid;
};
const register = (e) => {
e.preventDefault();
setError("");
if (validatePassword()) {
// Create a new user with email and password using firebase
const { user } = createUserWithEmailAndPassword(auth, email, password)
.then(() => {
sendEmailVerification(auth.currentUser)
.then(() => {
console.log(user);
createUserDocument(user, { displayName });
setTimeActive(true);
navigate("/verify-email");
})
.catch((err) => alert(err.message));
})
.catch((err) => setError(err.message));
}
setEmail("");
setPassword("");
setConfirmPassword("");
setDisplayName("");
};
return (
<div className="center">
<div className="auth">
<h2>Create an Account</h2>
{error && <div className="auth__error">{error}</div>}
<form onSubmit={register} name="registration_form">
<input
type="displayName"
value={displayName}
placeholder="Enter your user name"
required
onChange={(e) => setDisplayName(e.target.value)}
className="header__inputfields"
/>
<input
type="email"
value={email}
placeholder="Enter your email"
required
onChange={(e) => setEmail(e.target.value)}
className="header__inputfields"
/>
<input
type="password"
value={password}
required
placeholder="Enter your password"
onChange={(e) => setPassword(e.target.value)}
className="header__inputfields"
/>
<input
type="password"
value={confirmPassword}
required
placeholder="Confirm password"
onChange={(e) => setConfirmPassword(e.target.value)}
className="header__inputfields"
/>
<button
type="submit"
style={{ display: "flex", justifyContent: "center" }}
className="header__loginbutton"
>
Register
</button>
</form>
<span>
Already have an account?
<Link to="/login">login</Link>
</span>
</div>
</div>
);
}
export default Register;
// firebase
// Import the functions you need from the SDKs you need
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
//lines from tinderclone app
import firebase from "firebase/compat/app";
import "firebase/compat/auth";
import "firebase/compat/firestore";
// Your web app's Firebase configuration
const firebaseConfig = {
apiKey: "
authDomain: ( deleted this info )
projectId:
storageBucket: "",
messagingSenderId: "",
appId: "
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
//lines from tinderclone to export default database
const firebaseApp = firebase.initializeApp(firebaseConfig);
const database = firebaseApp.firestore();
export default database;
export { auth };
// below here are user id documents addition
export const firestore = firebase.firestore();
export const createUserDocument = async (user, additionalData) => {
if (!user) return;
const userRef = firestore.doc(`users/${user.uid}`);
const snapshot = userRef.get();
if (!snapshot.exists) {
const { email } = user;
const { displayName } = additionalData;
try {
userRef.set({
displayName,
email,
createdAt: new Date(),
});
} catch (error) {
console.log("Error in creating user", error);
}
}
};
Here I want to register user with image so I want to pass both image and name in my formdata.
I am able to upload the file using some guideline (I am not good with react) but I am not able to pass the input name with my formdata. which procedure to follow?
import axios from "axios";
import React, { useState, useEffect } from 'react'
import { LinkContainer } from 'react-router-bootstrap'
import { Table, Button, Row, Col } from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux'
const UPLOAD_ENDPOINT = "http://127.0.0.1:8000/api/orders/vendor/register/";
function VendorRegistration() {
const [file, setFile] = useState(null);
const [name, setName] = useState("");
const { userInfo } = useSelector((state) => state.userLogin);
const handleSubmit = async (event) => {
event.preventDefault();
const formData = new FormData();
formData.append("avatar", file);
formData.append("name", name);
const resp = await axios.post(UPLOAD_ENDPOINT, formData, {
headers: {
"content-type": "multipart/form-data",
Authorization: `Bearer ${userInfo.token}`,
},
});
console.log(resp.status)
};
return (
<form onSubmit={handleSubmit}>
<h1>React File Upload</h1>
<input type="file" onChange={(e) => setFile(e.target.files[0])} />
<input type="text" onChange={(e) => setName(e.target.value)} value={name} />
<button type="submit" disabled={!(file && name)}>
Upload File
</button>
{resp.status == 200(
<h1>ok</h1>
)
}
</form>
);
}
export default VendorRegistration;
You'll just want to bind the other input to state as per usual, and then add that value to the form data.
I added rudimentary validation that prevents clicking the submit button unless both fields are filled in, too.
EDIT: I also added status responses, as per comments.
import React from "react";
import axios from "axios";
const UPLOAD_ENDPOINT = "http://127.0.0.1:8000/api/orders/vendor/register/";
function VendorRegistration() {
const [file, setFile] = useState(null);
const [name, setName] = useState("");
const [status, setStatus] = useState("");
const { userInfo } = useSelector((state) => state.userLogin);
const handleSubmit = async (event) => {
setStatus(""); // Reset status
event.preventDefault();
const formData = new FormData();
formData.append("avatar", file);
formData.append("name", name);
const resp = await axios.post(UPLOAD_ENDPOINT, formData, {
headers: {
"content-type": "multipart/form-data",
Authorization: `Bearer ${userInfo.token}`,
},
});
setStatus(resp.status === 200 ? "Thank you!" : "Error.");
};
return (
<form onSubmit={handleSubmit}>
<h1>React File Upload</h1>
<input type="file" onChange={(e) => setFile(e.target.files[0])} />
<input type="text" onChange={(e) => setName(e.target.value)} value={name} />
<button type="submit" disabled={!(file && name)}>
Upload File
</button>
{status ? <h1>{status}</h1> : null}
</form>
);
}
export default VendorRegistration;
I am writing unit test case for login.
I am unsure about how to test handle submit as it contains one of the service call in the form of getToken() method, it would be greate if someone can guide me through how to handle this situation.
export const getToken = (credentials) => {
const token = 'abccss';
if (
credentials.username === 'test#test.com' &&
credentials.password === '123'
) {
return token;
} else {
return null;
}
};
The above code fetches user name and password and sends it to login in handleSubmit() function
//all imports(loginservice,auth etc etc)
import './Login.scss';
const Login = () => {
const [email, setEmail] = useState('');
const [pwd, setPwd] = useState('');
const authCon = useContext(AuthContext);
const handleSubmit = (e) => {
e.preventDefault();
const token = getToken({ username: email, password: pwd });
if (token) {
authCon.login(token);
window.location.href = '/dashboard';
}
};
return (
<div className="div-login">
<div className="div-login-logo">
<img src={logo} alt="Logo"></img>
</div>
<div>
<form onSubmit={handleSubmit}>
<input
className="credentials-input"
type="email"
value={email}
placeholder="Email Address"
required
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="credentials-input"
type="password"
value={pwd}
placeholder="Password"
required
onChange={(e) => setPwd(e.target.value)}
/>
<button className="login-button" type="submit">
Log In
</button>
</form>
</div>
</div>
);
};
export default Login;
Test Code
test('Submit shoud work successfully', () => {
const mockLogin = jest.fn();
const { getByRole } = render(<Login handleSubmit={mockLogin} />);
const login_button = getByRole('button');
fireEvent.submit(login_button);
expect(mockLogin).toHaveBeenCalledTimes(1);
});
expect(jest.fn()).toHaveBeenCalledTimes(expected)
Expected number of calls: 1
Received number of calls: 0
As I am new to React, help will be appreciated.
The actual issue is handleSubmit is not a props of Login component.
Also you can't test the internal methods of a component using React testing Library, you have to move the handleSubmit method to either parent component or a common file and pass it to the login component or import it so that you can mock the method and perform the test.
Move the getToken and handleSubmit to a common file like below,
common.ts
export const getToken = (credentials:any) => {
const token = 'abccss';
if (
credentials.username === 'test#test.com' &&
credentials.password === '123'
) {
return token;
} else {
return null;
}
};
export const handleSubmit = (e:any, email:string, pwd: string) => {
e.preventDefault();
const token = getToken({ username: email, password: pwd });
if (token) {
// authCon.login(token);
window.location.href = '/dashboard';
}
};
Modify Login.ts as like below ( see below handleSubmit is not internal and its imported from common.ts file so we that we can mock it)
import React, { useContext, useState } from 'react';
import { getToken, handleSubmit } from './common';
const Login = () => {
const [email, setEmail] = useState('');
const [pwd, setPwd] = useState('');
// const authCon = useContext(AuthContext);
return (
<div className="div-login">
<div className="div-login-logo">
{/* <img src={logo} alt="Logo"></img> */}
</div>
<div>
<form onSubmit={(e) => handleSubmit(e, email, pwd)}>
<input
className="credentials-input"
type="email"
value={email}
placeholder="Email Address"
required
onChange={(e) => setEmail(e.target.value)}
/>
<input
className="credentials-input"
type="password"
value={pwd}
placeholder="Password"
required
onChange={(e) => setPwd(e.target.value)}
/>
<button className="login-button" type="submit">
Log In
</button>
</form>
</div>
</div>
);
};
export default Login;
And finally Login.test.tsx shown below
import { fireEvent, render, screen } from '#testing-library/react';
import Login from './Login';
import * as CommonModule from './common';
jest.mock('./common');
test('Submit shoud work successfully', () => {
const mockLogin = jest.spyOn(CommonModule,'handleSubmit').mockImplementation();
const { getByRole } = render(<Login />);
const login_button = getByRole('button');
fireEvent.submit(login_button);
expect(mockLogin).toHaveBeenCalledTimes(1);
});
Test Result :
This is my Signupcomponent
const SignupComponent = () => {
const [values, setValues] = useState({
username: 'silvio1',
name: 'Silvioo',
email: 'berlusconi#gmail.com',
password: '123ooo007',
});
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
const { username, name, email, password } = values;
const user = {username, name, email, password};
await axios.post('${API)/signup', user);
};
const handleChange = name => e => {
setValues({ ...values, [name]: e.target.value });
};
const showLoading = () => (loading ? <div className="alert alert-info">Loading...</div> : '');
const signupForm = () => {
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<input
value={values.username}
onChange={handleChange('username')}
type="text"
className="form-control"
placeholder="Type your username"
/>
</div>
<div className="form-group">
<input
value={values.name}
onChange={handleChange('name')}
type="text"
className="form-control"
placeholder="Type your name"
/>
</div>
<div className="form-group">
<input
value={values.email}
onChange={handleChange('email')}
type="email"
className="form-control"
placeholder="Type your email"
/>
</div>
<div className="form-group">
<input
value={values.password}
onChange={handleChange('password')}
type="password"
className="form-control"
placeholder="Type your password"
/>
</div>
<div>
<button className="btn btn-primary">Signup</button>
</div>
</form>
);
};
return <React.Fragment>
{showLoading()}
{signupForm()}
</React.Fragment>;
};
export default SignupComponent;
EDIT
I changed my code(zhulien's accepted answer).
Signup page appears,I try to sign up user.
I got error
Unhandled Runtime Error
Error: Request failed with status code 404
Call Stack
createError
node_modules/axios/lib/core/createError.js (16:0)
settle
node_modules/axios/lib/core/settle.js (17:0)
XMLHttpRequest.handleLoad
node_modules/axios/lib/adapters/xhr.js (62:0)
Frontend folder
components
config.js
next.config.js
node_modules
package.json
package-lock.json
pages
My pages folder
_document.js
index.js
signin.js
signup.js
signup.js imports the code above
import Link from 'next/link';
import Layout from '../components/Layout';
import SignupComponent from '../components/frontauth/SignupComponent';
const Signup = () => {
return (
<Layout>
<h2>Signup page</h2>
<SignupComponent />
</Layout>
);
};
My next.config.js
{
APP_NAME: 'BLOG FRONTEND',
APP_DEVELOPMENT: 'http://localhost:3000',
PRODUCTION: false
}
And config.js
const { publicRuntimeConfig } = getConfig();
console.log(publicRuntimeConfig);
export const API = publicRuntimeConfig.PRODUCTION
? 'https://cryptoblog.com'
: 'http://localhost:3000';
export const APP_NAME = publicRuntimeConfig.APP_NAME;
I am new to React and React Hooks. How to solve this problem?
First of all, you're trying to access {username}(which doesn't exist) instead of the state property which is values.username. Furthermore, don't use hooks in event handlers, they should be used in the top level body of the component or in custom hooks only. Checkout this: React hooks rules.
So:
In your form you have to use the state(values) properties.
Extract useEffect hook in the main body flow of the component or BETTER remove it altogether as you're not using it properly currently. You're better of with just the simple event handler for form submit which should post the data somewhere without setting any state.
Your code could look something like:
import axios from 'axios';
import React, { useEffect, useState } from 'react';
import { API } from '../../config';
const SignupComponent = () => {
const [values, setValues] = useState({
username: 'silvio1',
name: 'Silvioo',
email: 'berlusconi#gmail.com',
password: '123ooo007',
});
const [loading, setLoading] = useState(false);
const handleSubmit = async (e) => {
e.preventDefault();
const { username, name, email, password } = values;
const user = {username, name, email, password};
await axios.post('${API)/signup', user);
};
const handleChange = name => e => {
setValues({ ...values, [name]: e.target.value });
};
const showLoading = () => (loading ? <div className="alert alert-info">Loading...</div> : '');
const signupForm = () => {
return (
<form onSubmit={handleSubmit}>
<div className="form-group">
<input
value={values.username}
onChange={handleChange('username')}
type="text"
className="form-control"
placeholder="Type your username"
/>
</div>
this is how it should be:
useEffect(() => {
postUser();
}, []);
not inside the function the way you have done it:
const handleSubmit = e => {
e.preventDefault();
setValues({...values});
const { username, name, email, password } = values;
const user = {username, name, email, password};
async function postUser () {
const result = await axios.post('${API)/signup', user);
};
useEffect(() => {
postUser();
}, []);
};
UseEffects aren't meant to be placed inside your functions.Just place them inside your functional component,with some value(or no value) inside your dependency array of the useEffect.These values present inside the array will trigger the useEffect whenever they get changed.
I recently added redux-forms to my React/Redux/Redux-thunk project, and now if I submit information to a redux-thunk action, the info is submitted successfully, but nothing after the return function fires.
Everything was working as intended before adding redux-forms, so I think thatÅ› the source of the problem, but even after double checking the docs for Redux, redux-form, and redux-thunk, I can't find any obvious errors in my connections or setup. What am I missing?
My reducer:
import {combineReducers} from 'redux';
import {reducer as formReducer} from 'redux-form';
import signUpReducer from './containers/SignUp/reducer';
export default function createReducer() {
return combineReducers({
signUpReducer,
form: formReducer
});
}
My form component:
import React from 'react';
import {Field, reduxForm} from 'redux-form';
import {validate, onHandleInfoSubmit} from '../../containers/SignUp/actions';
import {inputField} from '../../components/SmallUIBits/FormFields';
let UserSignUpForm = props => {
const {handleSubmit} = props;
return (
<form className="NewAccountForm" onSubmit={handleSubmit}>
<div className="text-center">
<small className="center-align">All fields are required</small>
</div>
<div className="AccountLine form-group">
<Field classes="LoginInput form-control form-control-sm"
component={inputField}
label="Email address"
name="email"
placeholder="Enter email"
required="true"
type="text"
value={props.email} />
</div>
<div className="form-row">
<div className="col-lg-6 col-md-6 col-xs-12">
<Field aria-describedby="passwordHelp"
classes="LoginInput form-control form-control-sm"
component={inputField}
label="Password"
name="password"
placeholder="Password"
required="true"
type="password"
value={props.password} />
<div className="col-lg-6 col-md-6 col-xs-12">
<Field classes="LoginInput form-control form-control-sm"
component={inputField}
label="Confirm password"
name="passwordConfirm"
placeholder="Re-enter your password"
required="true"
type="password"
value={props.passwordConfirm} />
</div>
</div>
</form>
);
};
export default UserSignUpForm = reduxForm({
form: 'UserSignUpForm',
validate,
onSubmit: onHandleInfoSubmit
})(UserSignUpForm);
My form container
import React from 'react';
import UserSignUpForm from '../../components/UserSignUpForm';
import SignUpSubmitBtn from '../../components/SmallUIBits/SignUpSubmitBtn';
class SignUp extends React.Component {
render() {
return (
<div className="Middle col-lg-6 col-md-12 col-sm-12 col-xs-12">
<UserSignUpForm />
<SignUpSubmitBtn />
</div>
);
}
}
export default SignUp;
My redux-thunk action:
export const onHandleInfoSubmit = values => {
// trim data
const userInfo = Object.keys(values).reduce((previous, current) => {
previous[current] = values[current].trim();
return previous;
}, {});
const {
email,
password,
} = userInfo;
console.log(userInfo);
console.log('creating with email and password:');
console.log(email);
console.log(password);
//^^ Works fine. No problems submitting info.
//vv Does nothing. Return never returns.
return dispatch => {
// Auth imported from database.js
console.log('Creating new account);
auth.createUserWithEmailAndPassword(email, password)
.then(() => {
const {currentUser} = auth;
const userRef = database.ref(`users/${currentUser.uid}/data`);
userRef.set({
uid: currentUser.uid,
email: currentUser.email,
emailVerified: currentUser.emailVerified,
});
console.log('Account created successfully');
},
err => {
const errorCode = err.code;
const errorMessage = err.message;
if (errorCode || errorMessage) {
dispatch(newUserAccountCreateError(errorMessage));
console.log(errorCode + errorMessage);
}
});
};
};
Finally figured this out.
As it turns out, I didn't need to return a function or use dispatch to trigger any of the actions I wanted to fire after successful form submit.
So my thunk action should in fact look like this.
export const onHandleInfoSubmit = values => {
// trim data
const userInfo = Object.keys(values).reduce((previous, current) => {
previous[current] = values[current].trim();
return previous;
}, {});
const {
email,
password,
} = userInfo;
console.log(userInfo);
console.log('creating with email and password:');
console.log(email);
console.log(password);
//^^ No change needed
//vv remove the return function and all instances of dispatch()
// Auth imported from database.js
console.log('Creating new account);
auth.createUserWithEmailAndPassword(email, password)
.then(() => {
const {currentUser} = auth;
const userRef = database.ref(`users/${currentUser.uid}/data`);
userRef.set({
uid: currentUser.uid,
email: currentUser.email,
emailVerified: currentUser.emailVerified,
});
console.log('Account created successfully');
},
err => {
const errorCode = err.code;
const errorMessage = err.message;
if (errorCode || errorMessage) {
newUserAccountCreateError(errorMessage);
console.log(errorCode + errorMessage);
}
});
};
I still don't know if this is the solution because I'm using redux-forms in general or because I'm using the remote submit feature of redux-forms (and I'm not changing my code to find out), but I hope this helps anyone else having the same problem.